Snap for 10103804 from 32df980cf4b3d7c0448a639c3f040bbc82074cc2 to mainline-tzdata5-release

Change-Id: Ifa0154b57fb9da651e8d8dca9884ca928596b73b
diff --git a/libs/TelephonyStatsLib/OWNERS b/libs/TelephonyStatsLib/OWNERS
new file mode 100644
index 0000000..7d5d12e
--- /dev/null
+++ b/libs/TelephonyStatsLib/OWNERS
@@ -0,0 +1,8 @@
+set noparent
+
+# for TelephonyStatsLib
+sewookseo@google.com
+sangyun@google.com
+seheele@google.com
+nagendranb@google.com
+mdungriyal@google.com
diff --git a/services/QualifiedNetworksService/Android.bp b/services/QualifiedNetworksService/Android.bp
index 1a5426f..80e6d14 100644
--- a/services/QualifiedNetworksService/Android.bp
+++ b/services/QualifiedNetworksService/Android.bp
@@ -17,6 +17,13 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
+genrule {
+  name: "statslog-qns-java-gen",
+  tools: ["stats-log-api-gen"],
+  cmd: "$(location stats-log-api-gen) --java $(out) --module qns --javaPackage com.android.telephony.qns.stats --javaClass QnsStatsLog --worksource",
+  out: ["com/android/telephony/qns/stats/QnsStatsLog.java"],
+}
+
 android_app {
     name: "QualifiedNetworksService",
     system_ext_specific: true,
@@ -25,11 +32,13 @@
     srcs: [
         "src/**/*.java",
         "src/**/I*.aidl",
+        ":statslog-qns-java-gen",
     ],
 
     static_libs: [
         "androidx.appcompat_appcompat",
         "androidx.browser_browser",
+        "TelephonyStatsLib",
     ],
 
     libs: [
@@ -72,6 +81,7 @@
         "src/**/*.java",
         "src/**/I*.aidl",
         "tests/**/*.java",
+        ":statslog-qns-java-gen",
     ],
     libs: [
         "android.test.runner",
@@ -90,8 +100,9 @@
         "frameworks-base-testutils",
         "mockito-target-extended-minus-junit4",
         "platform-test-annotations",
-         "truth-prebuilt",
-         "testables",
+        "truth-prebuilt",
+        "testables",
+        "TelephonyStatsLib",
     ],
     jni_libs: [
         "libdexmakerjvmtiagent",
diff --git a/services/QualifiedNetworksService/AndroidManifest.xml b/services/QualifiedNetworksService/AndroidManifest.xml
index 5170124..5abdbf9 100644
--- a/services/QualifiedNetworksService/AndroidManifest.xml
+++ b/services/QualifiedNetworksService/AndroidManifest.xml
@@ -26,6 +26,8 @@
   <uses-permission android:name="android.permission.READ_PHONE_STATE" />
   <uses-permission android:name="android.permission.READ_PRECISE_PHONE_STATE" />
   <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
+  <uses-permission android:name="android.permission.REGISTER_STATS_PULL_ATOM" />
+  <uses-permission android:name="android.permission.USE_EXACT_ALARM"/>
   <application
       android:directBootAware="true"
       android:defaultToDeviceProtectedStorage="true">
diff --git a/services/QualifiedNetworksService/privapp-permissions_com.android.telephony.qns.xml b/services/QualifiedNetworksService/privapp-permissions_com.android.telephony.qns.xml
index 223aa19..3f31bcf 100644
--- a/services/QualifiedNetworksService/privapp-permissions_com.android.telephony.qns.xml
+++ b/services/QualifiedNetworksService/privapp-permissions_com.android.telephony.qns.xml
@@ -20,5 +20,6 @@
         <permission name="android.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP"/>
         <permission name="android.permission.READ_PRECISE_PHONE_STATE"/>
         <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
+        <permission name="android.permission.REGISTER_STATS_PULL_ATOM" />
     </privapp-permissions>
 </permissions>
diff --git a/services/QualifiedNetworksService/src/com/android/telephony/qns/AccessNetworkEvaluator.java b/services/QualifiedNetworksService/src/com/android/telephony/qns/AccessNetworkEvaluator.java
index d708f2d..f77da91 100644
--- a/services/QualifiedNetworksService/src/com/android/telephony/qns/AccessNetworkEvaluator.java
+++ b/services/QualifiedNetworksService/src/com/android/telephony/qns/AccessNetworkEvaluator.java
@@ -26,6 +26,7 @@
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
+import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.ProvisioningManager;
 import android.util.Log;
 
@@ -61,8 +62,13 @@
     private static final int EVENT_IMS_REGISTRATION_STATE_CHANGED = EVENT_BASE + 10;
     private static final int EVENT_WIFI_RTT_STATUS_CHANGED = EVENT_BASE + 11;
     private static final int EVENT_SIP_DIALOG_SESSION_STATE_CHANGED = EVENT_BASE + 12;
+    private static final int EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED = EVENT_BASE + 13;
     private static final int EVALUATE_SPECIFIC_REASON_NONE = 0;
     private static final int EVALUATE_SPECIFIC_REASON_IWLAN_DISABLE = 1;
+    private static final int EVALUATE_SPECIFIC_REASON_DATA_DISCONNECTED = 2;
+    private static final int EVALUATE_SPECIFIC_REASON_DATA_FAILED = 3;
+    private static final int EVALUATE_SPECIFIC_REASON_DATA_CONNECTED = 4;
+
     protected final int mSlotIndex;
     protected final Context mContext;
     private final String mLogTag;
@@ -79,11 +85,13 @@
     protected IwlanNetworkStatusTracker mIwlanNetworkStatusTracker;
     protected DataConnectionStatusTracker mDataConnectionStatusTracker;
     protected QnsEventDispatcher mQnsEventDispatcher;
-    protected AlternativeEventListener mAltEventListener;
     protected QnsCallStatusTracker mCallStatusTracker;
     protected QnsProvisioningListener mQnsProvisioningListener;
     protected QnsImsManager mQnsImsManager;
     protected WifiBackhaulMonitor mWifiBackhaulMonitor;
+    protected QnsTelephonyListener mQnsTelephonyListener;
+    // for metric
+    protected QnsMetrics mQnsMetrics;
 
     protected int mCellularAccessNetworkType = AccessNetworkType.UNKNOWN;
     protected boolean mCellularAvailable = false;
@@ -113,6 +121,7 @@
     private boolean mSipDialogSessionState = false;
     private int mCachedTransportTypeForEmergencyInitialConnect =
             AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
+    private int mLastEvaluateSpecificReason = EVALUATE_SPECIFIC_REASON_NONE;
 
     AccessNetworkEvaluator(QnsComponents qnsComponents, int netCapability, int slotIndex) {
         mNetCapability = netCapability;
@@ -139,7 +148,6 @@
         Executor executor = new QnsUtils.QnsExecutor(mHandler);
 
         mConfigManager = mQnsComponents.getQnsCarrierConfigManager(mSlotIndex);
-        mAltEventListener = mQnsComponents.getAlternativeEventListener(mSlotIndex);
         mCallStatusTracker = mQnsComponents.getQnsCallStatusTracker(mSlotIndex);
         mQnsProvisioningListener = mQnsComponents.getQnsProvisioningListener(mSlotIndex);
         mIwlanNetworkStatusTracker = mQnsComponents.getIwlanNetworkStatusTracker();
@@ -151,6 +159,8 @@
                         mNetCapability);
         mQnsImsManager = mQnsComponents.getQnsImsManager(mSlotIndex);
         mWifiBackhaulMonitor = mQnsComponents.getWifiBackhaulMonitor(mSlotIndex);
+        mQnsTelephonyListener = mQnsComponents.getQnsTelephonyListener(mSlotIndex);
+        mQnsMetrics = mQnsComponents.getQnsMetrics();
 
         // Pre-Conditions
         mCellularNetworkStatusTracker = mQnsComponents.getCellularNetworkStatusTracker(mSlotIndex);
@@ -204,11 +214,12 @@
         mIwlanNetworkStatusTracker = mQnsComponents.getIwlanNetworkStatusTracker();
         mDataConnectionStatusTracker = dataConnectionStatusTracker;
         mQnsEventDispatcher = mQnsComponents.getQnsEventDispatcher(mSlotIndex);
-        mAltEventListener = mQnsComponents.getAlternativeEventListener(mSlotIndex);
         mCallStatusTracker = mQnsComponents.getQnsCallStatusTracker(mSlotIndex);
         mQnsProvisioningListener = mQnsComponents.getQnsProvisioningListener(mSlotIndex);
         mQnsImsManager = mQnsComponents.getQnsImsManager(mSlotIndex);
         mWifiBackhaulMonitor = mQnsComponents.getWifiBackhaulMonitor(mSlotIndex);
+        mQnsTelephonyListener = mQnsComponents.getQnsTelephonyListener(mSlotIndex);
+        mQnsMetrics = mQnsComponents.getQnsMetrics();
         mHandlerThread =
                 new HandlerThread(AccessNetworkEvaluator.class.getSimpleName() + mNetCapability);
         mHandlerThread.start();
@@ -244,6 +255,7 @@
                         isAllowed(AccessNetworkConstants.TRANSPORT_TYPE_WWAN))) {
             mHandler.post(this::evaluate);
         }
+        mLastEvaluateSpecificReason = EVALUATE_SPECIFIC_REASON_NONE;
     }
 
     void close() {
@@ -323,6 +335,9 @@
         QualifiedNetworksInfo info = new QualifiedNetworksInfo(mNetCapability, accessNetworkTypes);
         QnsAsyncResult ar = new QnsAsyncResult(null, info, null);
         mQualifiedNetworksChangedRegistrants.notifyRegistrants(ar);
+
+        // metrics
+        sendMetricsForQualifiedNetworks(info);
     }
 
     private void initSettings() {
@@ -379,6 +394,10 @@
         }
         mQnsProvisioningListener.registerProvisioningItemInfoChanged(
                 mHandler, EVENT_PROVISIONING_INFO_CHANGED, null, true);
+        if (mNetCapability == NetworkCapabilities.NET_CAPABILITY_IMS) {
+            mQnsTelephonyListener.registerImsCallDropDisconnectCauseListener(
+                    mHandler, EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED, null);
+        }
         List<Integer> events = new ArrayList<>();
         events.add(QnsEventDispatcher.QNS_EVENT_WFC_ENABLED);
         events.add(QnsEventDispatcher.QNS_EVENT_WFC_DISABLED);
@@ -415,6 +434,9 @@
                 mWifiBackhaulMonitor.unRegisterForRttStatusChange(mHandler);
             }
         }
+        if (mNetCapability == NetworkCapabilities.NET_CAPABILITY_IMS) {
+            mQnsTelephonyListener.unregisterImsCallDropDisconnectCauseListener(mHandler);
+        }
         mQnsProvisioningListener.unregisterProvisioningItemInfoChanged(mHandler);
         mQnsEventDispatcher.unregisterEvent(mHandler);
         mRestrictManager.unRegisterRestrictInfoChanged(mHandler);
@@ -518,7 +540,9 @@
 
     protected void onIwlanNetworkStatusChanged(IwlanAvailabilityInfo info) {
         if (info != null) {
-            mIwlanAvailable = info.getIwlanAvailable();
+            if (mIwlanAvailable != info.getIwlanAvailable()) {
+                mIwlanAvailable = info.getIwlanAvailable();
+            }
             mIsCrossWfc = info.isCrossWfc();
             log("onIwlanNetworkStatusChanged IwlanAvailable:" + mIwlanAvailable);
             if (info.getNotifyIwlanDisabled()) {
@@ -656,6 +680,10 @@
                 && callType == QnsConstants.CALL_TYPE_EMERGENCY) {
             if (!mDataConnectionStatusTracker.isActiveState()) return;
         }
+
+        // metrics
+        sendMetricsForCallTypeChanged(mCallType, callType);
+
         mCallType = callType;
         mRestrictManager.setQnsCallType(mCallType);
         log("onSetCallType CallType:" + mCallType);
@@ -675,7 +703,11 @@
                     log(
                             "onEmergencyPreferredTransportTypeChanged transport:"
                                     + QnsConstants.transportTypeToString(transport));
-                    if (mDataConnectionStatusTracker.isInactiveState()) {
+                    if (mDataConnectionStatusTracker.isInactiveState()
+                            || (mDataConnectionStatusTracker.isActiveState()
+                                    && mCallType == QnsConstants.CALL_TYPE_IDLE)) {
+                        // If data network state is inactive OR active but call is not active yet,
+                        // QNS will follow domain selection's decision.
                         enforceNotifyQualifiedNetworksWithTransportType(transport);
                     } else {
                         log(
@@ -704,32 +736,54 @@
             DataConnectionStatusTracker.DataConnectionChangedInfo info) {
         log("onDataConnectionStateChanged info:" + info);
         boolean needEvaluate = false;
+        int evaluateSpecificReason = EVALUATE_SPECIFIC_REASON_NONE;
         switch (info.getEvent()) {
             case DataConnectionStatusTracker.EVENT_DATA_CONNECTION_DISCONNECTED:
+                evaluateSpecificReason = EVALUATE_SPECIFIC_REASON_DATA_DISCONNECTED;
                 if (mNetCapability == NetworkCapabilities.NET_CAPABILITY_EIMS) {
                     // If FWK guided emergency's transport type during data connected state, notify
                     // the transport type when the data connection is disconnected.
                     notifyCachedTransportTypeForEmergency();
+                    // When cellular is not available, CellularQualityMonitor will stop listening to
+                    // TelephonyCallback. Thresholds for Emergency apn may not be reset as ANE for
+                    // emergency only evaluates while data is connected. CellularQualityMonitor will
+                    // not listen to TelephonyCallback again until the set of threshold values
+                    // changed. Resetting thresholds for emergency after the emergency connection
+                    // disconnects.
+                    unregisterThresholdToQualityMonitor();
                 } else {
                     needEvaluate = true;
                     initLastNotifiedQualifiedNetwork();
                 }
                 break;
             case DataConnectionStatusTracker.EVENT_DATA_CONNECTION_CONNECTED:
+                evaluateSpecificReason = EVALUATE_SPECIFIC_REASON_DATA_CONNECTED;
                 mHandler.post(() -> onDataConnectionConnected(info.getTransportType()));
                 break;
             case DataConnectionStatusTracker.EVENT_DATA_CONNECTION_FAILED:
+                evaluateSpecificReason = EVALUATE_SPECIFIC_REASON_DATA_FAILED;
                 if (mNetCapability == NetworkCapabilities.NET_CAPABILITY_EIMS) {
                     // If FWK guided emergency's transport type during data connecting state, notify
                     // the transport type when the data connection is failed.
                     notifyCachedTransportTypeForEmergency();
+                    // When cellular is not available, CellularQualityMonitor will stop listening to
+                    // TelephonyCallback. Thresholds for Emergency apn may not be reset as ANE for
+                    // emergency only evaluates while data is connected. CellularQualityMonitor will
+                    // not listen to TelephonyCallback again until the set of threshold values
+                    // changed. Resetting thresholds for emergency after the emergency connection
+                    // disconnects.
+                    unregisterThresholdToQualityMonitor();
                 } else {
                     needEvaluate = true;
                 }
                 break;
         }
+
+        // metrics
+        sendMetricsForDataConnectionChanged(info);
+
         if (needEvaluate) {
-            evaluate();
+            evaluate(evaluateSpecificReason);
         }
     }
 
@@ -891,6 +945,26 @@
         }
     }
 
+    protected void onImsCallDisconnectCauseChanged(ImsReasonInfo imsReasonInfo) {
+        if (imsReasonInfo == null) {
+            return;
+        }
+        if (mNetCapability == NetworkCapabilities.NET_CAPABILITY_IMS
+                && imsReasonInfo.getCode() == ImsReasonInfo.CODE_MEDIA_NO_DATA) {
+            log(
+                    "onImsCallDisconnectCauseChanged: iwlanAvailable="
+                            + mIwlanAvailable
+                            + " cellularAvailable="
+                            + mCellularAvailable
+                            + " imsReasonInfo="
+                            + imsReasonInfo);
+            if (mIwlanAvailable && mCellularAvailable) {
+                // metrics
+                sendMetricsForImsCallDropStats();
+            }
+        }
+    }
+
     protected void onCellularQualityChanged(Threshold[] ths) {
         if (ths == null || ths.length == 0) {
             log("onCellularQualityChanged: E threshold is null");
@@ -1168,11 +1242,13 @@
             if (DBG) log("ANE is not initialized yet.");
             return;
         }
+        mLastEvaluateSpecificReason = specificReason;
         log("evaluate reason:" + evaluateSpecificReasonToString(specificReason));
-        if (mNetCapability == NetworkCapabilities.NET_CAPABILITY_EIMS
-                && mDataConnectionStatusTracker.isInactiveState()) {
-            log("QNS only handles HO of EMERGENCY data connection");
-            return;
+        if (mNetCapability == NetworkCapabilities.NET_CAPABILITY_EIMS) {
+            if (!mDataConnectionStatusTracker.isActiveState()) {
+                log("QNS only handles HO of EMERGENCY data connection");
+                return;
+            }
         }
 
         /* Check handover policy */
@@ -1350,7 +1426,7 @@
             return true;
         } else {
             if (mNetCapability == NetworkCapabilities.NET_CAPABILITY_IMS
-                    && mCallType == QnsConstants.CALL_TYPE_IDLE) {
+                    && mCallStatusTracker.isCallIdle(NetworkCapabilities.NET_CAPABILITY_IMS)) {
                 // Telephony will make new connection with preferred AccessNetwork
                 log("handover is not allowed. but need to move to target Transport.");
                 return true;
@@ -1871,6 +1947,12 @@
             return "EVALUATE_SPECIFIC_REASON_NONE";
         } else if (specificReason == EVALUATE_SPECIFIC_REASON_IWLAN_DISABLE) {
             return "EVALUATE_SPECIFIC_REASON_IWLAN_DISABLE";
+        } else if (specificReason == EVALUATE_SPECIFIC_REASON_DATA_DISCONNECTED) {
+            return "EVALUATE_SPECIFIC_REASON_DATA_DISCONNECTED";
+        } else if (specificReason == EVALUATE_SPECIFIC_REASON_DATA_FAILED) {
+            return "EVALUATE_SPECIFIC_REASON_DATA_FAILED";
+        } else if (specificReason == EVALUATE_SPECIFIC_REASON_DATA_CONNECTED) {
+            return "EVALUATE_SPECIFIC_REASON_DATA_CONNECTED";
         }
         return "UNKNOWN";
     }
@@ -1921,6 +2003,9 @@
                 case EVENT_SIP_DIALOG_SESSION_STATE_CHANGED:
                     onSipDialogSessionStateChanged((boolean) ar.mResult);
                     break;
+                case EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED:
+                    onImsCallDisconnectCauseChanged((ImsReasonInfo) ar.mResult);
+                    break;
                 case QnsEventDispatcher.QNS_EVENT_WFC_ENABLED:
                     onWfcEnabledChanged(true, false);
                     break;
@@ -2066,4 +2151,50 @@
     boolean getSipDialogSessionState() {
         return mSipDialogSessionState;
     }
+
+    private void sendMetricsForQualifiedNetworks(QualifiedNetworksInfo info) {
+        if (!mCellularAvailable
+                || !mIwlanAvailable
+                || mCellularAccessNetworkType == AccessNetworkType.UNKNOWN
+                || mLastEvaluateSpecificReason == EVALUATE_SPECIFIC_REASON_DATA_DISCONNECTED) {
+            // b/268557926, decided to cut off if WWAN and WLAN are not in contention.
+            return;
+        }
+        mQnsMetrics.reportAtomForQualifiedNetworks(
+                info,
+                mSlotIndex,
+                mDataConnectionStatusTracker.getLastTransportType(),
+                mCoverage,
+                mSettingWfcEnabled,
+                mSettingWfcRoamingEnabled,
+                mSettingWfcMode,
+                mSettingWfcRoamingMode,
+                mCellularAccessNetworkType,
+                mIwlanAvailable,
+                mIsCrossWfc,
+                mRestrictManager,
+                mCellularQualityMonitor,
+                mWifiQualityMonitor,
+                mCallType);
+    }
+
+    private void sendMetricsForCallTypeChanged(int oldCallType, int newCallType) {
+        int transportTypeOfCall = mDataConnectionStatusTracker.getLastTransportType();
+        mQnsMetrics.reportAtomForCallTypeChanged(mNetCapability, mSlotIndex,
+                oldCallType, newCallType, mRestrictManager, transportTypeOfCall);
+    }
+
+    private void sendMetricsForDataConnectionChanged(
+            DataConnectionStatusTracker.DataConnectionChangedInfo info) {
+        mQnsMetrics.reportAtomForDataConnectionChanged(
+                mNetCapability, mSlotIndex, info, mConfigManager.getCarrierId());
+    }
+
+    private void sendMetricsForImsCallDropStats() {
+        int transportTypeOfCall = mDataConnectionStatusTracker.getLastTransportType();
+        mQnsMetrics.reportAtomForImsCallDropStats(mNetCapability, mSlotIndex, mRestrictManager,
+                mCellularQualityMonitor, mWifiQualityMonitor, transportTypeOfCall,
+                mCellularAccessNetworkType);
+    }
+
 }
diff --git a/services/QualifiedNetworksService/src/com/android/telephony/qns/AlternativeEventListener.java b/services/QualifiedNetworksService/src/com/android/telephony/qns/AlternativeEventListener.java
deleted file mode 100644
index 36fdaa4..0000000
--- a/services/QualifiedNetworksService/src/com/android/telephony/qns/AlternativeEventListener.java
+++ /dev/null
@@ -1,501 +0,0 @@
-/*
- * Copyright (C) 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.telephony.qns;
-
-import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_ACTIVE;
-import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_ALERTING;
-import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_DIALING;
-import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_DISCONNECTED;
-import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_HOLDING;
-import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_IDLE;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.net.NetworkCapabilities;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.telephony.Annotation;
-import android.telephony.PreciseDataConnectionState;
-import android.telephony.TelephonyManager;
-import android.util.Log;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-class AlternativeEventListener {
-    private static final int EVENT_SRVCC_STATE_CHANGED = 13001;
-    private static final SparseArray<AlternativeEventListener> sAlternativeInterfaceManager =
-            new SparseArray<>();
-    private final String mLogTag;
-    private Context mContext;
-    private int mSlotIndex;
-    private QnsTelephonyListener mQnsTelephonyListener;
-    private QnsRegistrant mCallTypeChangedEventListener;
-    private QnsRegistrant mEmergencyCallTypeChangedEventListener;
-    private QnsRegistrant mEmergencyPreferredTransportTypeChanged;
-    private QnsRegistrant mTryWfcConnectionState;
-    private QnsRegistrant mLowRtpQuallityListener;
-    private QnsRegistrant mEmcLowRtpQuallityListener;
-    private AlternativeEventCb mEventCb;
-    private AlternativeEventProvider mEventProvider;
-    private Handler mHandler;
-    private CallInfoManager mCallInfoManager = new CallInfoManager();
-
-    class CallInfo {
-        int mId;
-        int mType;
-        int mState;
-
-        CallInfo(int id, int type, int state) {
-            mId = id;
-            mType = type;
-            mState = state;
-        }
-    }
-
-    /** Manager of CallInfo list. */
-    private class CallInfoManager {
-        private SparseArray<CallInfo> mCallInfos = new SparseArray<>();
-        QnsCarrierConfigManager.RtpMetricsConfig mRtpMetricsConfig;
-        int mEmergencyCallState = PRECISE_CALL_STATE_IDLE;
-        int mLastReportedCallType = QnsConstants.CALL_TYPE_IDLE;
-
-        CallInfoManager() {
-            mRtpMetricsConfig = null;
-        }
-
-        void updateCallInfo(
-                int id,
-                @QnsConstants.QnsCallType int type,
-                @Annotation.PreciseCallStates int state) {
-            if (mRtpMetricsConfig != null && state == PRECISE_CALL_STATE_ACTIVE) {
-                requestRtpThreshold(mRtpMetricsConfig);
-            }
-            if (type == QnsConstants.CALL_TYPE_EMERGENCY) {
-                mEmergencyCallState = state;
-                return;
-            }
-            if (type == QnsConstants.CALL_TYPE_VIDEO || type == QnsConstants.CALL_TYPE_VOICE) {
-                CallInfo info = mCallInfos.get(id);
-                if (info == null) {
-                    if (state == PRECISE_CALL_STATE_ACTIVE) {
-                        mCallInfos.put(id, new CallInfo(id, type, state));
-                        log("add callinfo with id " + id);
-                    }
-                } else {
-                    if (info.mType != type) {
-                        log("CallId[" + info.mId + "] type changed " + info.mType + " > " + id);
-                        info.mType = type;
-                    }
-                    if (state == PRECISE_CALL_STATE_HOLDING) {
-                        log("CallId[" + info.mId + "] changed to HOLDING ");
-                        info.mState = state;
-                    } else if (state == PRECISE_CALL_STATE_ACTIVE) {
-                        log("CallId[" + info.mId + "] changed to ACTIVE ");
-                        info.mState = state;
-                    } else if (state == PRECISE_CALL_STATE_DISCONNECTED) {
-                        log("delete callInfo callId:" + id);
-                        mCallInfos.remove(id);
-                    }
-                }
-            }
-        }
-
-        boolean isCallIdle() {
-            return mCallInfos.size() == 0;
-        }
-
-        CallInfo getActiveCallInfo() {
-            for (int i = 0; i < mCallInfos.size(); i++) {
-                int key = mCallInfos.keyAt(i);
-                if (mCallInfos.get(key).mState == PRECISE_CALL_STATE_ACTIVE) {
-                    return mCallInfos.get(key);
-                }
-            }
-            return null;
-        }
-
-        void clearCallInfo() {
-            mCallInfos.clear();
-        }
-
-        boolean hasVideoCall() {
-            for (int i = 0; i < mCallInfos.size(); i++) {
-                int key = mCallInfos.keyAt(i);
-                if (mCallInfos.get(key).mType == QnsConstants.CALL_TYPE_VIDEO) {
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        void requestRtpThreshold(QnsCarrierConfigManager.RtpMetricsConfig config) {
-            if (config != null) {
-                mRtpMetricsConfig =
-                        new QnsCarrierConfigManager.RtpMetricsConfig(
-                                config.mJitter,
-                                config.mPktLossRate,
-                                config.mPktLossTime,
-                                config.mNoRtpInterval);
-            }
-            CallInfo info = getActiveCallInfo();
-            if (mEventProvider != null && info != null) {
-                log("requestRtpThreshold callId:" + info.mId + " " + config.toString());
-                mEventProvider.requestRtpThreshold(
-                        info.mId,
-                        mRtpMetricsConfig.mJitter,
-                        mRtpMetricsConfig.mPktLossRate,
-                        mRtpMetricsConfig.mPktLossTime,
-                        mRtpMetricsConfig.mNoRtpInterval);
-            }
-        }
-    }
-
-    class MessageHandler extends Handler {
-        MessageHandler(Looper l) {
-            super(l);
-        }
-
-        @Override
-        public void handleMessage(Message message) {
-            Log.d(mLogTag, "handleMessage msg=" + message.what);
-            QnsAsyncResult ar = (QnsAsyncResult) message.obj;
-            int state = (int) ar.mResult;
-            switch (message.what) {
-                case EVENT_SRVCC_STATE_CHANGED:
-                    onSrvccStateChanged(state);
-                    break;
-                default:
-                    Log.d(mLogTag, "Unknown message received!");
-                    break;
-            }
-        }
-    }
-
-    /**
-     * AlternativeEventListener constructor.
-     */
-    AlternativeEventListener(
-            Context context, QnsTelephonyListener qnsTelephonyListener, int slotId) {
-        mSlotIndex = slotId;
-        mLogTag =
-                QnsConstants.QNS_TAG
-                        + "_"
-                        + AlternativeEventListener.class.getSimpleName()
-                        + "_"
-                        + mSlotIndex;
-        mContext = context;
-        mQnsTelephonyListener = qnsTelephonyListener;
-        mHandler = new AlternativeEventListener.MessageHandler(mContext.getMainLooper());
-        mEventCb = new AlternativeEventCb();
-    }
-
-    /**
-     * register emergency preferred transport type changed event.
-     *
-     * @param h Handler want to receive event.
-     * @param what event Id to receive
-     * @param userObj user object
-     */
-    void registerEmergencyPreferredTransportTypeChanged(
-            @NonNull Handler h, int what, Object userObj) {
-        mEmergencyPreferredTransportTypeChanged = new QnsRegistrant(h, what, userObj);
-    }
-
-    /** Unregister emergency preferred transport type changed event. */
-    void unregisterEmergencyPreferredTransportTypeChanged() {
-        mEmergencyPreferredTransportTypeChanged = null;
-    }
-
-    /**
-     * register try WFC connection state change event.
-     *
-     * @param h Handler want to receive event
-     * @param what event Id to receive
-     * @param userObj user object
-     */
-    void registerTryWfcConnectionStateListener(@NonNull Handler h, int what, Object userObj) {
-        mTryWfcConnectionState = new QnsRegistrant(h, what, userObj);
-    }
-
-    /**
-     * Register low RTP quality event.
-     *
-     * @param netCapability Network Capability
-     * @param h Handler want to receive event.
-     * @param what event Id to receive
-     * @param userObj user object
-     */
-    void registerLowRtpQualityEvent(
-            int netCapability,
-            @NonNull Handler h,
-            int what,
-            Object userObj,
-            QnsCarrierConfigManager.RtpMetricsConfig config) {
-        if (h != null) {
-            QnsRegistrant r = new QnsRegistrant(h, what, userObj);
-            if (netCapability == NetworkCapabilities.NET_CAPABILITY_IMS) {
-                mLowRtpQuallityListener = r;
-            } else if (netCapability == NetworkCapabilities.NET_CAPABILITY_EIMS) {
-                mEmcLowRtpQuallityListener = r;
-            }
-            if (mEventProvider != null) {
-                mCallInfoManager.requestRtpThreshold(config);
-            }
-        }
-    }
-
-    /**
-     * Unregister low RTP quality event.
-     *
-     * @param netCapability Network Capability
-     * @param h Handler want to receive event.
-     */
-    void unregisterLowRtpQualityEvent(int netCapability, @NonNull Handler h) {
-        if (netCapability == NetworkCapabilities.NET_CAPABILITY_IMS) {
-            mLowRtpQuallityListener = null;
-        } else if (netCapability == NetworkCapabilities.NET_CAPABILITY_EIMS) {
-            mEmcLowRtpQuallityListener = null;
-        }
-        mCallInfoManager.mRtpMetricsConfig = null;
-    }
-
-    /**
-     * register call type changed event.
-     *
-     * @param netCapability Network Capability of caller
-     * @param h Handler want to receive event.
-     * @param what event Id to receive
-     * @param userObj user object
-     */
-    void registerCallTypeChangedListener(
-            int netCapability, @NonNull Handler h, int what, Object userObj) {
-        if (netCapability != NetworkCapabilities.NET_CAPABILITY_IMS
-                && netCapability != NetworkCapabilities.NET_CAPABILITY_EIMS) {
-            log("registerCallTypeChangedListener : wrong netCapability");
-            return;
-        }
-        if (h != null) {
-            QnsRegistrant r = new QnsRegistrant(h, what, userObj);
-            if (netCapability == NetworkCapabilities.NET_CAPABILITY_IMS) {
-                mCallTypeChangedEventListener = r;
-                mQnsTelephonyListener.registerSrvccStateListener(
-                        mHandler, EVENT_SRVCC_STATE_CHANGED, null);
-            } else if (netCapability == NetworkCapabilities.NET_CAPABILITY_EIMS) {
-                mEmergencyCallTypeChangedEventListener = r;
-            }
-        } else {
-            log("registerCallTypeChangedListener : Handler is Null");
-        }
-    }
-
-    /**
-     * Unregister call type changed event.
-     *
-     * @param netCapability Network Capability of caller
-     * @param h Handler want to receive event.
-     */
-    void unregisterCallTypeChangedListener(int netCapability, @NonNull Handler h) {
-        if (netCapability != NetworkCapabilities.NET_CAPABILITY_IMS
-                && netCapability != NetworkCapabilities.NET_CAPABILITY_EIMS) {
-            log("unregisterCallTypeChangedListener : wrong netCapability");
-            return;
-        }
-        if (h != null) {
-            if (netCapability == NetworkCapabilities.NET_CAPABILITY_IMS) {
-                mCallTypeChangedEventListener = null;
-                mQnsTelephonyListener.unregisterSrvccStateChanged(mHandler);
-            } else if (netCapability == NetworkCapabilities.NET_CAPABILITY_EIMS) {
-                mEmergencyCallTypeChangedEventListener = null;
-            }
-        } else {
-            log("unregisterCallTypeChangedListener : Handler is Null");
-        }
-    }
-
-    /**
-     * register EventProvider to EventListener
-     *
-     * @param provider extends of AlternativeEventProvider
-     */
-    void setEventProvider(AlternativeEventProvider provider) {
-        log("setEventProvider provider " + provider);
-        mEventProvider = provider;
-        provider.registerCallBack(mEventCb);
-    }
-
-    @VisibleForTesting
-    void onSrvccStateChanged(int srvccState) {
-        if (srvccState == TelephonyManager.SRVCC_STATE_HANDOVER_COMPLETED) {
-            mCallInfoManager.clearCallInfo();
-            int callType = QnsConstants.CALL_TYPE_IDLE;
-            mCallTypeChangedEventListener.notifyResult(callType);
-        }
-    }
-
-    void clearNormalCallInfo() {
-        mCallInfoManager.clearCallInfo();
-        mCallInfoManager.mLastReportedCallType = QnsConstants.CALL_TYPE_IDLE;
-        unregisterLowRtpQualityEvent(NetworkCapabilities.NET_CAPABILITY_IMS, null);
-    }
-
-    @VisibleForTesting
-    void notifyRtpLowQuality(int callType, int reason) {
-        if (callType == QnsConstants.CALL_TYPE_VOICE) {
-            if (mLowRtpQuallityListener != null) {
-                mLowRtpQuallityListener.notifyResult(reason);
-            } else {
-                log("notifyRtpLowQuality mLowRtpQuallityListener is null.");
-            }
-        } else if (callType == QnsConstants.CALL_TYPE_EMERGENCY) {
-            if (mEmcLowRtpQuallityListener != null) {
-                mEmcLowRtpQuallityListener.notifyResult(reason);
-            } else {
-                log("notifyRtpLowQuality mEmcLowRtpQuallityListener is null.");
-            }
-            if (mCallInfoManager.mLastReportedCallType == QnsConstants.CALL_TYPE_EMERGENCY) {
-                if (mLowRtpQuallityListener != null) {
-                    log("notifyRtpLowQuality for emergency call to IMS ANE");
-                    mLowRtpQuallityListener.notifyResult(reason);
-                } else {
-                    log("notifyRtpLowQuality mLowRtpQuallityListener is null.");
-                }
-            }
-        }
-    }
-
-    class AlternativeEventCb implements AlternativeEventProvider.EventCallback {
-        @Override
-        public void onCallInfoChanged(
-                int id,
-                @QnsConstants.QnsCallType int type,
-                @Annotation.PreciseCallStates int state) {
-            log("onCallInfoChanged callId" + id + "  type" + type + "  state" + state);
-
-            mCallInfoManager.updateCallInfo(id, type, state);
-            if (type == QnsConstants.CALL_TYPE_EMERGENCY) {
-                if (mEmergencyCallTypeChangedEventListener != null) {
-                    if (state == PRECISE_CALL_STATE_DISCONNECTED) {
-                        mEmergencyCallTypeChangedEventListener.notifyResult(
-                                QnsConstants.CALL_TYPE_IDLE);
-                    } else if (state == PRECISE_CALL_STATE_ACTIVE) {
-                        mEmergencyCallTypeChangedEventListener.notifyResult(
-                                QnsConstants.CALL_TYPE_EMERGENCY);
-                    }
-                }
-                if (mCallTypeChangedEventListener != null) {
-                    if ((state == PRECISE_CALL_STATE_ACTIVE
-                                    || state == PRECISE_CALL_STATE_DIALING
-                                    || state == PRECISE_CALL_STATE_ALERTING)
-                            && !isDataNetworkConnected(NetworkCapabilities.NET_CAPABILITY_EIMS)
-                            && isDataNetworkConnected(NetworkCapabilities.NET_CAPABILITY_IMS)) {
-                        log("Emergency call is progressing without emergency PDN");
-                        if (mCallInfoManager.mLastReportedCallType
-                                != QnsConstants.CALL_TYPE_EMERGENCY) {
-                            mCallTypeChangedEventListener.notifyResult(
-                                    QnsConstants.CALL_TYPE_EMERGENCY);
-                            mCallInfoManager.mLastReportedCallType =
-                                    QnsConstants.CALL_TYPE_EMERGENCY;
-                        }
-                    } else if (state == PRECISE_CALL_STATE_DISCONNECTED) {
-                        log("Emergency call disconnected");
-                        if (mCallInfoManager.mLastReportedCallType
-                                == QnsConstants.CALL_TYPE_EMERGENCY) {
-                            mCallTypeChangedEventListener.notifyResult(QnsConstants.CALL_TYPE_IDLE);
-                            mCallInfoManager.mLastReportedCallType = QnsConstants.CALL_TYPE_IDLE;
-                        }
-                    }
-                }
-                return;
-            }
-            if (mCallTypeChangedEventListener != null) {
-                int callType = QnsConstants.CALL_TYPE_IDLE;
-                if (mCallInfoManager.isCallIdle()) {
-                    callType = QnsConstants.CALL_TYPE_IDLE;
-                } else if (mCallInfoManager.hasVideoCall()) {
-                    callType = QnsConstants.CALL_TYPE_VIDEO;
-                } else {
-                    callType = QnsConstants.CALL_TYPE_VOICE;
-                }
-                if (mCallTypeChangedEventListener != null
-                        && mCallInfoManager.mLastReportedCallType != callType) {
-                    mCallTypeChangedEventListener.notifyResult(callType);
-                    mCallInfoManager.mLastReportedCallType = callType;
-                }
-            }
-        }
-
-        @Override
-        public void onVoiceRtpLowQuality(@QnsConstants.RtpLowQualityReason int reason) {
-            if (mCallInfoManager.mEmergencyCallState == PRECISE_CALL_STATE_ACTIVE) {
-                notifyRtpLowQuality(QnsConstants.CALL_TYPE_EMERGENCY, reason);
-            } else if (!mCallInfoManager.isCallIdle() && !mCallInfoManager.hasVideoCall()) {
-                notifyRtpLowQuality(QnsConstants.CALL_TYPE_VOICE, reason);
-            }
-        }
-
-        @Override
-        public void onEmergencyPreferenceChanged(int preferredTransportType) {
-            if (mEmergencyPreferredTransportTypeChanged != null) {
-                mEmergencyPreferredTransportTypeChanged.notifyResult(preferredTransportType);
-            }
-        }
-
-        @Override
-        public void onTryWfcConnectionStateChanged(boolean isEnabled) {
-            mTryWfcConnectionState.notifyResult(isEnabled);
-        }
-    }
-
-    protected boolean isIdleState() {
-        return mCallInfoManager.isCallIdle();
-    }
-
-    protected void setEcnoSignalThreshold(@Nullable int[] threshold) {
-        if (mEventProvider != null) {
-            mEventProvider.setEcnoSignalThreshold(threshold);
-        }
-    }
-
-    private boolean isDataNetworkConnected(int netCapability) {
-        PreciseDataConnectionState preciseDataStatus =
-                mQnsTelephonyListener.getLastPreciseDataConnectionState(netCapability);
-
-        if (preciseDataStatus == null) return false;
-        int state = preciseDataStatus.getState();
-        return (state == TelephonyManager.DATA_CONNECTED
-                || state == TelephonyManager.DATA_HANDOVER_IN_PROGRESS
-                || state == TelephonyManager.DATA_SUSPENDED);
-    }
-
-    @VisibleForTesting
-    protected void close() {
-        mCallTypeChangedEventListener = null;
-        mEmergencyCallTypeChangedEventListener = null;
-        mEmergencyPreferredTransportTypeChanged = null;
-        mTryWfcConnectionState = null;
-        mLowRtpQuallityListener = null;
-        mEmcLowRtpQuallityListener = null;
-        sAlternativeInterfaceManager.remove(mSlotIndex);
-    }
-
-    protected void log(String s) {
-        Log.d(mLogTag, s);
-    }
-}
diff --git a/services/QualifiedNetworksService/src/com/android/telephony/qns/AlternativeEventProvider.java b/services/QualifiedNetworksService/src/com/android/telephony/qns/AlternativeEventProvider.java
deleted file mode 100644
index b2ef78d..0000000
--- a/services/QualifiedNetworksService/src/com/android/telephony/qns/AlternativeEventProvider.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright (C) 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.telephony.qns;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.telephony.AccessNetworkConstants;
-import android.telephony.Annotation;
-import android.util.Log;
-
-/** AlternativeEventProvider class */
-public abstract class AlternativeEventProvider {
-    private EventCallback mEventCb;
-    private final String mLogTag;
-
-    public AlternativeEventProvider(AlternativeEventListener altEventListener, int slotId) {
-        mLogTag =
-                QnsConstants.QNS_TAG
-                        + "_"
-                        + AlternativeEventProvider.class.getSimpleName()
-                        + "_"
-                        + slotId;
-        altEventListener.setEventProvider(this);
-    }
-
-    /**
-     * Thins is to register RTP threshold.
-     *
-     * @param callId CallId
-     * @param jitter RTP jitter value
-     * @param packetLossRate RTP packet loss rate
-     * @param packetLossTimeInMilliSec timer for RTP packet loss
-     * @param noRtpTimeInMilliSec time for no incoming RTP
-     */
-    public abstract void requestRtpThreshold(
-            int callId,
-            int jitter,
-            int packetLossRate,
-            int packetLossTimeInMilliSec,
-            int noRtpTimeInMilliSec);
-
-    /**
-     * This is to set signal strength threshold.
-     *
-     * @param threshold signal strength threshold values
-     */
-    public abstract void setEcnoSignalThreshold(@Nullable int[] threshold);
-
-    /**
-     * Event provider calls this to notify call info changed.
-     *
-     * @param id CallId
-     * @param type CallType
-     * @param state CallState
-     */
-    public void notifyCallInfo(int id, int type, int state) {
-        if (mEventCb != null) {
-            mEventCb.onCallInfoChanged(id, type, state);
-        }
-    }
-
-    /**
-     * Event provider calls this to notify RTP low quality
-     *
-     * @param reason RTP low quality reason.
-     */
-    public void notifyRtpLowQuality(@QnsConstants.RtpLowQualityReason int reason) {
-        if (mEventCb != null) {
-            mEventCb.onVoiceRtpLowQuality(reason);
-        }
-    }
-
-    /**
-     * Event provider needs to call this to notify Emergency preference change
-     *
-     * @param type Emergency transport type preference for initial data connection.
-     */
-    public void notifyEmergencyPreferredTransportType(
-            @AccessNetworkConstants.TransportType int type) {
-        if (mEventCb != null) {
-            mEventCb.onEmergencyPreferenceChanged(type);
-        }
-    }
-
-    /**
-     * Event provider needs to call this to notify try WFC connection state change
-     *
-     * @param isEnabled try WFC connection
-     */
-    public void notifyTryWfcConnectionState(boolean isEnabled) {
-        if (mEventCb != null) {
-            mEventCb.onTryWfcConnectionStateChanged(isEnabled);
-        }
-    }
-
-    /**
-     * Listener need to register CallBack with implements of EventCallback.
-     *
-     * @param eventCb implements of EventCallback.
-     */
-    public void registerCallBack(@NonNull EventCallback eventCb) {
-        log("registerCallBack" + eventCb);
-        mEventCb = eventCb;
-    }
-
-    /** Event callback for call related item */
-    public interface EventCallback {
-        /**
-         * call type event notification.
-         *
-         * @param id CallId
-         * @param type CallType
-         * @param state CallState
-         */
-        void onCallInfoChanged(
-                int id,
-                @QnsConstants.QnsCallType int type,
-                @Annotation.PreciseCallStates int state);
-
-        /**
-         * RTP event notification.
-         *
-         * @param reason reason for RTP low quality
-         */
-        void onVoiceRtpLowQuality(@QnsConstants.RtpLowQualityReason int reason);
-
-        /**
-         * Notify Emergency Transport Type Preference for initial connect.
-         *
-         * @param transport Transport Type
-         */
-        void onEmergencyPreferenceChanged(@AccessNetworkConstants.TransportType int transport);
-
-        /**
-         * Try WFC connection state change notification.
-         *
-         * @param isEnabled flag value for WFC connection state change notification
-         */
-        void onTryWfcConnectionStateChanged(boolean isEnabled);
-    }
-
-    protected void log(String s) {
-        Log.d(mLogTag, s);
-    }
-}
diff --git a/services/QualifiedNetworksService/src/com/android/telephony/qns/CellularQualityMonitor.java b/services/QualifiedNetworksService/src/com/android/telephony/qns/CellularQualityMonitor.java
index c2b8d07..c9edc08 100644
--- a/services/QualifiedNetworksService/src/com/android/telephony/qns/CellularQualityMonitor.java
+++ b/services/QualifiedNetworksService/src/com/android/telephony/qns/CellularQualityMonitor.java
@@ -62,6 +62,7 @@
             SignalThresholdInfo.MAXIMUM_NUMBER_OF_THRESHOLDS_ALLOWED;
     private final String mTag;
     private TelephonyManager mTelephonyManager;
+    private QnsCarrierConfigManager mConfigManager;
     private int mSubId;
     private final int mSlotIndex;
     private boolean mIsQnsListenerRegistered;
@@ -89,7 +90,10 @@
      * @param listener QnsTelephonyListener instance
      * @param slotIndex slot index
      */
-    CellularQualityMonitor(Context context, QnsTelephonyListener listener, int slotIndex) {
+    CellularQualityMonitor(Context context,
+            QnsCarrierConfigManager configMgr,
+            QnsTelephonyListener listener,
+            int slotIndex) {
         super(QualityMonitor.class.getSimpleName() + "-C-" + slotIndex);
         mContext = context;
         mSlotIndex = slotIndex;
@@ -110,6 +114,7 @@
         } else {
             Log.e(mTag, "Failed to get Telephony Service");
         }
+        mConfigManager = configMgr;
         mSignalStrengthListener = new CellularSignalStrengthListener(mContext.getMainExecutor());
         mSignalStrengthListener.setSignalStrengthListener(this::onSignalStrengthsChanged);
     }
@@ -273,6 +278,9 @@
             if (backhaulTime > 0) {
                 builder.setHysteresisMs(backhaulTime);
             }
+            int hysteresisDb = mConfigManager.getWwanHysteresisDbLevel(networkType,
+                    measurementType);
+            builder.setHysteresisDb(hysteresisDb);
             mSignalThresholdInfoList.add(builder.build());
             Log.d(mTag, "Updated SignalThresholdInfo List: " + mSignalThresholdInfoList);
         }
diff --git a/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsCallStatusTracker.java b/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsCallStatusTracker.java
index f1b6e37..cc1f194 100644
--- a/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsCallStatusTracker.java
+++ b/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsCallStatusTracker.java
@@ -16,6 +16,8 @@
 
 package com.android.telephony.qns;
 
+import static com.android.telephony.qns.QnsConstants.INVALID_ID;
+
 import android.annotation.NonNull;
 import android.net.NetworkCapabilities;
 import android.os.Handler;
@@ -50,6 +52,7 @@
     private List<CallState> mCallStates = new ArrayList<>();
     private QnsRegistrant mCallTypeChangedEventListener;
     private QnsRegistrant mEmergencyCallTypeChangedEventListener;
+    private final QnsTimer mQnsTimer;
     private int mLastNormalCallType = QnsConstants.CALL_TYPE_IDLE;
     private int mLastEmergencyCallType = QnsConstants.CALL_TYPE_IDLE;
     private boolean mEmergencyOverIms;
@@ -144,7 +147,6 @@
             private static final int EVENT_PACKET_LOSS_TIMER_EXPIRED = 3402;
             private static final int EVENT_HYSTERESIS_FOR_NORMAL_QUALITY = 3403;
             private static final int EVENT_POLLING_CHECK_LOW_QUALITY = 3404;
-            private static final int EVENT_LOW_QUALITY_HANDLER_MAX = 3405;
 
             private static final int STATE_NORMAL_QUALITY = 0;
             private static final int STATE_SUSPECT_LOW_QUALITY = 1;
@@ -156,6 +158,9 @@
             private static final int LOW_QUALITY_REPORTED_TIME_INITIAL_VALUE = -1;
 
             private int mState = STATE_NORMAL_QUALITY;
+            private int mPacketLossTimerId = INVALID_ID;
+            private int mHysteresisTimerId = INVALID_ID;
+            private int mPollingCheckTimerId = INVALID_ID;
             private MediaQualityStatus mMediaQualityStatus;
             private String mTag;
 
@@ -204,12 +209,14 @@
                         return;
                     } else {
                         // check normal quality is stable or not.
-                        this.sendEmptyMessageDelayed(EVENT_HYSTERESIS_FOR_NORMAL_QUALITY,
+                        mHysteresisTimerId = mQnsTimer.registerTimer(
+                                Message.obtain(this, EVENT_HYSTERESIS_FOR_NORMAL_QUALITY),
                                 HYSTERESIS_TIME_NORMAL_QUALITY_MILLIS);
                     }
                 } else {
                     // Threshold breached.
-                    this.removeMessages(EVENT_HYSTERESIS_FOR_NORMAL_QUALITY);
+                    mQnsTimer.unregisterTimer(mHysteresisTimerId);
+                    mHysteresisTimerId = INVALID_ID;
                     switch (mState) {
                         case STATE_NORMAL_QUALITY:
                         case STATE_SUSPECT_LOW_QUALITY:
@@ -223,7 +230,8 @@
                                     needNotify = true;
                                 }
                             } else {
-                                removeMessages(EVENT_PACKET_LOSS_TIMER_EXPIRED);
+                                mQnsTimer.unregisterTimer(mPacketLossTimerId);
+                                mPacketLossTimerId = INVALID_ID;
                                 enterLowQualityState(status);
                                 needNotify = true;
                             }
@@ -250,28 +258,30 @@
             void enterLowQualityState(MediaQualityStatus status) {
                 Log.d(mTag, "enterLowQualityState " + status);
                 mState = STATE_LOW_QUALITY;
-                this.sendEmptyMessageDelayed(
-                        EVENT_POLLING_CHECK_LOW_QUALITY, LOW_QUALITY_CHECK_INTERVAL_MILLIS);
+                mPollingCheckTimerId = mQnsTimer.registerTimer(
+                        Message.obtain(this, EVENT_POLLING_CHECK_LOW_QUALITY),
+                        LOW_QUALITY_CHECK_INTERVAL_MILLIS);
             }
 
             void enterSuspectLowQualityState(int delayMillis) {
                 Log.d(mTag, "enterSuspectLowQualityState.");
-                if (!this.hasMessages(EVENT_PACKET_LOSS_TIMER_EXPIRED)) {
-                    this.removeMessages(EVENT_PACKET_LOSS_TIMER_EXPIRED);
-                }
+                mQnsTimer.unregisterTimer(mPacketLossTimerId);
                 Log.d(mTag, "Packet loss timer start. " + delayMillis);
                 Message msg = this.obtainMessage(
                         EVENT_PACKET_LOSS_TIMER_EXPIRED, mTransportType, 0);
-                this.sendMessageDelayed(msg, delayMillis);
+                mPacketLossTimerId = mQnsTimer.registerTimer(msg, delayMillis);
                 mState = STATE_SUSPECT_LOW_QUALITY;
             }
 
             void exitLowQualityState() {
                 mState = STATE_NORMAL_QUALITY;
-                for (int i = EVENT_PACKET_LOSS_TIMER_EXPIRED;
-                        i < EVENT_LOW_QUALITY_HANDLER_MAX; i++) {
-                    this.removeMessages(i);
-                }
+                this.removeCallbacksAndMessages(null);
+                mQnsTimer.unregisterTimer(mPacketLossTimerId);
+                mQnsTimer.unregisterTimer(mHysteresisTimerId);
+                mQnsTimer.unregisterTimer(mPollingCheckTimerId);
+                mPacketLossTimerId = INVALID_ID;
+                mHysteresisTimerId = INVALID_ID;
+                mPollingCheckTimerId = INVALID_ID;
                 notifyLowMediaQuality(0);
             }
 
@@ -283,9 +293,10 @@
                     int reason = thresholdBreached(mMediaQualityStatus);
                     if (reason > 0) {
                         notifyLowMediaQuality(thresholdBreached(mMediaQualityStatus));
-                    } else if (this.hasMessages(EVENT_HYSTERESIS_FOR_NORMAL_QUALITY)) {
+                    } else if (mHysteresisTimerId != INVALID_ID) {
                         // hysteresis time to be normal state is running. let's check after that.
-                        this.sendEmptyMessageDelayed(EVENT_POLLING_CHECK_LOW_QUALITY,
+                        mPollingCheckTimerId = mQnsTimer.registerTimer(
+                                Message.obtain(this, EVENT_POLLING_CHECK_LOW_QUALITY),
                                 HYSTERESIS_TIME_NORMAL_QUALITY_MILLIS);
                     } else {
                         Log.w(mTag, "Unexpected case.");
@@ -296,19 +307,22 @@
             void updateForHandover(int transportType) {
                 // restart timers that they need to be restarted on new transport type.
                 if (mState == STATE_SUSPECT_LOW_QUALITY) {
-                    this.removeMessages(EVENT_PACKET_LOSS_TIMER_EXPIRED);
+                    mQnsTimer.unregisterTimer(mPacketLossTimerId);
                     Message msg = this.obtainMessage(
                             EVENT_PACKET_LOSS_TIMER_EXPIRED, transportType, 0);
-                    this.sendMessageDelayed(msg, (mConfigManager.getRTPMetricsData()).mPktLossTime);
+                    mPacketLossTimerId = mQnsTimer.registerTimer(msg,
+                            (mConfigManager.getRTPMetricsData()).mPktLossTime);
                 }
-                if (this.hasMessages(EVENT_HYSTERESIS_FOR_NORMAL_QUALITY)) {
-                    this.removeMessages(EVENT_HYSTERESIS_FOR_NORMAL_QUALITY);
-                    this.sendEmptyMessageDelayed(EVENT_HYSTERESIS_FOR_NORMAL_QUALITY,
+                if (mHysteresisTimerId != INVALID_ID) {
+                    mQnsTimer.unregisterTimer(mHysteresisTimerId);
+                    mHysteresisTimerId = mQnsTimer.registerTimer(
+                            Message.obtain(this, EVENT_HYSTERESIS_FOR_NORMAL_QUALITY),
                             HYSTERESIS_TIME_NORMAL_QUALITY_MILLIS);
                 }
                 if (mState == STATE_LOW_QUALITY) {
-                    this.removeMessages(EVENT_POLLING_CHECK_LOW_QUALITY);
-                    this.sendEmptyMessageDelayed(EVENT_POLLING_CHECK_LOW_QUALITY,
+                    mQnsTimer.unregisterTimer(mPollingCheckTimerId);
+                    mPollingCheckTimerId = mQnsTimer.registerTimer(
+                            Message.obtain(this, EVENT_POLLING_CHECK_LOW_QUALITY),
                             LOW_QUALITY_CHECK_AFTER_HO_MILLIS);
                 }
             }
@@ -727,17 +741,19 @@
     }
 
     QnsCallStatusTracker(QnsTelephonyListener telephonyListener,
-            QnsCarrierConfigManager configManager, int slotIndex) {
-        this(telephonyListener, configManager, slotIndex, null);
+            QnsCarrierConfigManager configManager, QnsTimer qnsTimer, int slotIndex) {
+        this(telephonyListener, configManager, qnsTimer, slotIndex, null);
     }
 
     /** Only for test */
     @VisibleForTesting
     QnsCallStatusTracker(QnsTelephonyListener telephonyListener,
-            QnsCarrierConfigManager configManager, int slotIndex, Looper looper) {
+            QnsCarrierConfigManager configManager, QnsTimer qnsTimer, int slotIndex,
+            Looper looper) {
         mLogTag = QnsCallStatusTracker.class.getSimpleName() + "_" + slotIndex;
         mTelephonyListener = telephonyListener;
         mConfigManager = configManager;
+        mQnsTimer = qnsTimer;
         mActiveCallTracker = new ActiveCallTracker(slotIndex, looper);
         mTelephonyListener.addCallStatesChangedCallback(mCallStatesConsumer);
         mTelephonyListener.addSrvccStateChangedCallback(mSrvccStateConsumer);
@@ -801,14 +817,18 @@
                 }
             }
             //2. Notify a new ongoing call type
-            if (hasEmergencyCall() && mLastEmergencyCallType != QnsConstants.CALL_TYPE_EMERGENCY) {
-                mLastEmergencyCallType = QnsConstants.CALL_TYPE_EMERGENCY;
-                if (!isDataNetworkConnected(NetworkCapabilities.NET_CAPABILITY_EIMS)
-                        && isDataNetworkConnected(NetworkCapabilities.NET_CAPABILITY_IMS)) {
-                    notifyCallType(NetworkCapabilities.NET_CAPABILITY_IMS, mLastEmergencyCallType);
-                    mEmergencyOverIms = true;
-                } else {
-                    notifyCallType(NetworkCapabilities.NET_CAPABILITY_EIMS, mLastEmergencyCallType);
+            if (hasEmergencyCall()) {
+                if (mLastEmergencyCallType != QnsConstants.CALL_TYPE_EMERGENCY) {
+                    mLastEmergencyCallType = QnsConstants.CALL_TYPE_EMERGENCY;
+                    if (!isDataNetworkConnected(NetworkCapabilities.NET_CAPABILITY_EIMS)
+                            && isDataNetworkConnected(NetworkCapabilities.NET_CAPABILITY_IMS)) {
+                        notifyCallType(NetworkCapabilities.NET_CAPABILITY_IMS,
+                                mLastEmergencyCallType);
+                        mEmergencyOverIms = true;
+                    } else {
+                        notifyCallType(NetworkCapabilities.NET_CAPABILITY_EIMS,
+                                mLastEmergencyCallType);
+                    }
                 }
             } else if (hasVideoCall()) {
                 if (mLastNormalCallType != QnsConstants.CALL_TYPE_VIDEO) {
@@ -842,12 +862,28 @@
         } else {
             mActiveCallTracker.callStarted(callType, netCapability);
         }
+        mQnsTimer.updateCallState(callType);
     }
 
     boolean isCallIdle() {
         return mCallStates.size() == 0;
     }
 
+    boolean isCallIdle(int netCapability) {
+        int callNum = mCallStates.size();
+        if (callNum == 0) {
+            return true;
+        }
+        if (netCapability == NetworkCapabilities.NET_CAPABILITY_IMS) {
+            return (mLastNormalCallType == QnsConstants.CALL_TYPE_IDLE)
+                    && (mLastEmergencyCallType != QnsConstants.CALL_TYPE_IDLE
+                    && !mEmergencyOverIms);
+        } else if (netCapability == NetworkCapabilities.NET_CAPABILITY_EIMS) {
+            return mLastEmergencyCallType == QnsConstants.CALL_TYPE_IDLE || mEmergencyOverIms;
+        }
+        return false;
+    }
+
     boolean hasEmergencyCall() {
         for (CallState cs : mCallStates) {
             if (cs.getImsCallServiceType() == ImsCallProfile.SERVICE_TYPE_EMERGENCY
diff --git a/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsCarrierConfigManager.java b/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsCarrierConfigManager.java
index 1f869d1..ac5b561 100644
--- a/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsCarrierConfigManager.java
+++ b/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsCarrierConfigManager.java
@@ -46,6 +46,7 @@
 import android.telephony.AccessNetworkConstants;
 import android.telephony.Annotation.NetCapability;
 import android.telephony.CarrierConfigManager;
+import android.telephony.SignalThresholdInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.ims.ImsMmTelManager;
@@ -667,6 +668,20 @@
      */
     static final String KEY_SIP_DIALOG_SESSION_POLICY_INT = "qns.sip_dialog_session_policy_int";
 
+    /**
+     * List of Array items indicating hysteresis db levels based on access network and measurement
+     * type , whose value to be used at api
+     * {@link SignalThresholdInfo#Builder().setHysteresisDb(int)}
+     * The values are set as Format "<accessNetwork>:<meas_type>:<hysteresisDb>"
+     * Ex: "eutran:rsrp:2","ngran:ssrsrp:1"
+     *
+     * The default value or if value set is less than zero,
+     * for this key is {@link QnsConstants#KEY_DEFAULT_VALUE}
+     *
+     */
+    public static final String KEY_QNS_CELLULAR_SIGNAL_STRENGTH_HYSTERESIS_DB_STRING_ARRAY =
+            "qns.cellular_signal_strength_hysteresis_db_string_array";
+
     static HashMap<Integer, String> sAccessNetworkMap =
             new HashMap<>() {
                 {
@@ -755,6 +770,7 @@
     private String[] mImsAllowedRats;
     private String[] mRoveInGuardTimerConditionThresholdGaps;
     private String[] mFallbackOnInitialConnectionFailure;
+    private String[] mAccessNetworkMeasurementHysteresisDb;
 
     @NonNull
     private final List<FallbackRule> mFallbackWwanRuleWithImsUnregistered = new ArrayList<>();
@@ -1382,6 +1398,11 @@
                         KEY_QNS_ROVEIN_THRESHOLD_GAP_WITH_GUARD_TIMER_STRING_ARRAY);
         mSipDialogSessionPolicy =
                 getConfig(bundleCarrier, bundleAsset, KEY_SIP_DIALOG_SESSION_POLICY_INT);
+        mAccessNetworkMeasurementHysteresisDb =
+                getConfig(
+                        bundleCarrier,
+                        bundleAsset,
+                        KEY_QNS_CELLULAR_SIGNAL_STRENGTH_HYSTERESIS_DB_STRING_ARRAY);
 
         loadFallbackPolicyWithImsRegiFail(bundleCarrier, bundleAsset);
     }
@@ -2063,22 +2084,46 @@
     int getThresholdGapWithGuardTimer(
             @AccessNetworkConstants.RadioAccessNetworkType int accessNetwork, int measType) {
 
-        if (mRoveInGuardTimerConditionThresholdGaps == null) {
+        return getValueForMeasurementType(
+                accessNetwork, measType, mRoveInGuardTimerConditionThresholdGaps);
+
+    }
+
+    /**
+     *  This method returns hysteresis Dbm level for ran and measurement type configured.
+     *
+     * @return : Based on Carrier Config Settings & operator requirement Default Value.
+     * Note: If configured value set is less than zero or not set,
+     * {@link QnsConstants#KEY_DEFAULT_VALUE}
+     */
+    public int getWwanHysteresisDbLevel(
+            @AccessNetworkConstants.RadioAccessNetworkType int accessNetwork, int measType) {
+
+        int hysteresisDb = getValueForMeasurementType(
+                accessNetwork, measType, mAccessNetworkMeasurementHysteresisDb);
+        return hysteresisDb >= 0 ? hysteresisDb : QnsConstants.KEY_DEFAULT_VALUE;
+    }
+
+    private int getValueForMeasurementType(
+            @AccessNetworkConstants.RadioAccessNetworkType int accessNetwork, int measType,
+            String [] measurementValues) {
+
+        if (measurementValues == null) {
             return QnsConstants.KEY_DEFAULT_VALUE;
         }
-        if (!mRoveInGuardTimerConditionThresholdGaps[0].isEmpty()) {
-            for (String check_offset : mRoveInGuardTimerConditionThresholdGaps) {
-                String[] gap = check_offset.split(":");
-                String access_network = sAccessNetworkMap.get(accessNetwork);
-                String measurement_Type = sMeasTypeMap.get(measType);
 
-                try {
-                    if (gap[0].equalsIgnoreCase(access_network)
-                            && gap[1].equalsIgnoreCase(measurement_Type)) {
-                        return Integer.parseInt(gap[2]);
-                    }
-                } catch (Exception e) {
+        for (String check_offset : measurementValues) {
+            if (check_offset == null || check_offset.isEmpty()) continue;
+            String[] value = check_offset.split(":");
+            String access_network = sAccessNetworkMap.get(accessNetwork);
+            String measurement_Type = sMeasTypeMap.get(measType);
+            try {
+                if (value.length == 3 && value[0].equalsIgnoreCase(access_network)
+                        && value[1].equalsIgnoreCase(measurement_Type)) {
+                    return Integer.parseInt(value[2]);
                 }
+            } catch (Exception e) {
+                e.printStackTrace();
             }
         }
         return QnsConstants.KEY_DEFAULT_VALUE;
diff --git a/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsComponents.java b/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsComponents.java
index 1c9b898..0471ea8 100644
--- a/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsComponents.java
+++ b/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsComponents.java
@@ -22,6 +22,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -33,7 +34,6 @@
 
     private final String mLogTag = QnsComponents.class.getSimpleName();
     private final Context mContext;
-    private final SparseArray<AlternativeEventListener> mAlternativeEventListeners;
     private final SparseArray<CellularNetworkStatusTracker> mCellularNetworkStatusTrackers;
     private final SparseArray<CellularQualityMonitor> mCellularQualityMonitors;
     private final SparseArray<QnsImsManager> mQnsImsManagers;
@@ -45,12 +45,13 @@
     private final SparseArray<WifiBackhaulMonitor> mWifiBackhaulMonitors;
     private final List<Integer> mSlotIds;
     private IwlanNetworkStatusTracker mIwlanNetworkStatusTracker;
+    private QnsTimer mQnsTimer;
     private WifiQualityMonitor mWifiQualityMonitor;
+    private QnsMetrics mQnsMetrics;
 
     /** Constructor to instantiate QnsComponents class. */
     QnsComponents(Context context) {
         mContext = context;
-        mAlternativeEventListeners = new SparseArray<>();
         mCellularNetworkStatusTrackers = new SparseArray<>();
         mCellularQualityMonitors = new SparseArray<>();
         mQnsImsManagers = new SparseArray<>();
@@ -68,15 +69,9 @@
         mSlotIds.add(slotId);
         mQnsTelephonyListeners.put(slotId, new QnsTelephonyListener(mContext, slotId));
         mQnsImsManagers.put(slotId, new QnsImsManager(mContext, slotId));
-        mAlternativeEventListeners.put(
-                slotId,
-                new AlternativeEventListener(mContext, mQnsTelephonyListeners.get(slotId), slotId));
         mCellularNetworkStatusTrackers.put(
                 slotId,
                 new CellularNetworkStatusTracker(mQnsTelephonyListeners.get(slotId), slotId));
-        mCellularQualityMonitors.put(
-                slotId,
-                new CellularQualityMonitor(mContext, mQnsTelephonyListeners.get(slotId), slotId));
         mQnsProvisioningListeners.put(
                 slotId, new QnsProvisioningListener(mContext, mQnsImsManagers.get(slotId), slotId));
         mQnsEventDispatchers.put(
@@ -89,20 +84,32 @@
         mQnsCarrierConfigManagers.put(
                 slotId,
                 new QnsCarrierConfigManager(mContext, mQnsEventDispatchers.get(slotId), slotId));
+        mCellularQualityMonitors.put(
+                slotId,
+                new CellularQualityMonitor(mContext,
+                        mQnsCarrierConfigManagers.get(slotId),
+                        mQnsTelephonyListeners.get(slotId),
+                        slotId));
+        if (mQnsTimer == null) {
+            mQnsTimer = new QnsTimer(mContext);
+        }
         mQnsCallStatusTracker.put(
                 slotId,
-                new QnsCallStatusTracker(mQnsTelephonyListeners.get(slotId),
-                        mQnsCarrierConfigManagers.get(slotId), slotId));
+                new QnsCallStatusTracker(
+                        mQnsTelephonyListeners.get(slotId),
+                        mQnsCarrierConfigManagers.get(slotId),
+                        mQnsTimer,
+                        slotId));
         mWifiBackhaulMonitors.put(
                 slotId,
                 new WifiBackhaulMonitor(
                         mContext,
                         mQnsCarrierConfigManagers.get(slotId),
                         mQnsImsManagers.get(slotId),
+                        mQnsTimer,
                         slotId));
-
         if (mWifiQualityMonitor == null) {
-            mWifiQualityMonitor = new WifiQualityMonitor(mContext);
+            mWifiQualityMonitor = new WifiQualityMonitor(mContext, mQnsTimer);
         }
         if (mIwlanNetworkStatusTracker == null) {
             mIwlanNetworkStatusTracker = new IwlanNetworkStatusTracker(mContext);
@@ -113,6 +120,9 @@
                 mQnsImsManagers.get(slotId),
                 mQnsTelephonyListeners.get(slotId),
                 slotId);
+        if (mQnsMetrics == null) {
+            mQnsMetrics = new QnsMetrics(mContext);
+        }
 
         Log.d(mLogTag, "QnsComponents created for slot " + slotId);
     }
@@ -120,7 +130,6 @@
     @VisibleForTesting
     QnsComponents(
             Context context,
-            AlternativeEventListener alternativeEventListener,
             CellularNetworkStatusTracker cellularNetworkStatusTracker,
             CellularQualityMonitor cellularQualityMonitor,
             IwlanNetworkStatusTracker iwlanNetworkStatusTracker,
@@ -130,14 +139,15 @@
             QnsProvisioningListener qnsProvisioningListener,
             QnsTelephonyListener qnsTelephonyListener,
             QnsCallStatusTracker qnsCallStatusTracker,
+            QnsTimer qnsTimer,
             WifiBackhaulMonitor wifiBackhaulMonitor,
             WifiQualityMonitor wifiQualityMonitor,
+            QnsMetrics qnsMetrics,
             int slotId) {
         this(context);
         mSlotIds.add(slotId);
         mQnsTelephonyListeners.put(slotId, qnsTelephonyListener);
         mQnsImsManagers.put(slotId, qnsImsManager);
-        mAlternativeEventListeners.put(slotId, alternativeEventListener);
         mCellularNetworkStatusTrackers.put(slotId, cellularNetworkStatusTracker);
         mCellularQualityMonitors.put(slotId, cellularQualityMonitor);
         mQnsCallStatusTracker.put(slotId, qnsCallStatusTracker);
@@ -148,6 +158,7 @@
         mWifiBackhaulMonitors.put(slotId, wifiBackhaulMonitor);
 
         mWifiQualityMonitor = wifiQualityMonitor;
+        mQnsTimer = qnsTimer;
         mIwlanNetworkStatusTracker = iwlanNetworkStatusTracker;
         mIwlanNetworkStatusTracker.initBySlotIndex(
                 qnsCarrierConfigManager,
@@ -155,11 +166,7 @@
                 qnsImsManager,
                 qnsTelephonyListener,
                 slotId);
-    }
-
-    /** Returns instance of AlternativeEventListener for given slotId. */
-    AlternativeEventListener getAlternativeEventListener(int slotId) {
-        return mAlternativeEventListeners.get(slotId);
+        mQnsMetrics = qnsMetrics;
     }
 
     /** Returns instance of CellularNetworkStatusTracker for given slotId. */
@@ -217,6 +224,16 @@
         return mWifiQualityMonitor;
     }
 
+    /** Returns instance of QnsTimer. */
+    QnsTimer getQnsTimer() {
+        return mQnsTimer;
+    }
+
+    /** Returns instance of WifiQualityMonitor. */
+    QnsMetrics getQnsMetrics() {
+        return mQnsMetrics;
+    }
+
     /** Returns context. */
     Context getContext() {
         return mContext;
@@ -229,8 +246,10 @@
         if (mSlotIds.size() == 1) {
             mIwlanNetworkStatusTracker.close();
             mWifiQualityMonitor.close();
+            mQnsMetrics.close();
             mIwlanNetworkStatusTracker = null;
             mWifiQualityMonitor = null;
+            mQnsMetrics = null;
         }
 
         WifiBackhaulMonitor wifiBackhaulMonitor = mWifiBackhaulMonitors.get(slotId);
@@ -243,6 +262,15 @@
             mQnsCallStatusTracker.remove(slotId);
             qnsCallStatusTracker.close();
         }
+        if (mSlotIds.size() == 1) {
+            mQnsTimer.close();
+            mQnsTimer = null;
+        }
+        CellularQualityMonitor cellularQualityMonitor = mCellularQualityMonitors.get(slotId);
+        if (cellularQualityMonitor != null) {
+            mCellularQualityMonitors.remove(slotId);
+            cellularQualityMonitor.close();
+        }
         QnsCarrierConfigManager qnsCarrierConfigManager = mQnsCarrierConfigManagers.get(slotId);
         if (qnsCarrierConfigManager != null) {
             mQnsCarrierConfigManagers.remove(slotId);
@@ -258,21 +286,11 @@
             mQnsProvisioningListeners.remove(slotId);
             qnsProvisioningListener.close();
         }
-        CellularQualityMonitor cellularQualityMonitor = mCellularQualityMonitors.get(slotId);
-        if (cellularQualityMonitor != null) {
-            mCellularQualityMonitors.remove(slotId);
-            cellularQualityMonitor.close();
-        }
         CellularNetworkStatusTracker cellularTracker = mCellularNetworkStatusTrackers.get(slotId);
         if (cellularTracker != null) {
             mCellularNetworkStatusTrackers.remove(slotId);
             cellularTracker.close();
         }
-        AlternativeEventListener alternativeEventListener = mAlternativeEventListeners.get(slotId);
-        if (alternativeEventListener != null) {
-            mAlternativeEventListeners.remove(slotId);
-            alternativeEventListener.close();
-        }
         QnsImsManager qnsImsManager = mQnsImsManagers.get(slotId);
         if (qnsImsManager != null) {
             mQnsImsManagers.remove(slotId);
@@ -287,4 +305,16 @@
         mSlotIds.remove(Integer.valueOf(slotId));
         Log.d(mLogTag, "QnsComponents closed for slot " + slotId);
     }
+
+    void dump(PrintWriter pw) {
+        if (mIwlanNetworkStatusTracker != null) {
+            mIwlanNetworkStatusTracker.dump(pw, "  ");
+        }
+        if (mIwlanNetworkStatusTracker != null) {
+            mWifiQualityMonitor.dump(pw, "  ");
+        }
+        if (mIwlanNetworkStatusTracker != null) {
+            mQnsTimer.dump(pw, " ");
+        }
+    }
 }
diff --git a/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsMetrics.java b/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsMetrics.java
new file mode 100644
index 0000000..d936d0b
--- /dev/null
+++ b/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsMetrics.java
@@ -0,0 +1,1026 @@
+/*
+ * Copyright (C) 2022 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.telephony.qns;
+
+import android.content.Context;
+import android.net.NetworkCapabilities;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.AccessNetworkConstants.AccessNetworkType;
+import android.telephony.SignalThresholdInfo;
+import android.telephony.qns.QnsProtoEnums;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import com.android.telephony.qns.DataConnectionStatusTracker.DataConnectionChangedInfo;
+import com.android.telephony.qns.QualifiedNetworksServiceImpl.QualifiedNetworksInfo;
+import com.android.telephony.qns.atoms.AtomsQnsFallbackRestrictionChangedInfo;
+import com.android.telephony.qns.atoms.AtomsQnsHandoverPingPongInfo;
+import com.android.telephony.qns.atoms.AtomsQnsHandoverTimeMillisInfo;
+import com.android.telephony.qns.atoms.AtomsQnsImsCallDropStats;
+import com.android.telephony.qns.atoms.AtomsQnsRatPreferenceMismatchInfo;
+import com.android.telephony.qns.atoms.AtomsQualifiedRatListChangedInfo;
+import com.android.telephony.statslib.StatsLib;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+
+/** QnsStats class */
+class QnsMetrics {
+
+    private StatsLib mStats;
+    private final String mLogTag;
+    private final Handler mHandler;
+    private final HandlerThread mHandlerThread;
+
+    // For HandoverTIme.
+    private final ConcurrentHashMap<Integer, HandoverTimeStateMachine> mHandoverTimeMap;
+    private final ConcurrentHashMap<Integer, PingPongTime> mPingPongTime;
+    private final ConcurrentHashMap<Integer, RatMismatchTime> mRatMismatchTime;
+    private final ConcurrentHashMap<Integer, RtpCallDrop> mRtpCallDrop;
+
+    /** Constructor */
+    QnsMetrics(Context context) {
+        mStats = new StatsLib(context);
+        mLogTag = QnsMetrics.class.getSimpleName();
+
+        mHandoverTimeMap = new ConcurrentHashMap<>();
+        mPingPongTime = new ConcurrentHashMap<>();
+        mRatMismatchTime = new ConcurrentHashMap<>();
+        mRtpCallDrop = new ConcurrentHashMap<>();
+
+        mHandlerThread = new HandlerThread(QnsMetrics.class.getSimpleName());
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+    }
+
+    @VisibleForTesting
+    QnsMetrics(StatsLib statsLib) {
+        mStats = statsLib;
+        mLogTag = QnsMetrics.class.getSimpleName();
+
+        mHandoverTimeMap = new ConcurrentHashMap<>();
+        mPingPongTime = new ConcurrentHashMap<>();
+        mRatMismatchTime = new ConcurrentHashMap<>();
+        mRtpCallDrop = new ConcurrentHashMap<>();
+
+        mHandlerThread = new HandlerThread(QnsMetrics.class.getSimpleName());
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+    }
+
+    @VisibleForTesting
+    Handler getHandler() {
+        return mHandler;
+    }
+
+    /** close */
+    public void close() {
+        mStats = null;
+        mHandlerThread.quitSafely();
+    }
+
+    /**
+     * Report atoms when the qualified access network is reported.
+     *
+     * @param info QualifiedNetworksInfo
+     * @param slotId slot index
+     * @param dataConnectionCurrentTransportType transportType currently stayed in.
+     * @param coverage coverage home or roam.
+     * @param settingWfcEnabled setting for wfc
+     * @param settingWfcRoamingEnabled roaming setting for wfc
+     * @param settingWfcMode setting for wfc mode
+     * @param settingWfcRoamingMode roaming setting for wfc mode
+     * @param cellularAccessNetworkType cellular rat
+     * @param iwlanAvailable iwlan available
+     * @param isCrossWfc cross sim wfc enabled
+     * @param restrictManager restriction manager
+     * @param cellularQualityMonitor cellular quality monitor
+     * @param wifiQualityMonitor wifi quality monitor
+     * @param callType call type
+     */
+    public void reportAtomForQualifiedNetworks(
+            QualifiedNetworksInfo info,
+            int slotId,
+            int dataConnectionCurrentTransportType,
+            int coverage,
+            boolean settingWfcEnabled,
+            boolean settingWfcRoamingEnabled,
+            int settingWfcMode,
+            int settingWfcRoamingMode,
+            int cellularAccessNetworkType,
+            boolean iwlanAvailable,
+            boolean isCrossWfc,
+            RestrictManager restrictManager,
+            QualityMonitor cellularQualityMonitor,
+            QualityMonitor wifiQualityMonitor,
+            int callType) {
+        mHandler.post(() -> procQualifiedNetworksForHandoverTime(info, slotId));
+        mHandler.post(() ->
+                procQualifiedRatListChanged(
+                        info,
+                        slotId,
+                        dataConnectionCurrentTransportType,
+                        coverage,
+                        settingWfcEnabled,
+                        settingWfcRoamingEnabled,
+                        settingWfcMode,
+                        settingWfcRoamingMode,
+                        cellularAccessNetworkType,
+                        iwlanAvailable,
+                        isCrossWfc,
+                        restrictManager,
+                        cellularQualityMonitor,
+                        wifiQualityMonitor,
+                        callType));
+    }
+
+    /**
+     * Report atom when data connection is changed
+     *
+     * @param netCapability Network Capability
+     * @param slotId slot Index
+     * @param info DataConnectionChangedInfo
+     * @param carrierId carrier id.
+     */
+    public void reportAtomForDataConnectionChanged(
+            int netCapability, int slotId, DataConnectionChangedInfo info, int carrierId) {
+        mHandler.post(() -> procDataConnectionChangedForHandoverTime(netCapability, slotId, info));
+        mHandler.post(() -> procDataConnectionChangedForHandoverPingPong(
+                netCapability, slotId, info, carrierId));
+        mHandler.post(() -> procDataConnectionChangedForRatMismatch(
+                netCapability, slotId, info, carrierId));
+    }
+
+    /**
+     * Report atom when a restriction is set.
+     *
+     * @param netCapability Network Capability
+     * @param slotId slot Index
+     * @param wlanRestrictions list of restrictions on wlan
+     * @param wwanRestrictions list of restrictions on wwan
+     * @param carrierId carrier id.
+     */
+    public void reportAtomForRestrictions(
+            int netCapability,
+            int slotId,
+            List<Integer> wlanRestrictions,
+            List<Integer> wwanRestrictions,
+            int carrierId) {
+        mHandler.post(() -> procRestrictionsForFallback(
+                netCapability, slotId, wlanRestrictions, wwanRestrictions, carrierId));
+    }
+
+    /**
+     * Report atom when call type change
+     *
+     * @param netCapability Network Capability
+     * @param slotId slot Index
+     * @param oldCallType previous call type
+     * @param newCallType new call type
+     * @param restrictManager restriction manager
+     * @param transportTypeOfCall transport type in call
+     */
+    public void reportAtomForCallTypeChanged(
+            int netCapability,
+            int slotId,
+            int oldCallType,
+            int newCallType,
+            RestrictManager restrictManager,
+            int transportTypeOfCall) {
+        mHandler.post(() -> procCallTypeChangedForImsCallDrop(netCapability, slotId,
+                oldCallType, newCallType, restrictManager, transportTypeOfCall));
+    }
+
+    /**
+     * Report atom when ims call is dropped
+     *
+     * @param netCapability Network Capability
+     * @param slotId slot Index
+     * @param restrictManager restriction manager
+     * @param cellularQualityMonitor cellular quality monitor
+     * @param wifiQualityMonitor wifi quality monitor
+     * @param transportTypeOfCall transport type in call
+     * @param cellularAccessNetworkType cellular access network
+     */
+    public void reportAtomForImsCallDropStats(
+            int netCapability,
+            int slotId,
+            RestrictManager restrictManager,
+            QualityMonitor cellularQualityMonitor,
+            QualityMonitor wifiQualityMonitor,
+            int transportTypeOfCall,
+            int cellularAccessNetworkType) {
+        mHandler.post(() -> procCallDroppedForImsCallDrop(netCapability, slotId, restrictManager,
+                cellularQualityMonitor, wifiQualityMonitor, transportTypeOfCall,
+                cellularAccessNetworkType));
+    }
+
+    private void procQualifiedRatListChanged(
+            QualifiedNetworksInfo info,
+            int slotId,
+            int dataConnectedTransportType,
+            int coverage,
+            boolean settingWfcEnabled,
+            boolean settingWfcRoamingEnabled,
+            int settingWfcMode,
+            int settingWfcRoamingMode,
+            int cellularAccessNetworkType,
+            boolean iwlanAvailable,
+            boolean isCrossWfc,
+            RestrictManager restrictManager,
+            QualityMonitor cellularQualityMonitor,
+            QualityMonitor wifiQualityMonitor,
+            int callType) {
+        int netCapability = info.getNetCapability();
+        int firstQualifiedRat = getQualifiedAccessNetwork(info, 0);
+        int secondQualifiedRat = getQualifiedAccessNetwork(info, 1);
+        boolean wfcEnabled = getWfcEnabled(coverage, settingWfcEnabled, settingWfcRoamingEnabled);
+        int wfcMode = getWfcMode(coverage, settingWfcMode, settingWfcRoamingMode);
+        int iwlanNetworkType = getIwlanNetworkType(iwlanAvailable, isCrossWfc);
+        int restrictionsOnWwan = getRestrictionsBitmask(
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN, restrictManager);
+        int restrictionsOnWlan = getRestrictionsBitmask(
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN, restrictManager);
+        int signalStrength = getSignalStrength(cellularQualityMonitor, cellularAccessNetworkType);
+        int signalQuality = getSignalQuality(cellularQualityMonitor, cellularAccessNetworkType);
+        int signalNoise = getSignalNoise(cellularQualityMonitor, cellularAccessNetworkType);
+        int iwlanSignalStrength = getSignalStrength(wifiQualityMonitor, AccessNetworkType.IWLAN);
+        int updateReason = 0;
+        int imsCallQuality = 0;
+
+        writeQualifiedRatListChangedInfo(
+                netCapability,
+                slotId,
+                firstQualifiedRat,
+                secondQualifiedRat,
+                dataConnectedTransportType,
+                wfcEnabled,
+                wfcMode,
+                cellularAccessNetworkType,
+                iwlanNetworkType,
+                restrictionsOnWwan,
+                restrictionsOnWlan,
+                signalStrength,
+                signalQuality,
+                signalNoise,
+                iwlanSignalStrength,
+                updateReason,
+                callType,
+                imsCallQuality);
+    }
+
+
+    private void procQualifiedNetworksForHandoverTime(QualifiedNetworksInfo info, int slotId) {
+        if (info.getNetCapability() != NetworkCapabilities.NET_CAPABILITY_IMS) {
+            return;
+        }
+
+        HandoverTimeStateMachine handoverTimeStateMachine = mHandoverTimeMap.get(slotId);
+        if (handoverTimeStateMachine == null) {
+            handoverTimeStateMachine =
+                    new HandoverTimeStateMachine(info.getNetCapability(), slotId, mHandler);
+            mHandoverTimeMap.put(slotId, handoverTimeStateMachine);
+        }
+
+        handoverTimeStateMachine.sendQualifiedRatChanged(info);
+    }
+
+    private void procDataConnectionChangedForHandoverTime(
+            int netCapability, int slotId, DataConnectionChangedInfo info) {
+        if (netCapability != NetworkCapabilities.NET_CAPABILITY_IMS) {
+            return;
+        }
+
+        HandoverTimeStateMachine handoverTimeStateMachine = mHandoverTimeMap.get(slotId);
+        if (handoverTimeStateMachine == null) {
+            handoverTimeStateMachine =
+                    new HandoverTimeStateMachine(netCapability, slotId, mHandler);
+            mHandoverTimeMap.put(slotId, handoverTimeStateMachine);
+        }
+
+        handoverTimeStateMachine.sendDataStateChanged(info);
+    }
+
+    private void procDataConnectionChangedForHandoverPingPong(int netCapability, int slotId,
+            DataConnectionChangedInfo info, int carrierId) {
+        if (netCapability != NetworkCapabilities.NET_CAPABILITY_IMS) {
+            return;
+        }
+
+        PingPongTime pingPongTime = mPingPongTime.get(slotId);
+        if (pingPongTime == null) {
+            pingPongTime = new PingPongTime();
+            mPingPongTime.put(slotId, pingPongTime);
+        }
+
+        boolean bActiveState;
+        switch (info.getState()) {
+            case DataConnectionStatusTracker.STATE_INACTIVE:
+            case DataConnectionStatusTracker.STATE_CONNECTING:
+                bActiveState = false;
+                break;
+            case DataConnectionStatusTracker.STATE_CONNECTED:
+            case DataConnectionStatusTracker.STATE_HANDOVER:
+            default:
+                bActiveState = true;
+                break;
+        }
+        switch (info.getEvent()) {
+            case DataConnectionStatusTracker.EVENT_DATA_CONNECTION_CONNECTED:
+                pingPongTime.mSuccessTime = QnsUtils.getSystemElapsedRealTime();
+                pingPongTime.mHandoverCount = 0;
+                break;
+            case DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_STARTED:
+                pingPongTime.mStartTime = QnsUtils.getSystemElapsedRealTime();
+                break;
+            case DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_SUCCESS:
+                pingPongTime.mHandoverCount++;
+                pingPongTime.mSuccessTime = QnsUtils.getSystemElapsedRealTime();
+                break;
+        }
+
+        if (pingPongTime.mStartTime != 0L && pingPongTime.mSuccessTime != 0L) {
+            long pingPongTimeLimit = AtomsQnsHandoverPingPongInfo.PING_PONG_TIME_IN_MILLIS;
+            long elapsed =
+                    (pingPongTime.mSuccessTime > pingPongTime.mStartTime)
+                            ? (pingPongTime.mSuccessTime - pingPongTime.mStartTime)
+                            : (pingPongTime.mStartTime - pingPongTime.mSuccessTime);
+            if (pingPongTime.mHandoverCount > 0) {
+                log("HandoverPingPong elapsed:" + elapsed
+                        + " ping-pong count:" + (pingPongTime.mHandoverCount / 2));
+            }
+            if (elapsed > pingPongTimeLimit || !bActiveState) {
+                if (pingPongTime.mHandoverCount > 1) {
+                    int pingpongCount = pingPongTime.mHandoverCount / 2;
+                    writeQnsHandoverPingPong(slotId, pingpongCount, carrierId);
+                }
+                pingPongTime.mHandoverCount = 0;
+                pingPongTime.mStartTime = 0L;
+                pingPongTime.mSuccessTime = 0L;
+                procDataConnectionChangedForHandoverPingPong(
+                        netCapability, slotId, info, carrierId);
+            }
+        }
+        if (!bActiveState) {
+            pingPongTime.mHandoverCount = 0;
+            pingPongTime.mStartTime = 0L;
+            pingPongTime.mSuccessTime = 0L;
+        }
+    }
+
+    private void procRestrictionsForFallback(int netCapability, int slotId,
+            List<Integer> wlanRestrictions, List<Integer> wwanRestrictions, int carrierId) {
+        if (netCapability != NetworkCapabilities.NET_CAPABILITY_IMS
+                || wlanRestrictions == null
+                || wwanRestrictions == null) {
+            return;
+        }
+
+        boolean bRestrictionOnWlanByRtpThresholdBreached =
+                wlanRestrictions.contains(RestrictManager.RESTRICT_TYPE_RTP_LOW_QUALITY);
+        boolean bRestrictionOnWwanByRtpThresholdBreached =
+                wwanRestrictions.contains(RestrictManager.RESTRICT_TYPE_RTP_LOW_QUALITY);
+        boolean bRestrictionOnWlanByImsRegistrationFailed =
+                wlanRestrictions.contains(
+                        RestrictManager.RESTRICT_TYPE_FALLBACK_TO_WWAN_IMS_REGI_FAIL);
+        boolean bRestrictionOnWlanByWifiBackhaulProblem =
+                wlanRestrictions.contains(
+                        RestrictManager.RESTRICT_TYPE_FALLBACK_TO_WWAN_RTT_BACKHAUL_FAIL);
+
+        // all false will not write atom because this atom is used for count metric.
+        if (bRestrictionOnWlanByRtpThresholdBreached
+                || bRestrictionOnWwanByRtpThresholdBreached
+                || bRestrictionOnWlanByImsRegistrationFailed
+                || bRestrictionOnWlanByWifiBackhaulProblem) {
+            writeQnsFallbackRestrictionChangedInfo(
+                    bRestrictionOnWlanByRtpThresholdBreached,
+                    bRestrictionOnWwanByRtpThresholdBreached,
+                    bRestrictionOnWlanByImsRegistrationFailed,
+                    bRestrictionOnWlanByWifiBackhaulProblem,
+                    carrierId,
+                    slotId);
+        }
+    }
+
+    private void procDataConnectionChangedForRatMismatch(
+            int netCapability, int slotId, DataConnectionChangedInfo info, int carrierId) {
+
+        RatMismatchTime mismatchTime = mRatMismatchTime.get(slotId);
+        if (mismatchTime == null) {
+            mismatchTime = new RatMismatchTime();
+            mRatMismatchTime.put(slotId, mismatchTime);
+        }
+
+        switch (info.getEvent()) {
+            case DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_STARTED:
+                if (mismatchTime.mCount == 0) {
+                    mismatchTime.mStartTime = QnsUtils.getSystemElapsedRealTime();
+                }
+                break;
+            case DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_FAILED:
+                mismatchTime.mCount++;
+                break;
+            default:
+                if (mismatchTime.mCount > 0) {
+                    long duration = QnsUtils.getSystemElapsedRealTime() - mismatchTime.mStartTime;
+                    int count = mismatchTime.mCount;
+
+                    writeQnsRatPreferenceMismatchInfo(
+                            netCapability, count, (int) duration, carrierId, slotId);
+                    mismatchTime.mCount = 0;
+                    mismatchTime.mStartTime = 0L;
+                }
+                break;
+        }
+    }
+
+    private void writeQualifiedRatListChangedInfo(
+            int netCapability,
+            int slotId,
+            int firstQualifiedRat,
+            int secondQualifiedRat,
+            int currentTransportType,
+            boolean wfcEnabled,
+            int wfcMode,
+            int cellularNetworkType,
+            int iwlanNetworkType,
+            int restrictionsOnWwan,
+            int restrictionsOnWlan,
+            int signalStrength,
+            int signalQuality,
+            int signalNoise,
+            int iwlanSignalStrength,
+            int updateReason,
+            int imsCallType,
+            int imsCallQuality) {
+        AtomsQualifiedRatListChangedInfo atoms =
+                new AtomsQualifiedRatListChangedInfo(
+                        netCapability,
+                        firstQualifiedRat,
+                        secondQualifiedRat,
+                        currentTransportType,
+                        wfcEnabled,
+                        wfcMode,
+                        cellularNetworkType,
+                        iwlanNetworkType,
+                        restrictionsOnWwan,
+                        restrictionsOnWlan,
+                        signalStrength,
+                        signalQuality,
+                        signalNoise,
+                        iwlanSignalStrength,
+                        updateReason,
+                        imsCallType,
+                        imsCallQuality,
+                        slotId);
+        mStats.write(atoms);
+    }
+
+
+    private void writeQnsHandoverTimeMillisInfo(long handoverTime, int slotIndex) {
+        AtomsQnsHandoverTimeMillisInfo atoms =
+                new AtomsQnsHandoverTimeMillisInfo((int) handoverTime, slotIndex);
+        mStats.append(atoms);
+    }
+
+    private void writeQnsHandoverPingPong(int slotId, int pingPongCount, int carrierId) {
+        AtomsQnsHandoverPingPongInfo atoms =
+                new AtomsQnsHandoverPingPongInfo(pingPongCount, carrierId, slotId);
+        mStats.append(atoms);
+    }
+
+    private void writeQnsFallbackRestrictionChangedInfo(
+            boolean bRestrictionOnWlanByRtpThresholdBreached,
+            boolean bRestrictionOnWwanByRtpThresholdBreached,
+            boolean bRestrictionOnWlanByImsRegistrationFailed,
+            boolean bRestrictionOnWlanByWifiBackhaulProblem,
+            int carrierId,
+            int slotId) {
+        AtomsQnsFallbackRestrictionChangedInfo atom =
+                new AtomsQnsFallbackRestrictionChangedInfo(
+                        bRestrictionOnWlanByRtpThresholdBreached,
+                        bRestrictionOnWwanByRtpThresholdBreached,
+                        bRestrictionOnWlanByImsRegistrationFailed,
+                        bRestrictionOnWlanByWifiBackhaulProblem,
+                        carrierId,
+                        slotId);
+        mStats.write(atom);
+    }
+
+    private void writeQnsRatPreferenceMismatchInfo(int netCapability, int handoverFailCount,
+            int durationMismatch, int carrierId, int slotId) {
+        AtomsQnsRatPreferenceMismatchInfo atoms = new AtomsQnsRatPreferenceMismatchInfo(
+                netCapability, handoverFailCount, durationMismatch, carrierId, slotId);
+        mStats.append(atoms);
+    }
+
+    class HandoverTimeStateMachine extends StateMachine {
+
+        private static final int EVENT_DATA_STATE_CHANGED = 0;
+        private static final int EVENT_QUALIFIED_RAT_CHANGED = 1;
+
+        private final IdleState mIdleState;
+        private final ConnectedState mConnectedState;
+        private final HandoverRequestedState mHandoverRequestedState;
+        private final HandoverInProgressState mHandoverInProgressState;
+
+        private final int mNetCapability;
+        private final int mSlotId;
+        int mDataTransportType;
+        long mHandoverRequestedTime;
+
+        HandoverTimeStateMachine(int netCapability, int slotId, Handler handler) {
+            super(mLogTag + "_" + HandoverTimeStateMachine.class.getSimpleName() + "_" + slotId
+                    + "_" + QnsUtils.getNameOfNetCapability(netCapability), handler);
+
+            mIdleState = new IdleState();
+            mConnectedState = new ConnectedState();
+            mHandoverRequestedState = new HandoverRequestedState();
+            mHandoverInProgressState = new HandoverInProgressState();
+
+            mNetCapability = netCapability;
+            mSlotId = slotId;
+            mDataTransportType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
+            mHandoverRequestedTime = 0L;
+
+            addState(mIdleState);
+            addState(mConnectedState);
+            addState(mHandoverRequestedState);
+            addState(mHandoverInProgressState);
+            setInitialState(mIdleState);
+            start();
+        }
+
+        public void sendDataStateChanged(DataConnectionChangedInfo info) {
+            sendMessage(EVENT_DATA_STATE_CHANGED, info);
+        }
+
+        public void sendQualifiedRatChanged(QualifiedNetworksInfo info) {
+            sendMessage(EVENT_QUALIFIED_RAT_CHANGED, info);
+        }
+
+        private final class IdleState extends State {
+            @Override
+            public void enter() {
+                log("IdleState");
+                mDataTransportType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
+                mHandoverRequestedTime = 0L;
+            }
+
+            @Override
+            public boolean processMessage(Message msg) {
+                log("IdleState processMessage=" + msg.what);
+                switch (msg.what) {
+                    case EVENT_DATA_STATE_CHANGED:
+                        DataConnectionChangedInfo dataInfo = (DataConnectionChangedInfo) msg.obj;
+                        switch (dataInfo.getState()) {
+                            case DataConnectionStatusTracker.STATE_CONNECTED:
+                                mDataTransportType = dataInfo.getTransportType();
+                                transitionTo(mConnectedState);
+                                return HANDLED;
+                            case DataConnectionStatusTracker.STATE_HANDOVER:
+                                mDataTransportType = dataInfo.getTransportType();
+                                transitionTo(mConnectedState);
+                                break;
+                        }
+                        break;
+                    case EVENT_QUALIFIED_RAT_CHANGED:
+                        break;
+                }
+                return super.processMessage(msg);
+            }
+
+            @Override
+            public void exit() {
+                super.exit();
+            }
+        }
+
+        private final class ConnectedState extends State {
+            @Override
+            public void enter() {
+                log("ConnectedState");
+                mHandoverRequestedTime = 0L;
+            }
+
+            @Override
+            public boolean processMessage(Message msg) {
+                log("ConnectedState processMessage=" + msg.what);
+                switch (msg.what) {
+                    case EVENT_DATA_STATE_CHANGED:
+                        DataConnectionChangedInfo dataInfo = (DataConnectionChangedInfo) msg.obj;
+                        switch (dataInfo.getState()) {
+                            case DataConnectionStatusTracker.STATE_INACTIVE:
+                            case DataConnectionStatusTracker.STATE_CONNECTING:
+                                transitionTo(mIdleState);
+                                break;
+                            case DataConnectionStatusTracker.STATE_CONNECTED:
+                            case DataConnectionStatusTracker.STATE_HANDOVER:
+                                mDataTransportType = dataInfo.getTransportType();
+                                return HANDLED;
+                        }
+                        break;
+                    case EVENT_QUALIFIED_RAT_CHANGED:
+                        QualifiedNetworksInfo qualifiedInfo = (QualifiedNetworksInfo) msg.obj;
+                        // handover trigger
+                        if (mNetCapability == qualifiedInfo.getNetCapability()
+                                && qualifiedInfo.getAccessNetworkTypes().stream().anyMatch(
+                                        accessNetwork -> QnsUtils.getTransportTypeFromAccessNetwork(
+                                                accessNetwork) != mDataTransportType)) {
+                            mHandoverRequestedTime = QnsUtils.getSystemElapsedRealTime();
+                            transitionTo(mHandoverRequestedState);
+                            return HANDLED;
+                        }
+                        break;
+                }
+
+                return super.processMessage(msg);
+            }
+
+            @Override
+            public void exit() {
+                super.exit();
+            }
+        }
+
+        private final class HandoverRequestedState extends State {
+            @Override
+            public void enter() {
+                log("HandoverRequestedState");
+            }
+
+            @Override
+            public boolean processMessage(Message msg) {
+                log("HandoverRequestedState processMessage=" + msg.what);
+                switch (msg.what) {
+                    case EVENT_DATA_STATE_CHANGED:
+                        DataConnectionChangedInfo dataInfo = (DataConnectionChangedInfo) msg.obj;
+                        switch (dataInfo.getState()) {
+                            case DataConnectionStatusTracker.STATE_INACTIVE:
+                            case DataConnectionStatusTracker.STATE_CONNECTING:
+                                transitionTo(mIdleState);
+                                break;
+                            case DataConnectionStatusTracker.STATE_CONNECTED:
+                                if (dataInfo.getTransportType() != mDataTransportType) {
+                                    // back to connected state, already reached to target transport.
+                                    mDataTransportType = dataInfo.getTransportType();
+                                    transitionTo(mConnectedState);
+                                }
+                                break;
+                            case DataConnectionStatusTracker.STATE_HANDOVER:
+                                if (dataInfo.getTransportType() != mDataTransportType) {
+                                    // back to connected state, already reached to target transport.
+                                    mDataTransportType = dataInfo.getTransportType();
+                                    transitionTo(mConnectedState);
+                                }
+                                transitionTo(mHandoverInProgressState);
+                                break;
+                        }
+                        break;
+                    case EVENT_QUALIFIED_RAT_CHANGED:
+                        QualifiedNetworksInfo qualifiedInfo = (QualifiedNetworksInfo) msg.obj;
+                        if (mNetCapability != qualifiedInfo.getNetCapability()) {
+                            break;
+                        }
+                        if (qualifiedInfo.getAccessNetworkTypes().stream().noneMatch(
+                                accessNetwork -> QnsUtils.getTransportTypeFromAccessNetwork(
+                                        accessNetwork) != mDataTransportType)) {
+                            // back to connected state. no handover target transport.
+                            transitionTo(mConnectedState);
+                            return HANDLED;
+                        }
+                        break;
+                }
+                return super.processMessage(msg);
+            }
+
+            @Override
+            public void exit() {
+                super.exit();
+            }
+        }
+
+        private final class HandoverInProgressState extends State {
+            @Override
+            public void enter() {
+                log("HandoverInProgressState");
+            }
+
+            @Override
+            public boolean processMessage(Message msg) {
+                log("HandoverInProgressState processMessage=" + msg.what);
+                switch (msg.what) {
+                    case EVENT_DATA_STATE_CHANGED:
+                        DataConnectionChangedInfo dataInfo = (DataConnectionChangedInfo) msg.obj;
+                        switch (dataInfo.getState()) {
+                            case DataConnectionStatusTracker.STATE_INACTIVE:
+                            case DataConnectionStatusTracker.STATE_CONNECTING:
+                                transitionTo(mIdleState);
+                                break;
+                            case DataConnectionStatusTracker.STATE_CONNECTED:
+                                if (dataInfo.getTransportType() != mDataTransportType) {
+                                    if (mHandoverRequestedTime == 0L) {
+                                        break;
+                                    }
+                                    // handover done.
+                                    long handoverTime = QnsUtils.getSystemElapsedRealTime()
+                                            - mHandoverRequestedTime;
+                                    writeQnsHandoverTimeMillisInfo(handoverTime, mSlotId);
+                                    mDataTransportType = dataInfo.getTransportType();
+                                    mHandoverRequestedTime = 0L;
+                                    transitionTo(mConnectedState);
+                                } else {
+                                    // handover didn't have done yet.
+                                    transitionTo(mHandoverRequestedState);
+                                }
+                                break;
+                            case DataConnectionStatusTracker.STATE_HANDOVER:
+                                if (dataInfo.getTransportType() != mDataTransportType) {
+                                    // back to connected state, already reached to target transport.
+                                    mDataTransportType = dataInfo.getTransportType();
+                                    transitionTo(mConnectedState);
+                                }
+                                break;
+                        }
+                        break;
+                    case EVENT_QUALIFIED_RAT_CHANGED:
+                        QualifiedNetworksInfo qualifiedInfo = (QualifiedNetworksInfo) msg.obj;
+                        if (mNetCapability == qualifiedInfo.getNetCapability()
+                                && qualifiedInfo.getAccessNetworkTypes().stream().noneMatch(
+                                        accessNetwork -> QnsUtils.getTransportTypeFromAccessNetwork(
+                                                accessNetwork) != mDataTransportType)) {
+                            // back to connected state. no handover request
+                            transitionTo(mConnectedState);
+                            return HANDLED;
+                        }
+                        break;
+                }
+                return super.processMessage(msg);
+            }
+
+            @Override
+            public void exit() {
+                super.exit();
+            }
+        }
+    }
+
+    private static class PingPongTime {
+        int mHandoverCount = 0;
+        long mStartTime = 0L;
+        long mSuccessTime = 0L;
+    }
+
+    private static class RatMismatchTime {
+        int mCount = 0;
+        long mStartTime = 0L;
+    }
+
+    private static class RtpCallDrop {
+        boolean mRtpThresholdBreached;
+        int mRestrictionsOnOtherTransportType;
+    }
+
+
+    private void procCallTypeChangedForImsCallDrop(
+            int netCapability,
+            int slotId,
+            int oldCallType,
+            int newCallType,
+            RestrictManager restrictManager,
+            int transportTypeOfCall) {
+        if (netCapability != NetworkCapabilities.NET_CAPABILITY_IMS) {
+            return;
+        }
+
+        RtpCallDrop rtpCallDrop = mRtpCallDrop.get(slotId);
+        if (rtpCallDrop == null) {
+            rtpCallDrop = new RtpCallDrop();
+            mRtpCallDrop.put(slotId, rtpCallDrop);
+        }
+
+        // call ended
+        if (newCallType == QnsConstants.CALL_TYPE_IDLE
+                && oldCallType != QnsConstants.CALL_TYPE_IDLE) {
+            rtpCallDrop.mRtpThresholdBreached =
+                    restrictManager.hasRestrictionType(
+                            transportTypeOfCall, RestrictManager.RESTRICT_TYPE_RTP_LOW_QUALITY);
+            int otherTransportType =
+                    transportTypeOfCall == AccessNetworkConstants.TRANSPORT_TYPE_WLAN
+                            ? AccessNetworkConstants.TRANSPORT_TYPE_WWAN
+                            : AccessNetworkConstants.TRANSPORT_TYPE_WLAN;
+            rtpCallDrop.mRestrictionsOnOtherTransportType =
+                    getRestrictionsBitmask(otherTransportType, restrictManager);
+        } else {
+            rtpCallDrop.mRtpThresholdBreached = false;
+            rtpCallDrop.mRestrictionsOnOtherTransportType = 0;
+        }
+    }
+
+    private void procCallDroppedForImsCallDrop(
+            int netCapability,
+            int slotId,
+            RestrictManager restrictManager,
+            QualityMonitor cellularQualityMonitor,
+            QualityMonitor wifiQualityMonitor,
+            int transportTypeOfCall,
+            int cellularAccessNetworkType) {
+        if (netCapability != NetworkCapabilities.NET_CAPABILITY_IMS) {
+            return;
+        }
+
+        RtpCallDrop rtpCallDrop = mRtpCallDrop.get(slotId);
+        if (rtpCallDrop == null) {
+            rtpCallDrop = new RtpCallDrop();
+            mRtpCallDrop.put(slotId, rtpCallDrop);
+        }
+
+        if (!rtpCallDrop.mRtpThresholdBreached
+                || rtpCallDrop.mRestrictionsOnOtherTransportType == 0) {
+            rtpCallDrop.mRtpThresholdBreached = restrictManager.hasRestrictionType(
+                            transportTypeOfCall, RestrictManager.RESTRICT_TYPE_RTP_LOW_QUALITY);
+            int otherTransportType =
+                    transportTypeOfCall == AccessNetworkConstants.TRANSPORT_TYPE_WLAN
+                            ? AccessNetworkConstants.TRANSPORT_TYPE_WWAN
+                            : AccessNetworkConstants.TRANSPORT_TYPE_WLAN;
+            rtpCallDrop.mRestrictionsOnOtherTransportType =
+                    getRestrictionsBitmask(otherTransportType, restrictManager);
+        }
+
+        int signalStrength = getSignalStrength(cellularQualityMonitor, cellularAccessNetworkType);
+        int signalQuality = getSignalQuality(cellularQualityMonitor, cellularAccessNetworkType);
+        int signalNoise = getSignalNoise(cellularQualityMonitor, cellularAccessNetworkType);
+        int iwlanSignalStrength = getSignalStrength(wifiQualityMonitor, AccessNetworkType.IWLAN);
+
+        writeQnsImsCallDropStats(
+                transportTypeOfCall,
+                rtpCallDrop.mRtpThresholdBreached,
+                rtpCallDrop.mRestrictionsOnOtherTransportType,
+                signalStrength,
+                signalQuality,
+                signalNoise,
+                iwlanSignalStrength,
+                cellularAccessNetworkType,
+                slotId);
+    }
+
+    private void writeQnsImsCallDropStats(
+            int transportTypeCallDropped,
+            boolean rtpThresholdBreached,
+            int restrictionsOnOtherTransportType,
+            int signalStrength,
+            int signalQuality,
+            int signalNoise,
+            int iwlanSignalStrength,
+            int cellularNetworkType,
+            int slotId) {
+        AtomsQnsImsCallDropStats atoms =
+                new AtomsQnsImsCallDropStats(
+                        transportTypeCallDropped,
+                        rtpThresholdBreached,
+                        restrictionsOnOtherTransportType,
+                        signalStrength,
+                        signalQuality,
+                        signalNoise,
+                        iwlanSignalStrength,
+                        slotId,
+                        cellularNetworkType);
+        mStats.write(atoms);
+    }
+
+    private int getQualifiedAccessNetwork(QualifiedNetworksInfo info, int index) {
+        List<Integer> types = info.getAccessNetworkTypes();
+        if (types == null || index >= types.size()) {
+            return QnsProtoEnums.EMPTY;
+        }
+        return types.get(index);
+    }
+
+    private boolean getWfcEnabled(int coverage, boolean wfcEnabled, boolean wfcRoamingEnabled) {
+        return coverage == QnsConstants.COVERAGE_HOME ? wfcEnabled : wfcRoamingEnabled;
+    }
+
+    private int getWfcMode(int coverage, int wfcMode, int wfcRoamingMode) {
+        return coverage == QnsConstants.COVERAGE_HOME ? wfcMode : wfcRoamingMode;
+    }
+
+    private int getIwlanNetworkType(boolean iwlanAvailable, boolean isCrossWfc) {
+        if (!iwlanAvailable) {
+            return QnsProtoEnums.IWLAN_NETWORK_TYPE_NONE;
+        } else if (isCrossWfc) {
+            return QnsProtoEnums.IWLAN_NETWORK_TYPE_CST;
+        }
+        return QnsProtoEnums.IWLAN_NETWORK_TYPE_WIFI;
+    }
+
+    static final HashMap<Integer, Integer> sAtomRestrictionsMap;
+
+    static {
+        sAtomRestrictionsMap = new HashMap<>();
+        sAtomRestrictionsMap.put(
+                RestrictManager.RESTRICT_TYPE_GUARDING, QnsProtoEnums.RESTRICT_TYPE_GUARDING);
+        sAtomRestrictionsMap.put(
+                RestrictManager.RESTRICT_TYPE_THROTTLING, QnsProtoEnums.RESTRICT_TYPE_THROTTLING);
+        sAtomRestrictionsMap.put(
+                RestrictManager.RESTRICT_TYPE_HO_NOT_ALLOWED,
+                QnsProtoEnums.RESTRICT_TYPE_HO_NOT_ALLOWED);
+        sAtomRestrictionsMap.put(
+                RestrictManager.RESTRICT_TYPE_NON_PREFERRED_TRANSPORT,
+                QnsProtoEnums.RESTRICT_TYPE_NON_PREFERRED_TRANSPORT);
+        sAtomRestrictionsMap.put(
+                RestrictManager.RESTRICT_TYPE_RTP_LOW_QUALITY,
+                QnsProtoEnums.RESTRICT_TYPE_RTP_LOW_QUALITY);
+        sAtomRestrictionsMap.put(
+                RestrictManager.RESTRICT_TYPE_RESTRICT_IWLAN_IN_CALL,
+                QnsProtoEnums.RESTRICT_TYPE_RESTRICT_IWLAN_IN_CALL);
+        sAtomRestrictionsMap.put(
+                RestrictManager.RESTRICT_TYPE_RESTRICT_IWLAN_CS_CALL,
+                QnsProtoEnums.RESTRICT_TYPE_RESTRICT_IWLAN_CS_CALL);
+        sAtomRestrictionsMap.put(
+                RestrictManager.RESTRICT_TYPE_FALLBACK_TO_WWAN_IMS_REGI_FAIL,
+                QnsProtoEnums.RESTRICT_TYPE_FALLBACK_TO_WWAN_IMS_REGI_FAIL);
+        sAtomRestrictionsMap.put(
+                RestrictManager.RESTRICT_TYPE_FALLBACK_ON_DATA_CONNECTION_FAIL,
+                QnsProtoEnums.RESTRICT_TYPE_FALLBACK_ON_DATA_CONNECTION_FAIL);
+        sAtomRestrictionsMap.put(
+                RestrictManager.RESTRICT_TYPE_FALLBACK_TO_WWAN_RTT_BACKHAUL_FAIL,
+                QnsProtoEnums.RESTRICT_TYPE_FALLBACK_TO_WWAN_RTT_BACKHAUL_FAIL);
+    }
+
+    private int getRestrictionsBitmask(int transportType, RestrictManager restrictManager) {
+        int restrictions = QnsProtoEnums.RESTRICT_TYPE_NONE;
+        for (int restrictionType : sAtomRestrictionsMap.keySet()) {
+            if (restrictManager.hasRestrictionType(transportType, restrictionType)) {
+                restrictions |= sAtomRestrictionsMap.get(restrictionType);
+            }
+        }
+        return restrictions;
+    }
+
+    private int getSignalStrength(QualityMonitor qm, int accessNetworkType) {
+        switch (accessNetworkType) {
+            case AccessNetworkType.GERAN:
+            case AccessNetworkType.IWLAN:
+                return qm.getCurrentQuality(
+                        accessNetworkType, SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI);
+            case AccessNetworkType.UTRAN:
+                return qm.getCurrentQuality(
+                        accessNetworkType, SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP);
+            case AccessNetworkType.EUTRAN:
+                return qm.getCurrentQuality(
+                        accessNetworkType, SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP);
+            case AccessNetworkType.NGRAN:
+                return qm.getCurrentQuality(
+                        accessNetworkType, SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP);
+        }
+        return 0;
+    }
+
+    private int getSignalQuality(QualityMonitor qm, int accessNetworkType) {
+        switch (accessNetworkType) {
+            case AccessNetworkType.EUTRAN:
+                return qm.getCurrentQuality(
+                        accessNetworkType, SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ);
+            case AccessNetworkType.NGRAN:
+                return qm.getCurrentQuality(
+                        accessNetworkType, SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRQ);
+        }
+        return 0;
+    }
+
+    private int getSignalNoise(QualityMonitor qm, int accessNetworkType) {
+        switch (accessNetworkType) {
+            case AccessNetworkType.EUTRAN:
+                return qm.getCurrentQuality(
+                        accessNetworkType, SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSNR);
+            case AccessNetworkType.NGRAN:
+                return qm.getCurrentQuality(
+                        accessNetworkType, SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR);
+        }
+        return 0;
+    }
+
+    protected void log(String s) {
+        Log.d(mLogTag, s);
+    }
+}
diff --git a/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsTelephonyListener.java b/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsTelephonyListener.java
index 32883d4..9e59736 100644
--- a/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsTelephonyListener.java
+++ b/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsTelephonyListener.java
@@ -37,6 +37,7 @@
 import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyManager;
 import android.telephony.VopsSupportInfo;
+import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.MediaQualityStatus;
 import android.util.Log;
 
@@ -69,6 +70,7 @@
     QnsRegistrantList mSrvccStateListener = new QnsRegistrantList();
     QnsRegistrantList mSubscriptionIdListener = new QnsRegistrantList();
     QnsRegistrantList mIwlanServiceStateListener = new QnsRegistrantList();
+    QnsRegistrantList mImsCallDropDisconnectCauseListener = new QnsRegistrantList();
     List<Consumer<List<CallState>>> mCallStatesConsumerList = new ArrayList<>();
     List<Consumer<Integer>> mSrvccStateConsumerList = new ArrayList<>();
     List<Consumer<MediaQualityStatus>> mMediaQualityConsumerList = new ArrayList<>();
@@ -327,6 +329,21 @@
     }
 
     /**
+     * Register an event for ImsCallDropDisconnectCause changed.
+     *
+     * @param h the Handler to get event.
+     * @param what the event.
+     * @param userObj user object.
+     */
+    void registerImsCallDropDisconnectCauseListener(Handler h, int what, Object userObj) {
+        log("registerImsCallDropDisconnectCauseListener");
+        if (h != null) {
+            QnsRegistrant r = new QnsRegistrant(h, what, userObj);
+            mImsCallDropDisconnectCauseListener.add(r);
+        }
+    }
+
+    /**
      * Unregister an event for QnsTelephonyInfo changed.
      *
      * @param netCapability Network Capability to be notified.
@@ -406,6 +423,18 @@
         }
     }
 
+    /**
+     * Unregister an event for ImsCallDropDisconnectCause state changed.
+     *
+     * @param h the handler to get event.
+     */
+    void unregisterImsCallDropDisconnectCauseListener(Handler h) {
+        log("unregisterImsCallDropDisconnectCauseListener");
+        if (h != null) {
+            mImsCallDropDisconnectCauseListener.remove(h);
+        }
+    }
+
     private void createTelephonyListener() {
         if (mTelephonyListener == null) {
             mTelephonyListener = new TelephonyListener(mContext.getMainExecutor());
@@ -429,6 +458,10 @@
                     (int srvccState) -> {
                         onSrvccStateChanged(srvccState);
                     });
+            mTelephonyListener.setImsCallDisconnectCauseListener(
+                    (ImsReasonInfo imsReasonInfo) -> {
+                        onImsCallDisconnectCauseChanged(imsReasonInfo);
+                    });
         }
     }
 
@@ -721,6 +754,10 @@
         mSubscriptionIdListener.notifyResult(subId);
     }
 
+    protected void onImsCallDisconnectCauseChanged(ImsReasonInfo imsReasonInfo) {
+        mImsCallDropDisconnectCauseListener.notifyResult(imsReasonInfo);
+    }
+
     protected void log(String s) {
         Log.d(mLogTag, s);
     }
@@ -769,6 +806,11 @@
         void onCallStatesChanged(List<CallState> callStateList);
     }
 
+    protected interface OnImsCallDisconnectCauseListener {
+        /** Notify the call disconnected cause changed. */
+        void onImsCallDisconnectCauseChanged(@NonNull ImsReasonInfo imsReasonInfo);
+    }
+
     protected static class Archiving<V> {
         protected HashMap<String, V> mArchiving = new HashMap<>();
 
@@ -1037,7 +1079,8 @@
                     TelephonyCallback.CallStateListener,
                     TelephonyCallback.SrvccStateListener,
                     TelephonyCallback.CallAttributesListener,
-                    TelephonyCallback.MediaQualityStatusChangedListener {
+                    TelephonyCallback.MediaQualityStatusChangedListener,
+                    TelephonyCallback.ImsCallDisconnectCauseListener {
         private final Executor mExecutor;
         private OnServiceStateListener mServiceStateListener;
         private OnPreciseDataConnectionStateListener mPreciseDataConnectionStateListener;
@@ -1046,6 +1089,7 @@
         private OnSrvccStateChangedCallback mSrvccStateCallback;
         private OnSrvccStateChangedCallback mSrvccStateListener;
         private OnCallStatesChangedCallback mCallStatesCallback;
+        private OnImsCallDisconnectCauseListener mImsCallDisconnectCauseListener;
         private TelephonyManager mTelephonyManager;
 
         TelephonyListener(Executor executor) {
@@ -1080,6 +1124,11 @@
         void setCallStatesCallback(OnCallStatesChangedCallback listener) {
             mCallStatesCallback = listener;
         }
+
+        void setImsCallDisconnectCauseListener(OnImsCallDisconnectCauseListener listener) {
+            mImsCallDisconnectCauseListener = listener;
+        }
+
         /**
          * Register a TelephonyCallback for this listener.
          *
@@ -1168,6 +1217,13 @@
                 consumer.accept(status);
             }
         }
+
+        @Override
+        public void onImsCallDisconnectCauseChanged(@NonNull ImsReasonInfo imsReasonInfo) {
+            if (mImsCallDisconnectCauseListener != null) {
+                mImsCallDisconnectCauseListener.onImsCallDisconnectCauseChanged(imsReasonInfo);
+            }
+        }
     }
 
     void addCallStatesChangedCallback(Consumer<List<CallState>> consumer) {
diff --git a/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsTimer.java b/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsTimer.java
new file mode 100644
index 0000000..7630054
--- /dev/null
+++ b/services/QualifiedNetworksService/src/com/android/telephony/qns/QnsTimer.java
@@ -0,0 +1,434 @@
+/*
+ * Copyright (C) 2023 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.telephony.qns;
+
+import static com.android.telephony.qns.QnsConstants.CALL_TYPE_IDLE;
+import static com.android.telephony.qns.QnsConstants.INVALID_ID;
+import static com.android.telephony.qns.QnsUtils.getSystemElapsedRealTime;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.os.PowerManager;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.PrintWriter;
+import java.util.Comparator;
+import java.util.Objects;
+import java.util.PriorityQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/** This class handles the delayed events triggered in QNS. */
+class QnsTimer {
+
+    private static final String TAG = QnsTimer.class.getSimpleName();
+    private static final int EVENT_QNS_TIMER_EXPIRED = 1;
+
+    private static final int MIN_ALARM_CALL_ACTIVE_DELAY_MS = 0;
+    private static final int MIN_ALARM_SCREEN_OFF_DELAY_MS = 10000;
+    private static final int MIN_ALARM_DEVICE_LIGHT_IDLE_DELAY_MS = 30000;
+    private static final int MIN_ALARM_DEVICE_IDLE_DELAY_MS = 60000;
+    static final String ACTION_ALARM_TIMER_EXPIRED =
+            "com.android.telephony.qns.action.ALARM_TIMER_EXPIRED";
+
+    private static final AtomicInteger sTimerId = new AtomicInteger();
+    private final Context mContext;
+    private final AlarmManager mAlarmManager;
+    private final PowerManager mPowerManager;
+    private final HandlerThread mHandlerThread;
+    private final BroadcastReceiver mBroadcastReceiver;
+    private final PriorityQueue<TimerInfo> mTimerInfos;
+    private PendingIntent mPendingIntent;
+    private long mMinAlarmTimeMs = MIN_ALARM_SCREEN_OFF_DELAY_MS;
+    private int mCurrentAlarmTimerId = INVALID_ID;
+    private int mCurrentHandlerTimerId = INVALID_ID;
+    private boolean mIsAlarmRequired;
+    @VisibleForTesting Handler mHandler;
+    private long mLastAlarmTriggerAtMs = Long.MAX_VALUE;
+    private int mCallType = CALL_TYPE_IDLE;
+
+    QnsTimer(Context context) {
+        mContext = context;
+        mAlarmManager = mContext.getSystemService(AlarmManager.class);
+        mPowerManager = mContext.getSystemService(PowerManager.class);
+        mBroadcastReceiver = new AlarmReceiver();
+        mTimerInfos =
+                new PriorityQueue<>(Comparator.comparingLong(TimerInfo::getExpireAtElapsedMillis));
+        mHandlerThread = new HandlerThread(TAG);
+        mHandlerThread.start();
+        mHandler = new QnsTimerHandler();
+
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(ACTION_ALARM_TIMER_EXPIRED);
+        intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
+        intentFilter.addAction(Intent.ACTION_SCREEN_ON);
+        intentFilter.addAction(PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED);
+        intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+        mContext.registerReceiver(mBroadcastReceiver, intentFilter, Context.RECEIVER_EXPORTED);
+    }
+
+    /**
+     * This method uses AlarmManager to execute the delayed event passed as param.
+     *
+     * @param msg message to process.
+     * @param delayMs timer value for the delay.
+     * @return unique timer id associated with the registered timer.
+     */
+    int registerTimer(Message msg, long delayMs) {
+        int timerId = sTimerId.getAndIncrement();
+        TimerInfo timerInfo = new TimerInfo(timerId);
+        timerInfo.setMessage(msg);
+        timerInfo.setExpireAtElapsedMillis(getSystemElapsedRealTime() + delayMs);
+        logd("register timer for timerId=" + timerId + ", with delay=" + delayMs);
+        mHandler.post(
+                () -> {
+                    mTimerInfos.add(timerInfo);
+                    updateToShortestDelay(mIsAlarmRequired, false /* forceUpdate */);
+                });
+        return timerId;
+    }
+
+    /**
+     * This method unregisters the timer associated to given timerId.
+     *
+     * @param timerId timer id associated with the running timer.
+     */
+    void unregisterTimer(int timerId) {
+        if (timerId == INVALID_ID) {
+            return;
+        }
+        logd("unregisterTimer for timerId=" + timerId);
+        mHandler.post(
+                () -> {
+                    logd("Cancel timerId=" + timerId);
+                    TimerInfo timerInfo = new TimerInfo(timerId);
+                    if (mTimerInfos.remove(timerInfo) && timerId == mCurrentAlarmTimerId) {
+                        updateToShortestDelay(mIsAlarmRequired, false /* forceUpdate */);
+                    }
+                });
+    }
+
+    /**
+     * It updates the call state in QnsTimer. If the call is active the minimum timer value for an
+     * alarm is updated to 0ms. Otherwise the value will be based on device state (Idle, Light Idle
+     * or Screen off).
+     *
+     * @param type Call type {@code @QnsConstants.QnsCallType}
+     */
+    void updateCallState(@QnsConstants.QnsCallType int type) {
+        if (mCallType == CALL_TYPE_IDLE && type != CALL_TYPE_IDLE) {
+            mHandler.post(
+                    () -> {
+                        mMinAlarmTimeMs = MIN_ALARM_CALL_ACTIVE_DELAY_MS;
+                        if (mIsAlarmRequired) {
+                            updateToShortestDelay(true, true /* forceUpdate */);
+                        }
+                    });
+        }
+        mCallType = type;
+        if (mCallType == CALL_TYPE_IDLE && mIsAlarmRequired) {
+            if (mPowerManager.isDeviceIdleMode()) {
+                mMinAlarmTimeMs = MIN_ALARM_DEVICE_IDLE_DELAY_MS;
+            } else if (mPowerManager.isDeviceLightIdleMode()) {
+                mMinAlarmTimeMs = MIN_ALARM_DEVICE_LIGHT_IDLE_DELAY_MS;
+            } else {
+                mMinAlarmTimeMs = MIN_ALARM_SCREEN_OFF_DELAY_MS; // SCREEN_OFF case
+            }
+        }
+    }
+
+    /**
+     * This method performs the following actions: 1. checks if the shortest timer is set for
+     * handler or alarm. If not it overrides the earlier set timer with the shortest one. 2. checks
+     * for timers in the list those have passed the current elapsed time; and notifies them to
+     * respective handlers.
+     *
+     * @param isAlarmRequired flag indicates if timer is need to setup with Alarm.
+     * @param forceUpdate flag indicates to update the delay time for handler and/or alarm
+     *     forcefully.
+     */
+    private void updateToShortestDelay(boolean isAlarmRequired, boolean forceUpdate) {
+        TimerInfo timerInfo = mTimerInfos.peek();
+        long elapsedTime = getSystemElapsedRealTime();
+        while (timerInfo != null && timerInfo.getExpireAtElapsedMillis() <= elapsedTime) {
+            logd("Notify timerInfo=" + timerInfo);
+            timerInfo.getMessage().sendToTarget();
+            mTimerInfos.poll();
+            timerInfo = mTimerInfos.peek();
+        }
+        if (timerInfo == null) {
+            logd("No timers are pending to run");
+            clearAllTimers();
+            return;
+        }
+        long delay = timerInfo.getExpireAtElapsedMillis() - elapsedTime;
+        // Delayed Handler will always set for shortest delay.
+        if (timerInfo.getTimerId() != mCurrentHandlerTimerId || forceUpdate) {
+            mHandler.removeMessages(EVENT_QNS_TIMER_EXPIRED);
+            mHandler.sendEmptyMessageDelayed(EVENT_QNS_TIMER_EXPIRED, delay);
+            mCurrentHandlerTimerId = timerInfo.getTimerId();
+        }
+
+        // Alarm will always set for shortest from Math.max(delay, mMinAlarmTimeMs)
+        if (timerInfo.getTimerId() != mCurrentAlarmTimerId || forceUpdate) {
+            if (isAlarmRequired) {
+                delay = Math.max(delay, mMinAlarmTimeMs);
+                // check if smaller timer alarm is already running for active timer info.
+                if (mTimerInfos.contains(new TimerInfo(mCurrentAlarmTimerId))
+                        && mLastAlarmTriggerAtMs - elapsedTime < delay
+                        && mPendingIntent != null) {
+                    logd(
+                            "Skip update since minimum Alarm Timer already running for timerId="
+                                    + mCurrentAlarmTimerId);
+                    return;
+                }
+                logd("Setup alarm for delay " + delay);
+                mLastAlarmTriggerAtMs = elapsedTime + delay;
+                setupAlarmFor(mLastAlarmTriggerAtMs);
+            } else if (mPendingIntent != null) {
+                mAlarmManager.cancel(mPendingIntent);
+                mPendingIntent = null;
+            }
+            mCurrentAlarmTimerId = timerInfo.getTimerId();
+            logd("Update timer to timer id=" + mCurrentAlarmTimerId);
+        }
+    }
+
+    private void clearAllTimers() {
+        mHandler.removeMessages(EVENT_QNS_TIMER_EXPIRED);
+        if (mPendingIntent != null) {
+            logd("Cancel Alarm");
+            mAlarmManager.cancel(mPendingIntent);
+        }
+        mPendingIntent = null;
+    }
+
+    private void setupAlarmFor(long triggerAtMillis) {
+        mPendingIntent =
+                PendingIntent.getBroadcast(
+                        mContext,
+                        0,
+                        new Intent(ACTION_ALARM_TIMER_EXPIRED),
+                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+        mAlarmManager.setExactAndAllowWhileIdle(
+                AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtMillis, mPendingIntent);
+    }
+
+    private class AlarmReceiver extends BroadcastReceiver {
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            logd("onReceive action=" + action);
+            switch (action) {
+                case ACTION_ALARM_TIMER_EXPIRED:
+                    mHandler.sendEmptyMessage(EVENT_QNS_TIMER_EXPIRED);
+                    break;
+                case Intent.ACTION_SCREEN_OFF:
+                    mHandler.post(
+                            () -> {
+                                mMinAlarmTimeMs =
+                                        (mCallType == CALL_TYPE_IDLE)
+                                                ? MIN_ALARM_SCREEN_OFF_DELAY_MS
+                                                : MIN_ALARM_CALL_ACTIVE_DELAY_MS;
+                                if (!mIsAlarmRequired) {
+                                    mIsAlarmRequired = true;
+                                    updateToShortestDelay(true, true /* forceUpdate */);
+                                }
+                            });
+                    break;
+                case Intent.ACTION_SCREEN_ON:
+                    mHandler.post(
+                            () -> {
+                                if (mIsAlarmRequired) {
+                                    mIsAlarmRequired = false;
+                                    updateToShortestDelay(false, true /* forceUpdate */);
+                                }
+                            });
+                    break;
+                case PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED:
+                    mHandler.post(
+                            () -> {
+                                if (mPowerManager.isDeviceLightIdleMode()) {
+                                    mMinAlarmTimeMs =
+                                            (mCallType == CALL_TYPE_IDLE)
+                                                    ? MIN_ALARM_DEVICE_LIGHT_IDLE_DELAY_MS
+                                                    : MIN_ALARM_CALL_ACTIVE_DELAY_MS;
+                                    if (!mIsAlarmRequired) {
+                                        mIsAlarmRequired = true;
+                                        updateToShortestDelay(true, true /* forceUpdate */);
+                                    }
+                                } else {
+                                    mMinAlarmTimeMs =
+                                            (mCallType == CALL_TYPE_IDLE)
+                                                    ? MIN_ALARM_SCREEN_OFF_DELAY_MS
+                                                    : MIN_ALARM_CALL_ACTIVE_DELAY_MS;
+                                }
+                            });
+                    break;
+                case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED:
+                    mHandler.post(
+                            () -> {
+                                if (mPowerManager.isDeviceIdleMode()) {
+                                    mMinAlarmTimeMs =
+                                            (mCallType == CALL_TYPE_IDLE)
+                                                    ? MIN_ALARM_DEVICE_IDLE_DELAY_MS
+                                                    : MIN_ALARM_CALL_ACTIVE_DELAY_MS;
+                                    if (!mIsAlarmRequired) {
+                                        mIsAlarmRequired = true;
+                                        updateToShortestDelay(true, true /* forceUpdate */);
+                                    }
+                                } else {
+                                    mMinAlarmTimeMs =
+                                            (mCallType == CALL_TYPE_IDLE)
+                                                    ? MIN_ALARM_SCREEN_OFF_DELAY_MS
+                                                    : MIN_ALARM_CALL_ACTIVE_DELAY_MS;
+                                }
+                            });
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
+    private class QnsTimerHandler extends Handler {
+        QnsTimerHandler() {
+            super(mHandlerThread.getLooper());
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            super.handleMessage(msg);
+            logd("handleMessage msg.what=" + msg.what);
+            switch (msg.what) {
+                case EVENT_QNS_TIMER_EXPIRED:
+                    logd("Timer expired");
+                    updateToShortestDelay(mIsAlarmRequired, false /* forceUpdate */);
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
+    static class TimerInfo {
+        private final int mTimerId;
+        private long mExpireAtElapsedMillis;
+        private Message mMsg;
+
+        TimerInfo(int timerId) {
+            mTimerId = timerId;
+        }
+
+        public int getTimerId() {
+            return mTimerId;
+        }
+
+        public Message getMessage() {
+            return mMsg;
+        }
+
+        public void setMessage(Message msg) {
+            mMsg = msg;
+        }
+
+        public long getExpireAtElapsedMillis() {
+            return mExpireAtElapsedMillis;
+        }
+
+        public void setExpireAtElapsedMillis(long expireAtElapsedMillis) {
+            mExpireAtElapsedMillis = expireAtElapsedMillis;
+        }
+
+        /** Timers are equals if they share the same timer id. */
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof TimerInfo)) return false;
+            TimerInfo timerInfo = (TimerInfo) o;
+            return mTimerId == timerInfo.mTimerId;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mTimerId);
+        }
+
+        @Override
+        public String toString() {
+            return "TimerInfo{"
+                    + "mTimerId="
+                    + mTimerId
+                    + ", mExpireAtElapsedMillis="
+                    + mExpireAtElapsedMillis
+                    + ", mMsg="
+                    + mMsg
+                    + '}';
+        }
+    }
+
+    @VisibleForTesting
+    PriorityQueue<TimerInfo> getTimersInfo() {
+        return mTimerInfos;
+    }
+
+    void close() {
+        logd("Closing QnsTimer");
+        mHandlerThread.quitSafely();
+        mContext.unregisterReceiver(mBroadcastReceiver);
+        mTimerInfos.clear();
+        clearAllTimers();
+    }
+
+    private void logd(String s) {
+        Log.d(TAG, s);
+    }
+
+    /**
+     * Dumps the state of {@link QnsTimer}
+     *
+     * @param pw {@link PrintWriter} to write the state of the object.
+     * @param prefix String to append at start of dumped log.
+     */
+    void dump(PrintWriter pw, String prefix) {
+        pw.println(prefix + "------------------------------");
+        pw.println(prefix + "QnsTimer:");
+        pw.println(
+                prefix
+                        + "mIsAlarmRequired="
+                        + mIsAlarmRequired
+                        + ", mCurrentAlarmTimerId="
+                        + mCurrentAlarmTimerId
+                        + ", mCurrentHandlerTimerId="
+                        + mCurrentHandlerTimerId
+                        + ", latest timerId="
+                        + sTimerId.get()
+                        + ", Current elapsed time="
+                        + getSystemElapsedRealTime());
+        pw.println(prefix + "mTimerInfos=" + mTimerInfos);
+        pw.println(prefix + "mPendingIntent=" + mPendingIntent);
+    }
+}
diff --git a/services/QualifiedNetworksService/src/com/android/telephony/qns/QualifiedNetworksServiceImpl.java b/services/QualifiedNetworksService/src/com/android/telephony/qns/QualifiedNetworksServiceImpl.java
index 9ac37b1..679098e 100644
--- a/services/QualifiedNetworksService/src/com/android/telephony/qns/QualifiedNetworksServiceImpl.java
+++ b/services/QualifiedNetworksService/src/com/android/telephony/qns/QualifiedNetworksServiceImpl.java
@@ -358,14 +358,7 @@
             NetworkAvailabilityProviderImpl provider = providerMap.getValue();
             provider.dump(pw, "  ");
         }
-        IwlanNetworkStatusTracker iwlanNst = mQnsComponents.getIwlanNetworkStatusTracker();
-        if (iwlanNst != null) {
-            iwlanNst.dump(pw, "  ");
-        }
-        WifiQualityMonitor wQM = mQnsComponents.getWifiQualityMonitor();
-        if (wQM != null) {
-            wQM.dump(pw, "  ");
-        }
+        mQnsComponents.dump(pw);
         pw.println("==============================");
     }
 }
diff --git a/services/QualifiedNetworksService/src/com/android/telephony/qns/RestrictManager.java b/services/QualifiedNetworksService/src/com/android/telephony/qns/RestrictManager.java
index 9ae8a1d..21eaf7c 100644
--- a/services/QualifiedNetworksService/src/com/android/telephony/qns/RestrictManager.java
+++ b/services/QualifiedNetworksService/src/com/android/telephony/qns/RestrictManager.java
@@ -18,6 +18,7 @@
 
 import static com.android.telephony.qns.DataConnectionStatusTracker.STATE_CONNECTED;
 import static com.android.telephony.qns.DataConnectionStatusTracker.STATE_HANDOVER;
+import static com.android.telephony.qns.QnsConstants.INVALID_ID;
 
 import android.annotation.IntDef;
 import android.net.NetworkCapabilities;
@@ -161,7 +162,9 @@
     private QnsCallStatusTracker mQnsCallStatusTracker;
     private QnsCallStatusTracker.ActiveCallTracker mActiveCallTracker;
     private QnsImsManager mQnsImsManager;
+    private QnsTimer mQnsTimer;
     private WifiBackhaulMonitor mWifiBackhaulMonitor;
+    private QnsMetrics mQnsMetrics;
     private int mNetCapability;
     private int mSlotId;
     private int mTransportType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
@@ -173,6 +176,7 @@
     private int mFallbackCounterOnDataConnectionFail;
     private boolean mIsRttStatusCheckRegistered = false;
     private int mLastDataConnectionTransportType;
+    private int mFallbackTimerId = -1;
     private boolean mIsTimerRunningOnDataConnectionFail = false;
     private Pair<Integer, Long> mDeferredThrottlingEvent = null;
 
@@ -182,6 +186,7 @@
     @Annotation.CallState private int mCallState;
 
     private Map<Integer, RestrictInfo> mRestrictInfos = new ConcurrentHashMap<>();
+    private Map<Restriction, Integer> mRestrictionTimers = new ConcurrentHashMap<>();
 
     private class RestrictManagerHandler extends Handler {
         RestrictManagerHandler(Looper l) {
@@ -238,6 +243,9 @@
                                     .getRestrictionMap()
                                     .get(restriction.mRestrictType)) {
                         releaseRestriction(transportType, restriction.mRestrictType);
+                        mQnsTimer.unregisterTimer(mRestrictionTimers
+                                .getOrDefault(restriction, INVALID_ID));
+                        mRestrictionTimers.remove(restriction);
                     }
                     break;
 
@@ -247,6 +255,7 @@
                             "Initial Data Connection fail timer expired"
                                     + mIsTimerRunningOnDataConnectionFail);
 
+                    mQnsTimer.unregisterTimer(mFallbackTimerId);
                     if (mIsTimerRunningOnDataConnectionFail) {
                         int currTransportType = message.arg1;
                         fallbackToOtherTransportOnDataConnectionFail(currTransportType);
@@ -451,11 +460,13 @@
         mTelephonyListener = qnsComponents.getQnsTelephonyListener(mSlotId);
         mQnsEventDispatcher = qnsComponents.getQnsEventDispatcher(mSlotId);
         mQnsCarrierConfigManager = qnsComponents.getQnsCarrierConfigManager(mSlotId);
+        mQnsTimer = qnsComponents.getQnsTimer();
         mHandler = new RestrictManagerHandler(loop);
         mNetCapability = netCapability;
         mDataConnectionStatusTracker = dcst;
         mQnsCallStatusTracker = qnsComponents.getQnsCallStatusTracker(mSlotId);
         mActiveCallTracker = qnsComponents.getQnsCallStatusTracker(mSlotId).getActiveCallTracker();
+        mQnsMetrics = qnsComponents.getQnsMetrics();
         mDataConnectionStatusTracker.registerDataConnectionStatusChanged(
                 mHandler, EVENT_DATA_CONNECTION_CHANGED);
         if (mNetCapability == NetworkCapabilities.NET_CAPABILITY_IMS) {
@@ -518,6 +529,7 @@
         if (mNetCapability == NetworkCapabilities.NET_CAPABILITY_IMS) {
             mQnsImsManager.unregisterImsRegistrationStatusChanged(mHandler);
         }
+        mRestrictionTimers.clear();
     }
 
     private void onWfcModeChanged(int prefMode, @QnsConstants.CellularCoverage int coverage) {
@@ -776,9 +788,7 @@
         mIsTimerRunningOnDataConnectionFail = false;
         mRetryCounterOnDataConnectionFail = 0;
 
-        if (mHandler.hasMessages(EVENT_INITIAL_DATA_CONNECTION_FAIL_RETRY_TIMER_EXPIRED)) {
-            mHandler.removeMessages(EVENT_INITIAL_DATA_CONNECTION_FAIL_RETRY_TIMER_EXPIRED);
-        }
+        mQnsTimer.unregisterTimer(mFallbackTimerId);
     }
 
     private void processDataConnectionDisconnected() {
@@ -916,7 +926,7 @@
                             transportType,
                             0,
                             null);
-            mHandler.sendMessageDelayed(msg, (long) fallbackRetryTimer);
+            mFallbackTimerId = mQnsTimer.registerTimer(msg, fallbackRetryTimer);
             mIsTimerRunningOnDataConnectionFail = true;
         }
     }
@@ -1291,6 +1301,7 @@
                 removeReleaseRestrictionMessage(restriction);
             }
             restrictionMap.remove(restriction.mRestrictType);
+            mRestrictionTimers.remove(restriction);
             needNotify = true;
         }
         if (needNotify && !skipNotify) {
@@ -1327,7 +1338,8 @@
         Message msg =
                 mHandler.obtainMessage(EVENT_RELEASE_RESTRICTION, transportType, 0, restriction);
         long delayInMillis = restriction.mReleaseTime - SystemClock.elapsedRealtime();
-        mHandler.sendMessageDelayed(msg, delayInMillis);
+        int timerId = mQnsTimer.registerTimer(msg, delayInMillis);
+        mRestrictionTimers.put(restriction, timerId);
         Log.d(
                 mLogTag,
                 restrictTypeToString(restriction.mRestrictType)
@@ -1341,7 +1353,8 @@
             Log.e(mLogTag, "removeReleaseRestrictionMessage restriction is null");
             return;
         }
-        mHandler.removeMessages(EVENT_RELEASE_RESTRICTION, restriction);
+        mQnsTimer.unregisterTimer(mRestrictionTimers.getOrDefault(restriction, INVALID_ID));
+        mRestrictionTimers.remove(restriction);
     }
 
     void registerRestrictInfoChanged(Handler h, int what) {
@@ -1440,6 +1453,9 @@
         Log.d(mLogTag, "notifyRestrictInfoChanged");
         if (mRestrictInfoRegistrant != null) {
             mRestrictInfoRegistrant.notifyResult(mRestrictInfos);
+
+            // metrics
+            sendRestrictionsForMetrics();
         } else {
             Log.d(mLogTag, "notifyRestrictInfoChanged. no Registrant.");
         }
@@ -1684,4 +1700,24 @@
                         + QnsConstants.callStateToString(mCallState));
         pw.println(prefix + "mRestrictInfos=" + mRestrictInfos);
     }
+
+    private void sendRestrictionsForMetrics() {
+        if (mNetCapability != NetworkCapabilities.NET_CAPABILITY_IMS) {
+            return;
+        }
+        ArrayList<Integer> wlanRestrictions =
+                new ArrayList<>(
+                        mRestrictInfos
+                                .get(AccessNetworkConstants.TRANSPORT_TYPE_WLAN)
+                                .getRestrictionMap()
+                                .keySet());
+        ArrayList<Integer> wwanRestrictions =
+                new ArrayList<>(
+                        mRestrictInfos
+                                .get(AccessNetworkConstants.TRANSPORT_TYPE_WLAN)
+                                .getRestrictionMap()
+                                .keySet());
+        mQnsMetrics.reportAtomForRestrictions(mNetCapability, mSlotId,
+                wlanRestrictions, wwanRestrictions, mQnsCarrierConfigManager.getCarrierId());
+    }
 }
diff --git a/services/QualifiedNetworksService/src/com/android/telephony/qns/WifiBackhaulMonitor.java b/services/QualifiedNetworksService/src/com/android/telephony/qns/WifiBackhaulMonitor.java
index be3c22c..6bef150 100644
--- a/services/QualifiedNetworksService/src/com/android/telephony/qns/WifiBackhaulMonitor.java
+++ b/services/QualifiedNetworksService/src/com/android/telephony/qns/WifiBackhaulMonitor.java
@@ -16,6 +16,8 @@
 
 package com.android.telephony.qns;
 
+import static com.android.telephony.qns.QnsConstants.INVALID_ID;
+
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.LinkProperties;
@@ -28,6 +30,8 @@
 import android.telephony.AccessNetworkConstants;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStreamReader;
@@ -56,6 +60,7 @@
     private final HandlerThread mHandlerThread;
     private final Handler mHandler;
     private final QnsCarrierConfigManager mConfigManager;
+    private final QnsTimer mQnsTimer;
     private boolean mRttResult = false;
 
     ArrayList<InetAddress> mValidIpList = new ArrayList<>();
@@ -65,6 +70,7 @@
     private boolean mIsIwlanConnected = false;
     private boolean mIsRttRunning = false;
     private String mInterfaceName = null;
+    private int mRttTimerId = INVALID_ID;
 
     private class BackhaulHandler extends Handler {
         BackhaulHandler() {
@@ -118,6 +124,7 @@
             Context context,
             QnsCarrierConfigManager configManager,
             QnsImsManager imsManager,
+            QnsTimer qnstimer,
             int slotIndex) {
         mSlotIndex = slotIndex;
         mTag = WifiBackhaulMonitor.class.getSimpleName() + "[" + mSlotIndex + "]";
@@ -125,6 +132,7 @@
         mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
         mConfigManager = configManager;
         mQnsImsManager = imsManager;
+        mQnsTimer = qnstimer;
         mNetworkCallback = new WiFiStatusCallback();
         mRegistrantList = new QnsRegistrantList();
         mHandlerThread = new HandlerThread(mTag);
@@ -174,8 +182,9 @@
     /** Triggers the request to check RTT. */
     void requestRttCheck() {
         if (!mIsRttRunning) {
-            if (mHandler.hasMessages(EVENT_START_RTT_CHECK)) {
-                mHandler.removeMessages(EVENT_START_RTT_CHECK);
+            if (mRttTimerId != INVALID_ID) {
+                mQnsTimer.unregisterTimer(mRttTimerId);
+                mRttTimerId = INVALID_ID;
             }
             mHandler.sendEmptyMessage(EVENT_START_RTT_CHECK);
         } else {
@@ -232,13 +241,17 @@
     }
 
     private void startRttSchedule(int delay) {
-        mHandler.sendEmptyMessageDelayed(EVENT_START_RTT_CHECK, delay);
+        log("start RTT schedule for " + delay);
+        mRttTimerId = mQnsTimer.registerTimer(Message.obtain(mHandler, EVENT_START_RTT_CHECK),
+                delay);
         mIsRttScheduled = true;
     }
 
     private void stopRttSchedule() {
         if (mIsRttScheduled) {
-            mHandler.removeMessages(EVENT_START_RTT_CHECK);
+            log("stop RTT schedule");
+            mQnsTimer.unregisterTimer(mRttTimerId);
+            mRttTimerId = INVALID_ID;
             mIsRttScheduled = false;
         }
     }
@@ -350,6 +363,11 @@
         mIsRttScheduled = false;
     }
 
+    @VisibleForTesting
+    int getRttTimerId() {
+        return mRttTimerId;
+    }
+
     private void log(String s) {
         Log.d(mTag, s);
     }
diff --git a/services/QualifiedNetworksService/src/com/android/telephony/qns/WifiQualityMonitor.java b/services/QualifiedNetworksService/src/com/android/telephony/qns/WifiQualityMonitor.java
index bf758a9..0c5ea2a 100644
--- a/services/QualifiedNetworksService/src/com/android/telephony/qns/WifiQualityMonitor.java
+++ b/services/QualifiedNetworksService/src/com/android/telephony/qns/WifiQualityMonitor.java
@@ -53,6 +53,8 @@
     private final ConnectivityManager mConnectivityManager;
     private final WiFiThresholdCallback mWiFiThresholdCallback;
     private final NetworkRequest.Builder mBuilder;
+    private final QnsTimer mQnsTimer;
+    private final List<Integer> mTimerIds;
 
     private int mWifiRssi;
     @VisibleForTesting Handler mHandler;
@@ -60,6 +62,7 @@
     private static final int BACKHAUL_TIMER_DEFAULT = 3000;
     static final int INVALID_RSSI = -127;
     private boolean mIsRegistered = false;
+    private boolean mIsBackhaulRunning;
 
     private class WiFiThresholdCallback extends ConnectivityManager.NetworkCallback {
         /** Callback Received based on meeting Wifi RSSI Threshold Registered or Wifi Lost */
@@ -94,11 +97,7 @@
     synchronized void validateWqmStatus(int wifiRssi) {
         if (isWifiRssiValid(wifiRssi)) {
             Log.d(mTag, "Registered Threshold @ Wqm Status check =" + mRegisteredThreshold);
-            if (!mHandler.hasMessages(EVENT_WIFI_NOTIFY_TIMER_EXPIRED)) {
-                mHandler.obtainMessage(EVENT_WIFI_RSSI_CHANGED, wifiRssi, 0).sendToTarget();
-            } else {
-                Log.d(mTag, "BackhaulCheck in Progress , skip validation");
-            }
+            mHandler.obtainMessage(EVENT_WIFI_RSSI_CHANGED, wifiRssi, 0).sendToTarget();
         } else {
             Log.d(mTag, "Cancel backhaul if running for invalid SS received");
             clearBackHaulTimer();
@@ -117,21 +116,24 @@
     }
 
     private void clearBackHaulTimer() {
-        if (mHandler.hasMessages(EVENT_WIFI_NOTIFY_TIMER_EXPIRED)) {
-            Log.d(mTag, "Stop all active backhaul timers");
-            mHandler.removeMessages(EVENT_WIFI_NOTIFY_TIMER_EXPIRED);
-            mWaitingThresholds.clear();
+        Log.d(mTag, "Stop all active backhaul timers");
+        for (int timerId : mTimerIds) {
+            mQnsTimer.unregisterTimer(timerId);
         }
+        mTimerIds.clear();
+        mWaitingThresholds.clear();
     }
 
     /**
      * Create WifiQualityMonitor object for accessing WifiManager, ConnectivityManager to monitor
      * RSSI, build parameters for registering threshold & callback listening.
      */
-    WifiQualityMonitor(Context context) {
+    WifiQualityMonitor(Context context, QnsTimer qnsTimer) {
         super(QualityMonitor.class.getSimpleName() + "-I");
         mTag = WifiQualityMonitor.class.getSimpleName() + "-I";
         mContext = context;
+        mQnsTimer = qnsTimer;
+        mTimerIds = new ArrayList<>();
         HandlerThread handlerThread = new HandlerThread(mTag);
         handlerThread.start();
         mHandler = new WiFiEventsHandler(handlerThread.getLooper());
@@ -239,9 +241,7 @@
     }
 
     private void validateForWifiBackhaul(int wifiRssi) {
-        if (mHandler.hasMessages(EVENT_WIFI_NOTIFY_TIMER_EXPIRED)) {
-            mHandler.removeMessages(EVENT_WIFI_NOTIFY_TIMER_EXPIRED);
-        }
+        mIsBackhaulRunning = false;
         for (Map.Entry<String, List<Threshold>> entry : mThresholdsList.entrySet()) {
             if (mWaitingThresholds.getOrDefault(entry.getKey(), false)) {
                 continue;
@@ -262,9 +262,13 @@
         }
         if (backhaul > 0) {
             mWaitingThresholds.put(key, true);
-            if (!mHandler.hasMessages(EVENT_WIFI_NOTIFY_TIMER_EXPIRED)) {
-                Log.d(mTag, "Starting backhaul timer = " + backhaul);
-                mHandler.sendEmptyMessageDelayed(EVENT_WIFI_NOTIFY_TIMER_EXPIRED, backhaul);
+            Log.d(mTag, "Starting backhaul timer = " + backhaul);
+            if (!mIsBackhaulRunning) {
+                mTimerIds.add(
+                        mQnsTimer.registerTimer(
+                                Message.obtain(mHandler, EVENT_WIFI_NOTIFY_TIMER_EXPIRED),
+                                backhaul));
+                mIsBackhaulRunning = true;
             }
         } else {
             Log.d(mTag, "Notify for RSSI Threshold Registered w/o Backhaul = " + backhaul);
@@ -302,7 +306,6 @@
             unregisterCallback();
             if (mThresholdsList.isEmpty()) {
                 clearBackHaulTimer();
-                mWaitingThresholds.clear();
             }
         } else {
             Log.d(mTag, "Listening to threshold = " + mRegisteredThreshold);
@@ -378,8 +381,8 @@
                 prefix
                         + ", mIsRegistered="
                         + mIsRegistered
-                        + ", backhaulstatus ="
-                        + mHandler.hasMessages(EVENT_WIFI_NOTIFY_TIMER_EXPIRED));
+                        + ", mIsBackhaulRunning="
+                        + mIsBackhaulRunning);
         pw.println(
                 prefix
                         + "mWifiRssi="
diff --git a/services/QualifiedNetworksService/src/com/android/telephony/qns/atoms/AtomsQnsFallbackRestrictionChangedInfo.java b/services/QualifiedNetworksService/src/com/android/telephony/qns/atoms/AtomsQnsFallbackRestrictionChangedInfo.java
new file mode 100644
index 0000000..05a2fdf
--- /dev/null
+++ b/services/QualifiedNetworksService/src/com/android/telephony/qns/atoms/AtomsQnsFallbackRestrictionChangedInfo.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2022 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.telephony.qns.atoms;
+
+import android.util.StatsEvent;
+
+import com.android.telephony.qns.stats.QnsStatsLog;
+import com.android.telephony.statslib.AtomsPushed;
+
+import java.util.Objects;
+
+/** AtomsQnsFallbackRestrictionChangedInfo class */
+public class AtomsQnsFallbackRestrictionChangedInfo extends AtomsPushed {
+
+    /** atom #1 : Restriction on WLAN caused by RTP threshold breached */
+    private boolean mRestrictionOnWlanByRtpThresholdBreached;
+
+    /** atom #2 : Restriction on WWAN caused by RTP threshold breached */
+    private boolean mRestrictionOnWwanByRtpThresholdBreached;
+
+    /** atom #3 : Restriction on WLAN caused by IMS registration fail */
+    private boolean mRestrictionOnWlanByImsRegistrationFailed;
+
+    /** atom #4 : Restriction on WLAN caused by Wi-Fi backhaul problem. */
+    private boolean mRestrictionOnWlanByWifiBackhaulProblem;
+
+    /** atom #5 : Carrier Id */
+    private int mCarrierId;
+
+    /** atom #6 : Slot Index */
+    private int mSlotIndex;
+
+    /** Constructor of AtomsQnsFallbackRestrictionChangedInfo */
+    public AtomsQnsFallbackRestrictionChangedInfo() {}
+
+    /**
+     * Constructor of AtomsQnsFallbackRestrictionChangedInfo
+     *
+     * @param restrictionOnWlanByRtpThresholdBreached Restriction on wlan caused by RTP threshold
+     *     breached
+     * @param restrictionOnWwanByRtpThresholdBreached Restriction on wwan caused by RTP threshold
+     *     breached
+     * @param restrictionOnWlanByImsRegistrationFailed Restriction on wlan caused by IMS
+     *     registration fail
+     * @param restrictionOnWlanByWifiBackhaulProblem Restriction on wlan caused by Wifi backhaul
+     *     problem.
+     * @param carrierId Carrier Id
+     * @param slotIndex Index of sim slot
+     */
+    public AtomsQnsFallbackRestrictionChangedInfo(
+            boolean restrictionOnWlanByRtpThresholdBreached,
+            boolean restrictionOnWwanByRtpThresholdBreached,
+            boolean restrictionOnWlanByImsRegistrationFailed,
+            boolean restrictionOnWlanByWifiBackhaulProblem,
+            int carrierId,
+            int slotIndex) {
+        mRestrictionOnWlanByRtpThresholdBreached = restrictionOnWlanByRtpThresholdBreached;
+        mRestrictionOnWwanByRtpThresholdBreached = restrictionOnWwanByRtpThresholdBreached;
+        mRestrictionOnWlanByImsRegistrationFailed = restrictionOnWlanByImsRegistrationFailed;
+        mRestrictionOnWlanByWifiBackhaulProblem = restrictionOnWlanByWifiBackhaulProblem;
+        mCarrierId = carrierId;
+        mSlotIndex = slotIndex;
+    }
+
+    /**
+     * Copy Constructor of AtomsQnsFallbackRestrictionChangedInfo
+     *
+     * @param info The info param to copy from.
+     */
+    public AtomsQnsFallbackRestrictionChangedInfo(AtomsQnsFallbackRestrictionChangedInfo info) {
+        mRestrictionOnWlanByRtpThresholdBreached = info.mRestrictionOnWlanByRtpThresholdBreached;
+        mRestrictionOnWwanByRtpThresholdBreached = info.mRestrictionOnWwanByRtpThresholdBreached;
+        mRestrictionOnWlanByImsRegistrationFailed = info.mRestrictionOnWlanByImsRegistrationFailed;
+        mRestrictionOnWlanByWifiBackhaulProblem = info.mRestrictionOnWlanByWifiBackhaulProblem;
+        mCarrierId = info.mCarrierId;
+        mSlotIndex = info.mSlotIndex;
+    }
+
+    /**
+     * Write the atom information to be recorded to the builder according to the type in order.
+     *
+     * @param builder Builder class for StatsEvent Builder object.
+     */
+    @Override
+    public void build(StatsEvent.Builder builder) {
+        builder.writeBoolean(mRestrictionOnWlanByRtpThresholdBreached); // atom #1
+        builder.writeBoolean(mRestrictionOnWwanByRtpThresholdBreached); // atom #2
+        builder.writeBoolean(mRestrictionOnWlanByImsRegistrationFailed); // atom #3
+        builder.writeBoolean(mRestrictionOnWlanByWifiBackhaulProblem); // atom #4
+        builder.writeInt(mCarrierId); // atom #5
+        builder.writeInt(mSlotIndex); // atom #6
+    }
+
+    /** Return atom id defined in proto. */
+    @Override
+    public int getStatsId() {
+        return QnsStatsLog.QNS_FALLBACK_RESTRICTION_CHANGED;
+    }
+
+    /** Return copy of the AtomsQnsFallbackRestrictionChangedInfo */
+    @Override
+    public AtomsPushed copy() {
+        return new AtomsQnsFallbackRestrictionChangedInfo(this);
+    }
+
+    public boolean getRestrictionOnWlanByRtpThresholdBreached() {
+        return mRestrictionOnWlanByRtpThresholdBreached;
+    }
+
+    public void setRestrictionOnWlanByRtpThresholdBreached(
+            boolean restrictionOnWlanByRtpThresholdBreached) {
+        mRestrictionOnWlanByRtpThresholdBreached = restrictionOnWlanByRtpThresholdBreached;
+    }
+
+    public boolean getRestrictionOnWwanByRtpThresholdBreached() {
+        return mRestrictionOnWwanByRtpThresholdBreached;
+    }
+
+    public void setRestrictionOnWwanByRtpThresholdBreached(
+            boolean restrictionOnWwanByRtpThresholdBreached) {
+        mRestrictionOnWwanByRtpThresholdBreached = restrictionOnWwanByRtpThresholdBreached;
+    }
+
+    public boolean getRestrictionOnWlanByImsRegistrationFailed() {
+        return mRestrictionOnWlanByImsRegistrationFailed;
+    }
+
+    public void setRestrictionOnWlanByImsRegistrationFailed(
+            boolean restrictionOnWlanByImsRegistrationFailed) {
+        mRestrictionOnWlanByImsRegistrationFailed = restrictionOnWlanByImsRegistrationFailed;
+    }
+
+    public boolean getRestrictionOnWlanByWifiBackhaulProblem() {
+        return mRestrictionOnWlanByWifiBackhaulProblem;
+    }
+
+    public void setRestrictionOnWlanByWifiBackhaulProblem(
+            boolean restrictionOnWlanByWifiBackhaulProblem) {
+        mRestrictionOnWlanByWifiBackhaulProblem = restrictionOnWlanByWifiBackhaulProblem;
+    }
+
+    public int getCarrierId() {
+        return mCarrierId;
+    }
+
+    public void setCarrierId(int carrierId) {
+        mCarrierId = carrierId;
+    }
+
+    public int getSlotIndex() {
+        return mSlotIndex;
+    }
+
+    public void setSlotIndex(int slotIndex) {
+        mSlotIndex = slotIndex;
+    }
+
+    @Override
+    public String toString() {
+        return "AtomsQnsFallbackRestrictionChangedInfo{"
+                + "mRestrictionWlanRtpThresholdBreached="
+                + mRestrictionOnWlanByRtpThresholdBreached
+                + ", mRestrictionWwanRtpThresholdBreached="
+                + mRestrictionOnWwanByRtpThresholdBreached
+                + ", mRestrictionWwanImsRegiFail="
+                + mRestrictionOnWlanByImsRegistrationFailed
+                + ", mRestrictionWwanWifiBackhaulProblem="
+                + mRestrictionOnWlanByWifiBackhaulProblem
+                + ", mCarrierId="
+                + mCarrierId
+                + ", mSlotIndex="
+                + mSlotIndex
+                + '}';
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof AtomsQnsFallbackRestrictionChangedInfo)) return false;
+        AtomsQnsFallbackRestrictionChangedInfo that = (AtomsQnsFallbackRestrictionChangedInfo) o;
+        return mRestrictionOnWlanByRtpThresholdBreached
+                        == that.mRestrictionOnWlanByRtpThresholdBreached
+                && mRestrictionOnWwanByRtpThresholdBreached
+                        == that.mRestrictionOnWwanByRtpThresholdBreached
+                && mRestrictionOnWlanByImsRegistrationFailed
+                        == that.mRestrictionOnWlanByImsRegistrationFailed
+                && mRestrictionOnWlanByWifiBackhaulProblem
+                        == that.mRestrictionOnWlanByWifiBackhaulProblem
+                && mCarrierId == that.mCarrierId
+                && mSlotIndex == that.mSlotIndex;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(
+                mRestrictionOnWlanByRtpThresholdBreached,
+                mRestrictionOnWwanByRtpThresholdBreached,
+                mRestrictionOnWlanByImsRegistrationFailed,
+                mRestrictionOnWlanByWifiBackhaulProblem,
+                mCarrierId,
+                mSlotIndex);
+    }
+}
diff --git a/services/QualifiedNetworksService/src/com/android/telephony/qns/atoms/AtomsQnsHandoverPingPongInfo.java b/services/QualifiedNetworksService/src/com/android/telephony/qns/atoms/AtomsQnsHandoverPingPongInfo.java
new file mode 100644
index 0000000..a714e58
--- /dev/null
+++ b/services/QualifiedNetworksService/src/com/android/telephony/qns/atoms/AtomsQnsHandoverPingPongInfo.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2022 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.telephony.qns.atoms;
+
+import android.util.StatsEvent;
+
+import com.android.telephony.qns.stats.QnsStatsLog;
+import com.android.telephony.statslib.AtomsPulled;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+/** AtomsQnsHandoverPingPongInfo class */
+public class AtomsQnsHandoverPingPongInfo extends AtomsPulled implements Serializable {
+
+    private static final long serialVersionUID = 815898959L; // 0x30A1A14F
+
+    /** atom #1 : Count of handover ping-pong */
+    private int mCountHandoverPingPong;
+
+    /** atom #2 : Carrier Id */
+    private int mCarrierId;
+
+    /** atom #3 : Slot Index */
+    private int mSlotIndex;
+
+    /** Constructor of AtomsQnsHandoverPingPongInfo */
+    public AtomsQnsHandoverPingPongInfo() {}
+
+    /**
+     * Constructor of AtomsQnsHandoverPingPongInfo
+     *
+     * @param countHandoverPingPong count of Handover Ping-Pong
+     * @param carrierId Carrier Id
+     * @param slotIndex Index of sim slot
+     */
+    public AtomsQnsHandoverPingPongInfo(int countHandoverPingPong, int carrierId, int slotIndex) {
+        mCountHandoverPingPong = countHandoverPingPong;
+        mCarrierId = carrierId;
+        mSlotIndex = slotIndex;
+    }
+
+    /**
+     * Copy Constructor of AtomsQnsHandoverPingPongInfo
+     *
+     * @param info The info param to copy from.
+     */
+    public AtomsQnsHandoverPingPongInfo(AtomsQnsHandoverPingPongInfo info) {
+        mCountHandoverPingPong = info.mCountHandoverPingPong;
+        mCarrierId = info.mCarrierId;
+        mSlotIndex = info.mSlotIndex;
+    }
+
+    /**
+     * Write the atom information to be recorded to the builder according to the type in order.
+     *
+     * @param builder Builder class for StatsEvent Builder object.
+     */
+    @Override
+    public void build(StatsEvent.Builder builder) {
+        builder.writeInt(mCountHandoverPingPong); // atom #1
+        builder.writeInt(mCarrierId); // atom #2
+        builder.writeInt(mSlotIndex); // atom #3
+    }
+
+    /** Return atom id defined in proto. */
+    @Override
+    public int getStatsId() {
+        return QnsStatsLog.QNS_HANDOVER_PINGPONG;
+    }
+
+    /** Return copy of the AtomsQnsHandoverPingPongInfo */
+    @Override
+    public AtomsPulled copy() {
+        return new AtomsQnsHandoverPingPongInfo(this);
+    }
+
+    @Override
+    public String getDimension() {
+        return mCarrierId + "_" + mSlotIndex;
+    }
+
+    @Override
+    public void accumulate(AtomsPulled info) {
+        if (!(info instanceof AtomsQnsHandoverPingPongInfo)) {
+            return;
+        }
+        AtomsQnsHandoverPingPongInfo atomsQnsHandoverPingPongInfo =
+                (AtomsQnsHandoverPingPongInfo) info;
+        this.mCountHandoverPingPong += atomsQnsHandoverPingPongInfo.getCountHandoverPingPong();
+    }
+
+    public int getCountHandoverPingPong() {
+        return mCountHandoverPingPong;
+    }
+
+    public void setCountHandoverPingPong(int countHandoverPingPong) {
+        mCountHandoverPingPong = countHandoverPingPong;
+    }
+
+    public int getCarrierId() {
+        return mCarrierId;
+    }
+
+    public void setCarrierId(int carrierId) {
+        mCarrierId = carrierId;
+    }
+
+    public int getSlotIndex() {
+        return mSlotIndex;
+    }
+
+    public void setSlotIndex(int slotIndex) {
+        mSlotIndex = slotIndex;
+    }
+
+    @Override
+    public String toString() {
+        return "AtomsQnsHandoverPingPongInfo{"
+                + "mCountHandoverPingPong="
+                + mCountHandoverPingPong
+                + ", mCarrierId="
+                + mCarrierId
+                + ", mSlotIndex="
+                + mSlotIndex
+                + '}';
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof AtomsQnsHandoverPingPongInfo)) return false;
+        AtomsQnsHandoverPingPongInfo that = (AtomsQnsHandoverPingPongInfo) o;
+        return mCountHandoverPingPong == that.mCountHandoverPingPong
+                && mCarrierId == that.mCarrierId
+                && mSlotIndex == that.mSlotIndex;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mCountHandoverPingPong, mCarrierId, mSlotIndex);
+    }
+
+    public static long PING_PONG_TIME_IN_MILLIS = 5000L;
+}
diff --git a/services/QualifiedNetworksService/src/com/android/telephony/qns/atoms/AtomsQnsHandoverTimeMillisInfo.java b/services/QualifiedNetworksService/src/com/android/telephony/qns/atoms/AtomsQnsHandoverTimeMillisInfo.java
new file mode 100644
index 0000000..e6924ac
--- /dev/null
+++ b/services/QualifiedNetworksService/src/com/android/telephony/qns/atoms/AtomsQnsHandoverTimeMillisInfo.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2022 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.telephony.qns.atoms;
+
+import android.util.StatsEvent;
+
+import com.android.telephony.qns.stats.QnsStatsLog;
+import com.android.telephony.statslib.AtomsPulled;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+/** AtomsQnsHandoverTimeMillisInfo class */
+public class AtomsQnsHandoverTimeMillisInfo extends AtomsPulled implements Serializable {
+
+    private static final long serialVersionUID = 1379441656L; // 0x52389BF8
+
+    /** atom #1 : Time in milliseconds from QNS RAT update to successful HO completion */
+    private int mTimeForHoSuccess;
+
+    /** atom #2 : Slot Index */
+    private int mSlotIndex;
+
+    /** Constructor of AtomsQnsHandoverTimeMillisInfo */
+    public AtomsQnsHandoverTimeMillisInfo() {}
+
+    /**
+     * Constructor of AtomsQnsHandoverTimeMillisInfo
+     *
+     * @param timeForHoSuccess Time in milliseconds from QNS RAT update to successful HO completion
+     * @param slotIndex Index of sim slot
+     */
+    public AtomsQnsHandoverTimeMillisInfo(int timeForHoSuccess, int slotIndex) {
+        mTimeForHoSuccess = timeForHoSuccess;
+        mSlotIndex = slotIndex;
+    }
+
+    /**
+     * Copy Constructor of AtomsQnsHandoverTimeMillisInfo
+     *
+     * @param info The info param to copy from.
+     */
+    public AtomsQnsHandoverTimeMillisInfo(AtomsQnsHandoverTimeMillisInfo info) {
+        mTimeForHoSuccess = info.mTimeForHoSuccess;
+        mSlotIndex = info.mSlotIndex;
+    }
+
+    /**
+     * Write the atom information to be recorded to the builder according to the type in order.
+     *
+     * @param builder Builder class for StatsEvent Builder object.
+     */
+    @Override
+    public void build(StatsEvent.Builder builder) {
+        builder.writeInt(mTimeForHoSuccess); // atom #1
+        builder.writeInt(mSlotIndex); // atom #2
+    }
+
+    /** Return atom id defined in proto. */
+    @Override
+    public int getStatsId() {
+        return QnsStatsLog.QNS_HANDOVER_TIME_MILLIS;
+    }
+
+    /** Return copy of the AtomsQnsHandoverTimeMillisInfo */
+    @Override
+    public AtomsPulled copy() {
+        return new AtomsQnsHandoverTimeMillisInfo(this);
+    }
+
+    @Override
+    public String getDimension() {
+        return Integer.toString(mSlotIndex);
+    }
+
+    @Override
+    public void accumulate(AtomsPulled info) {
+        if (!(info instanceof AtomsQnsRatPreferenceMismatchInfo)) {
+            return;
+        }
+        AtomsQnsHandoverTimeMillisInfo atomsQnsHandoverTimeMillisInfo =
+                (AtomsQnsHandoverTimeMillisInfo) info;
+        this.mTimeForHoSuccess += atomsQnsHandoverTimeMillisInfo.getTimeForHoSuccess();
+    }
+
+    public int getTimeForHoSuccess() {
+        return mTimeForHoSuccess;
+    }
+
+    public void setTimeForHoSuccess(int timeForHoSuccess) {
+        mTimeForHoSuccess = timeForHoSuccess;
+    }
+
+    public int getSlotIndex() {
+        return mSlotIndex;
+    }
+
+    public void setSlotIndex(int slotIndex) {
+        mSlotIndex = slotIndex;
+    }
+
+    @Override
+    public String toString() {
+        return "AtomsQnsHandoverTimeMillisInfo{" + "mTimeForHoSuccess=" + mTimeForHoSuccess + '}';
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof AtomsQnsHandoverTimeMillisInfo)) return false;
+        AtomsQnsHandoverTimeMillisInfo that = (AtomsQnsHandoverTimeMillisInfo) o;
+        return mTimeForHoSuccess == that.mTimeForHoSuccess && mSlotIndex == that.mSlotIndex;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mTimeForHoSuccess, mSlotIndex);
+    }
+}
diff --git a/services/QualifiedNetworksService/src/com/android/telephony/qns/atoms/AtomsQnsImsCallDropStats.java b/services/QualifiedNetworksService/src/com/android/telephony/qns/atoms/AtomsQnsImsCallDropStats.java
new file mode 100644
index 0000000..991bce4
--- /dev/null
+++ b/services/QualifiedNetworksService/src/com/android/telephony/qns/atoms/AtomsQnsImsCallDropStats.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2022 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.telephony.qns.atoms;
+
+import android.util.StatsEvent;
+
+import com.android.telephony.qns.stats.QnsStatsLog;
+import com.android.telephony.statslib.AtomsPushed;
+
+import java.util.Objects;
+
+/** AtomsQnsImsCallDropStats class */
+public class AtomsQnsImsCallDropStats extends AtomsPushed {
+
+    /** atom #1 : Transport type in where IMS call drop occurred. */
+    private int mTransportTypeCallDropped;
+
+    /** atom #2 : RTP threshold breached event occurred. */
+    private boolean mRtpThresholdBreached;
+
+    /** atom #3 : Bit mask of restrictions on another transport type */
+    private int mRestrictionsOnOtherTransportType;
+
+    /** atom #4 : Cellular network signal strength {e.g. SSRSRP in NR, RSRP in LTE} */
+    private int mSignalStrength;
+
+    /** atom #5 : Cellular network signal quality {e.g. SSRSRQ in NR, RSRQ in LTE} */
+    private int mSignalQuality;
+
+    /** atom #6 : Cellular network signal noise ratio {e.g. SSSINR in NR, RSSNR in LTE} */
+    private int mSignalNoise;
+
+    /** atom #7 : Iwlan network signal strength (Wi-Fi RSSI) */
+    private int mIwlanSignalStrength;
+
+    /** atom #8 : Slot Index */
+    private int mSlotIndex;
+
+    /** atom #9 : cellular access network type. */
+    private int mCellularNetworkType;
+
+    /** Constructor of AtomsQnsImsCallDropStats */
+    public AtomsQnsImsCallDropStats() {}
+
+    /**
+     * Write the atom information to be recorded to the builder according to the type in order.
+     *
+     * @param builder Builder class for StatsEvent Builder object.
+     */
+    @Override
+    public void build(StatsEvent.Builder builder) {
+        builder.writeInt(mTransportTypeCallDropped); // atom #1
+        builder.writeBoolean(mRtpThresholdBreached); // atom #2
+        builder.writeInt(mRestrictionsOnOtherTransportType); // atom #3
+        builder.writeInt(mSignalStrength); // atom #4
+        builder.writeInt(mSignalQuality); // atom #5
+        builder.writeInt(mSignalNoise); // atom #6
+        builder.writeInt(mIwlanSignalStrength); // atom #7
+        builder.writeInt(mSlotIndex); // atom #8
+        builder.writeInt(mCellularNetworkType); // atom #9
+    }
+
+    /** Return atom id defined in proto. */
+    @Override
+    public int getStatsId() {
+        return QnsStatsLog.QNS_IMS_CALL_DROP_STATS;
+    }
+
+    /** Return copy of the AtomsQnsImsCallDropStats */
+    @Override
+    public AtomsPushed copy() {
+        return new AtomsQnsImsCallDropStats(this);
+    }
+
+    /**
+     * Constructor of AtomsQnsImsCallDropStats
+     *
+     * @param transportTypeCallDropped Transport type in where IMS call drop occurred.
+     * @param rtpThresholdBreached RTP threshold breached event occurred.
+     * @param restrictionsOnOtherTransportType Bit mask of restrictions on another transport type.
+     * @param signalStrength Cellular network signal strength.
+     * @param signalQuality Cellular network signal quality.
+     * @param signalNoise Cellular network signal noise ratio.
+     * @param iwlanSignalStrength Wi-Fi network signal strength.
+     * @param slotIndex Index of sim slot.
+     * @param cellularNetworkType cellular access network type.
+     */
+    public AtomsQnsImsCallDropStats(
+            int transportTypeCallDropped,
+            boolean rtpThresholdBreached,
+            int restrictionsOnOtherTransportType,
+            int signalStrength,
+            int signalQuality,
+            int signalNoise,
+            int iwlanSignalStrength,
+            int slotIndex,
+            int cellularNetworkType) {
+        mTransportTypeCallDropped = transportTypeCallDropped;
+        mRtpThresholdBreached = rtpThresholdBreached;
+        mRestrictionsOnOtherTransportType = restrictionsOnOtherTransportType;
+        mSignalStrength = signalStrength;
+        mSignalQuality = signalQuality;
+        mSignalNoise = signalNoise;
+        mIwlanSignalStrength = iwlanSignalStrength;
+        mSlotIndex = slotIndex;
+        mCellularNetworkType = cellularNetworkType;
+    }
+
+    /**
+     * Copy Constructor of AtomsQnsImsCallDropStats
+     *
+     * @param info The info param to copy from.
+     */
+    public AtomsQnsImsCallDropStats(AtomsQnsImsCallDropStats info) {
+        mTransportTypeCallDropped = info.mTransportTypeCallDropped;
+        mRtpThresholdBreached = info.mRtpThresholdBreached;
+        mRestrictionsOnOtherTransportType = info.mRestrictionsOnOtherTransportType;
+        mSignalStrength = info.mSignalStrength;
+        mSignalQuality = info.mSignalQuality;
+        mSignalNoise = info.mSignalNoise;
+        mIwlanSignalStrength = info.mIwlanSignalStrength;
+        mSlotIndex = info.mSlotIndex;
+        mCellularNetworkType = info.mCellularNetworkType;
+    }
+
+    public int getTransportTypeCallDropped() {
+        return mTransportTypeCallDropped;
+    }
+
+    public void setTransportTypeCallDropped(int transportTypeCallDropped) {
+        mTransportTypeCallDropped = transportTypeCallDropped;
+    }
+
+    public boolean getRtpThresholdBreached() {
+        return mRtpThresholdBreached;
+    }
+
+    public void setRtpThresholdBreached(boolean rtpThresholdBreached) {
+        mRtpThresholdBreached = rtpThresholdBreached;
+    }
+
+    public int getRestrictionsOnOtherTransportType() {
+        return mRestrictionsOnOtherTransportType;
+    }
+
+    public void setRestrictionsOnOtherTransportType(int restrictionsOnOtherTransportType) {
+        mRestrictionsOnOtherTransportType = restrictionsOnOtherTransportType;
+    }
+
+    public int getSignalStrength() {
+        return mSignalStrength;
+    }
+
+    public void setSignalStrength(int signalStrength) {
+        mSignalStrength = signalStrength;
+    }
+
+    public int getSignalQuality() {
+        return mSignalQuality;
+    }
+
+    public void setSignalQuality(int signalQuality) {
+        mSignalQuality = signalQuality;
+    }
+
+    public int getSignalNoise() {
+        return mSignalNoise;
+    }
+
+    public void setSignalNoise(int signalNoise) {
+        mSignalNoise = signalNoise;
+    }
+
+    public int getIwlanSignalStrength() {
+        return mIwlanSignalStrength;
+    }
+
+    public void setIwlanSignalStrength(int iwlanSignalStrength) {
+        mIwlanSignalStrength = iwlanSignalStrength;
+    }
+
+    public int getSlotIndex() {
+        return mSlotIndex;
+    }
+
+    public void setSlotIndex(int slotIndex) {
+        mSlotIndex = slotIndex;
+    }
+
+    public int getCellularNetworkType() {
+        return mCellularNetworkType;
+    }
+
+    public void setCellularNetworkType(int cellularNetworkType) {
+        mCellularNetworkType = cellularNetworkType;
+    }
+
+    @Override
+    public String toString() {
+        return "AtomsQnsImsCallDropStats{"
+                + "mTransportTypeCallDropped="
+                + mTransportTypeCallDropped
+                + ", mRtpThresholdBreached="
+                + mRtpThresholdBreached
+                + ", mRestrictionsOnOtherTransportType="
+                + mRestrictionsOnOtherTransportType
+                + ", mSignalStrength="
+                + mSignalStrength
+                + ", mSignalQuality="
+                + mSignalQuality
+                + ", mSignalNoise="
+                + mSignalNoise
+                + ", mIwlanSignalStrength="
+                + mIwlanSignalStrength
+                + ", mSlotIndex="
+                + mSlotIndex
+                + ", mCellularNetworkType="
+                + mCellularNetworkType
+                + '}';
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof AtomsQnsImsCallDropStats)) return false;
+        AtomsQnsImsCallDropStats that = (AtomsQnsImsCallDropStats) o;
+        return mTransportTypeCallDropped == that.mTransportTypeCallDropped
+                && mRtpThresholdBreached == that.mRtpThresholdBreached
+                && mRestrictionsOnOtherTransportType == that.mRestrictionsOnOtherTransportType
+                && mSignalStrength == that.mSignalStrength
+                && mSignalQuality == that.mSignalQuality
+                && mSignalNoise == that.mSignalNoise
+                && mIwlanSignalStrength == that.mIwlanSignalStrength
+                && mSlotIndex == that.mSlotIndex
+                && mCellularNetworkType == that.mCellularNetworkType;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(
+                mTransportTypeCallDropped,
+                mRtpThresholdBreached,
+                mRestrictionsOnOtherTransportType,
+                mSignalStrength,
+                mSignalQuality,
+                mSignalNoise,
+                mIwlanSignalStrength,
+                mSlotIndex,
+                mCellularNetworkType);
+    }
+}
diff --git a/services/QualifiedNetworksService/src/com/android/telephony/qns/atoms/AtomsQnsRatPreferenceMismatchInfo.java b/services/QualifiedNetworksService/src/com/android/telephony/qns/atoms/AtomsQnsRatPreferenceMismatchInfo.java
new file mode 100644
index 0000000..150f6a1
--- /dev/null
+++ b/services/QualifiedNetworksService/src/com/android/telephony/qns/atoms/AtomsQnsRatPreferenceMismatchInfo.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2022 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.telephony.qns.atoms;
+
+import android.util.StatsEvent;
+
+import com.android.telephony.qns.stats.QnsStatsLog;
+import com.android.telephony.statslib.AtomsPulled;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+/** AtomsQnsRatPreferenceMismatchInfo class */
+public class AtomsQnsRatPreferenceMismatchInfo extends AtomsPulled implements Serializable {
+
+    private static final long serialVersionUID = 1913485192L; // 0x720D7788
+
+    /** atom #1 : Net capability of this information. */
+    private int mNetCapability;
+
+    /** atom #2 : Count of handover failed. */
+    private int mHandoverFailCount;
+
+    /** atom #3 : Duration of this mismatch. */
+    private int mDurationOfMismatch;
+
+    /** atom #4 : Carrier Id */
+    private int mCarrierId;
+
+    /** atom #5 : Slot Index */
+    private int mSlotIndex;
+
+    /** Constructor of AtomsQnsRatPreferenceMismatchInfo */
+    public AtomsQnsRatPreferenceMismatchInfo() {}
+
+    /**
+     * Constructor of AtomsQnsRatPreferenceMismatchInfo
+     *
+     * @param netCapability Net capability of this information.
+     * @param handoverFailCount Count of handover failed.
+     * @param durationOfMismatch Duration of this mismatch.
+     * @param carrierId Carrier Id
+     * @param slotIndex Index of sim slot
+     */
+    public AtomsQnsRatPreferenceMismatchInfo(
+            int netCapability,
+            int handoverFailCount,
+            int durationOfMismatch,
+            int carrierId,
+            int slotIndex) {
+        mNetCapability = netCapability;
+        mHandoverFailCount = handoverFailCount;
+        mDurationOfMismatch = durationOfMismatch;
+        mCarrierId = carrierId;
+        mSlotIndex = slotIndex;
+    }
+
+    /**
+     * Copy Constructor of AtomsQnsRatPreferenceMismatchInfo
+     *
+     * @param info The info param to copy from.
+     */
+    public AtomsQnsRatPreferenceMismatchInfo(AtomsQnsRatPreferenceMismatchInfo info) {
+        mNetCapability = info.mNetCapability;
+        mHandoverFailCount = info.mHandoverFailCount;
+        mDurationOfMismatch = info.mDurationOfMismatch;
+        mCarrierId = info.mCarrierId;
+        mSlotIndex = info.mSlotIndex;
+    }
+
+    /**
+     * Write the atom information to be recorded to the builder according to the type in order.
+     *
+     * @param builder Builder class for StatsEvent Builder object.
+     */
+    @Override
+    public void build(StatsEvent.Builder builder) {
+        builder.writeInt(mNetCapability); // atom #1
+        builder.writeInt(mHandoverFailCount); // atom #2
+        builder.writeInt(mDurationOfMismatch); // atom #3
+        builder.writeInt(mCarrierId); // atom #4
+        builder.writeInt(mSlotIndex); // atom #5
+    }
+
+    /** Return atom id defined in proto. */
+    @Override
+    public int getStatsId() {
+        return QnsStatsLog.QNS_RAT_PREFERENCE_MISMATCH_INFO;
+    }
+
+    /** Return copy of the AtomsQnsRatPreferenceMismatchInfo */
+    @Override
+    public AtomsPulled copy() {
+        return new AtomsQnsRatPreferenceMismatchInfo(this);
+    }
+
+    @Override
+    public String getDimension() {
+        return mNetCapability + "_" + mCarrierId + "_" + mSlotIndex;
+    }
+
+    @Override
+    public void accumulate(AtomsPulled info) {
+        if (!(info instanceof AtomsQnsRatPreferenceMismatchInfo)) {
+            return;
+        }
+        AtomsQnsRatPreferenceMismatchInfo atomsQnsRatPreferenceMismatchInfo =
+                (AtomsQnsRatPreferenceMismatchInfo) info;
+        this.mDurationOfMismatch += atomsQnsRatPreferenceMismatchInfo.getDurationOfMismatch();
+        this.mHandoverFailCount += atomsQnsRatPreferenceMismatchInfo.getHandoverFailCount();
+    }
+
+    public int getNetCapability() {
+        return mNetCapability;
+    }
+
+    public void setNetCapability(int netCapability) {
+        mNetCapability = netCapability;
+    }
+
+    public int getHandoverFailCount() {
+        return mHandoverFailCount;
+    }
+
+    public void setHandoverFailCount(int handoverFailCount) {
+        mHandoverFailCount = handoverFailCount;
+    }
+
+    public int getDurationOfMismatch() {
+        return mDurationOfMismatch;
+    }
+
+    public void setDurationOfMismatch(int durationOfMismatch) {
+        mDurationOfMismatch = durationOfMismatch;
+    }
+
+    public int getCarrierId() {
+        return mCarrierId;
+    }
+
+    public void setCarrierId(int carrierId) {
+        mCarrierId = carrierId;
+    }
+
+    public int getSlotIndex() {
+        return mSlotIndex;
+    }
+
+    public void setSlotIndex(int slotIndex) {
+        mSlotIndex = slotIndex;
+    }
+
+    @Override
+    public String toString() {
+        return "AtomsQnsRatPreferenceMismatchInfo{"
+                + "mNetCapability="
+                + mNetCapability
+                + ", mHandoverFailCount="
+                + mHandoverFailCount
+                + ", mDurationOfMismatch="
+                + mDurationOfMismatch
+                + ", mCarrierId="
+                + mCarrierId
+                + ", mSlotIndex="
+                + mSlotIndex
+                + '}';
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof AtomsQnsRatPreferenceMismatchInfo)) return false;
+        AtomsQnsRatPreferenceMismatchInfo that = (AtomsQnsRatPreferenceMismatchInfo) o;
+        return mNetCapability == that.mNetCapability
+                && mHandoverFailCount == that.mHandoverFailCount
+                && mDurationOfMismatch == that.mDurationOfMismatch
+                && mCarrierId == that.mCarrierId
+                && mSlotIndex == that.mSlotIndex;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(
+                mNetCapability, mHandoverFailCount, mDurationOfMismatch, mCarrierId, mSlotIndex);
+    }
+}
diff --git a/services/QualifiedNetworksService/src/com/android/telephony/qns/atoms/AtomsQualifiedRatListChangedInfo.java b/services/QualifiedNetworksService/src/com/android/telephony/qns/atoms/AtomsQualifiedRatListChangedInfo.java
new file mode 100644
index 0000000..ea3ec6b
--- /dev/null
+++ b/services/QualifiedNetworksService/src/com/android/telephony/qns/atoms/AtomsQualifiedRatListChangedInfo.java
@@ -0,0 +1,430 @@
+/*
+ * Copyright (C) 2022 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.telephony.qns.atoms;
+
+import android.util.StatsEvent;
+
+import com.android.telephony.qns.stats.QnsStatsLog;
+import com.android.telephony.statslib.AtomsPushed;
+
+import java.util.Objects;
+
+/** AtomsQualifiedRatListChangedInfo class */
+public class AtomsQualifiedRatListChangedInfo extends AtomsPushed {
+
+    /** atom #1 : NetCapability of this Qualified RAT update */
+    private int mNetCapability;
+    /** atom #2 : The most preferred qualified RAT */
+    private int mFirstQualifiedRat;
+    /** atom #3 : Second preferred qualified RAT */
+    private int mSecondQualifiedRat;
+    /** atom #4 : Current actual transport type of Data session for this NetCapability */
+    private int mCurrentTransportType;
+    /** atom #5 : Indicates whether WFC is enabled */
+    private boolean mWfcEnabled;
+    /** atom #6 : Indicates the user's WFC mode */
+    private int mWfcMode;
+    /** atom #7 : Current Cellular AccessNetwork Type */
+    private int mCellularNetworkType;
+    /** atom #8 : Available IWLAN AccessNetwork */
+    private int mIwlanNetworkType;
+    /** atom #9 : Bit mask of restrictions on WWAN */
+    private int mRestrictionsOnWwan;
+    /** atom #10 : Bit mask of restrictions on WLAN */
+    private int mRestrictionsOnWlan;
+    /** atom #11 : Cellular network signal strength {e.g. SSRSRP in NR, RSRP in LTE} */
+    private int mSignalStrength;
+    /** atom #12 : Cellular network signal quality {e.g. SSRSRQ in NR, RSRQ in LTE} */
+    private int mSignalQuality;
+    /** atom #13 : Cellular network signal noise ratio {e.g. SSSINR in NR, RSSNR in LTE} */
+    private int mSignalNoise;
+    /** atom #14 : Iwlan network signal strength (Wi-Fi RSSI) */
+    private int mIwlanSignalStrength;
+    /** atom #15 : Reason for preferred RAT update */
+    private int mUpdateReason;
+    /** atom #16: IMS Call Type */
+    private int mImsCallType;
+    /** atom #17 : IMS Call Quality */
+    private int mImsCallQuality;
+    /** atom #18 : Slot Index */
+    private int mSlotIndex;
+
+    /** Constructor of AtomsQualifiedRatListChangedInfo */
+    public AtomsQualifiedRatListChangedInfo() {}
+
+    /**
+     * Constructor of AtomsQualifiedRatListChangedInfo
+     *
+     * @param netCapability NetCapability of this Qualified RAT update
+     * @param firstQualifiedRat The most preferred qualified RAT
+     * @param secondQualifiedRat Second preferred qualified RAT
+     * @param currentTransportType Current actual transport type of Data session for this
+     *     NetCapability
+     * @param wfcEnabled Indicates whether WFC is enabled
+     * @param wfcMode Indicates the user's WFC mode
+     * @param cellularNetworkType Current Cellular AccessNetwork Type
+     * @param iwlanNetworkType Available IWLAN AccessNetwork
+     * @param restrictionsOnWwan Bit mask of restrictions on WWAN
+     * @param restrictionsOnWlan Bit mask of restrictions on WLAN
+     * @param signalStrength Cellular network signal strength {e.g. SSRSRP in NR, RSRP in LTE, RSCP
+     *     in UMTS}
+     * @param signalQuality Cellular network signal quality {e.g. SSRSRQ in NR, RSRQ in LTE}
+     * @param signalNoise Cellular network signal noise ratio {e.g. SSSINR in NR, RSSNR in LTE}
+     * @param iwlanSignalStrength Iwlan network signal strength (Wi-Fi RSSI)
+     * @param updateReason Reason for preferred RAT update
+     * @param imsCallType Ims Call Type {e.g. IDLE, VOICE, VIDEO, E-CALL}
+     * @param imsCallQuality Ims Call Quality
+     * @param slotIndex Index of sim slot
+     */
+    public AtomsQualifiedRatListChangedInfo(
+            int netCapability,
+            int firstQualifiedRat,
+            int secondQualifiedRat,
+            int currentTransportType,
+            boolean wfcEnabled,
+            int wfcMode,
+            int cellularNetworkType,
+            int iwlanNetworkType,
+            int restrictionsOnWwan,
+            int restrictionsOnWlan,
+            int signalStrength,
+            int signalQuality,
+            int signalNoise,
+            int iwlanSignalStrength,
+            int updateReason,
+            int imsCallType,
+            int imsCallQuality,
+            int slotIndex) {
+        mNetCapability = netCapability;
+        mFirstQualifiedRat = firstQualifiedRat;
+        mSecondQualifiedRat = secondQualifiedRat;
+        mCurrentTransportType = currentTransportType;
+        mWfcEnabled = wfcEnabled;
+        mWfcMode = wfcMode;
+        mCellularNetworkType = cellularNetworkType;
+        mIwlanNetworkType = iwlanNetworkType;
+        mRestrictionsOnWwan = restrictionsOnWwan;
+        mRestrictionsOnWlan = restrictionsOnWlan;
+        mSignalStrength = signalStrength;
+        mSignalQuality = signalQuality;
+        mSignalNoise = signalNoise;
+        mIwlanSignalStrength = iwlanSignalStrength;
+        mUpdateReason = updateReason;
+        mImsCallType = imsCallType;
+        mImsCallQuality = imsCallQuality;
+        mSlotIndex = slotIndex;
+    }
+
+    /**
+     * Copy Constructor of AtomsQualifiedRatListChangedInfo
+     *
+     * @param info The info param to copy from.
+     */
+    public AtomsQualifiedRatListChangedInfo(AtomsQualifiedRatListChangedInfo info) {
+        mNetCapability = info.mNetCapability;
+        mFirstQualifiedRat = info.mFirstQualifiedRat;
+        mSecondQualifiedRat = info.mSecondQualifiedRat;
+        mCurrentTransportType = info.mCurrentTransportType;
+        mWfcEnabled = info.mWfcEnabled;
+        mWfcMode = info.mWfcMode;
+        mCellularNetworkType = info.mCellularNetworkType;
+        mIwlanNetworkType = info.mIwlanNetworkType;
+        mRestrictionsOnWwan = info.mRestrictionsOnWwan;
+        mRestrictionsOnWlan = info.mRestrictionsOnWlan;
+        mSignalStrength = info.mSignalStrength;
+        mSignalQuality = info.mSignalQuality;
+        mSignalNoise = info.mSignalNoise;
+        mIwlanSignalStrength = info.mIwlanSignalStrength;
+        mUpdateReason = info.mUpdateReason;
+        mImsCallType = info.mImsCallType;
+        mImsCallQuality = info.mImsCallQuality;
+        mSlotIndex = info.mSlotIndex;
+    }
+
+    /**
+     * Write the atom information to be recorded to the builder according to the type in order.
+     *
+     * @param builder Builder class for StatsEvent Builder object.
+     */
+    @Override
+    public void build(StatsEvent.Builder builder) {
+        builder.writeInt(mNetCapability); // atom #1
+        builder.writeInt(mFirstQualifiedRat); // atom #2
+        builder.writeInt(mSecondQualifiedRat); // atom #3
+        builder.writeInt(mCurrentTransportType); // atom #4
+        builder.writeBoolean(mWfcEnabled); // atom #5
+        builder.writeInt(mWfcMode); // atom #6
+        builder.writeInt(mCellularNetworkType); // atom #7
+        builder.writeInt(mIwlanNetworkType); // atom #8
+        builder.writeInt(mRestrictionsOnWwan); // atom #9
+        builder.writeInt(mRestrictionsOnWlan); // atom #10
+        builder.writeInt(mSignalStrength); // atom #11
+        builder.writeInt(mSignalQuality); // atom #12
+        builder.writeInt(mSignalNoise); // atom #13
+        builder.writeInt(mIwlanSignalStrength); // atom #14
+        builder.writeInt(mUpdateReason); // atom #15
+        builder.writeInt(mImsCallType); // atom #16
+        builder.writeInt(mImsCallQuality); // atom #17
+        builder.writeInt(mSlotIndex); // atom #18
+    }
+
+    /** Return atom id defined in proto. */
+    @Override
+    public int getStatsId() {
+        return QnsStatsLog.QUALIFIED_RAT_LIST_CHANGED;
+    }
+
+    /** Return copy of the AtomsQualifiedRatListChangedInfo */
+    @Override
+    public AtomsPushed copy() {
+        return new AtomsQualifiedRatListChangedInfo(this);
+    }
+
+    public int getNetCapability() {
+        return mNetCapability;
+    }
+
+    public void setNetCapability(int netCapability) {
+        mNetCapability = netCapability;
+    }
+
+    public int getFirstQualifiedRat() {
+        return mFirstQualifiedRat;
+    }
+
+    public void setFirstQualifiedRat(int firstQualifiedRat) {
+        mFirstQualifiedRat = firstQualifiedRat;
+    }
+
+    public int getSecondQualifiedRat() {
+        return mSecondQualifiedRat;
+    }
+
+    public void setSecondQualifiedRat(int secondQualifiedRat) {
+        mSecondQualifiedRat = secondQualifiedRat;
+    }
+
+    public int getCurrentTransportType() {
+        return mCurrentTransportType;
+    }
+
+    public void setCurrentTransportType(int currentTransportType) {
+        mCurrentTransportType = currentTransportType;
+    }
+
+    public boolean getWfcEnabled() {
+        return mWfcEnabled;
+    }
+
+    public void setWfcEnabled(boolean wfcEnabled) {
+        mWfcEnabled = wfcEnabled;
+    }
+
+    public int getWfcMode() {
+        return mWfcMode;
+    }
+
+    public void setWfcMode(int wfcMode) {
+        mWfcMode = wfcMode;
+    }
+
+    public int getCellularNetworkType() {
+        return mCellularNetworkType;
+    }
+
+    public void setCellularNetworkType(int cellularNetworkType) {
+        mCellularNetworkType = cellularNetworkType;
+    }
+
+    public int getIwlanNetworkType() {
+        return mIwlanNetworkType;
+    }
+
+    public void setIwlanNetworkType(int iwlanNetworkType) {
+        mIwlanNetworkType = iwlanNetworkType;
+    }
+
+    public int getRestrictionsOnWwan() {
+        return mRestrictionsOnWwan;
+    }
+
+    public void setRestrictionsOnWwan(int restrictionsOnWwan) {
+        mRestrictionsOnWwan = restrictionsOnWwan;
+    }
+
+    public int getRestrictionsOnWlan() {
+        return mRestrictionsOnWlan;
+    }
+
+    public void setRestrictionsOnWlan(int restrictionsOnWlan) {
+        mRestrictionsOnWlan = restrictionsOnWlan;
+    }
+
+    public int getSignalStrength() {
+        return mSignalStrength;
+    }
+
+    public void setSignalStrength(int signalStrength) {
+        mSignalStrength = signalStrength;
+    }
+
+    public int getSignalQuality() {
+        return mSignalQuality;
+    }
+
+    public void setSignalQuality(int signalQuality) {
+        mSignalQuality = signalQuality;
+    }
+
+    public int getSignalNoise() {
+        return mSignalNoise;
+    }
+
+    public void setSignalNoise(int signalNoise) {
+        mSignalNoise = signalNoise;
+    }
+
+    public int getIwlanSignalStrength() {
+        return mIwlanSignalStrength;
+    }
+
+    public void setIwlanSignalStrength(int iwlanSignalStrength) {
+        mIwlanSignalStrength = iwlanSignalStrength;
+    }
+
+    public int getUpdateReason() {
+        return mUpdateReason;
+    }
+
+    public void setUpdateReason(int updateReason) {
+        mUpdateReason = updateReason;
+    }
+
+    public int getImsCallType() {
+        return mImsCallType;
+    }
+
+    public void setImsCallType(int imsCallType) {
+        mImsCallType = imsCallType;
+    }
+
+    public int getImsCallQuality() {
+        return mImsCallQuality;
+    }
+
+    public void setImsCallQuality(int imsCallQuality) {
+        mImsCallQuality = imsCallQuality;
+    }
+
+    public int getSlotIndex() {
+        return mSlotIndex;
+    }
+
+    public void setSlotIndex(int slotIndex) {
+        mSlotIndex = slotIndex;
+    }
+
+    @Override
+    public String toString() {
+        return "AtomsQualifiedRatListChangedInfo{"
+                + "mNetCapability="
+                + mNetCapability
+                + ", mFirstQualifiedRat="
+                + mFirstQualifiedRat
+                + ", mSecondQualifiedRat="
+                + mSecondQualifiedRat
+                + ", mCurrentTransportType="
+                + mCurrentTransportType
+                + ", mWfcEnabled="
+                + mWfcEnabled
+                + ", mWfcMode="
+                + mWfcMode
+                + ", mCellularNetworkType="
+                + mCellularNetworkType
+                + ", mIwlanNetworkType="
+                + mIwlanNetworkType
+                + ", mRestrictionsOnWwan="
+                + mRestrictionsOnWwan
+                + ", mRestrictionsOnWlan="
+                + mRestrictionsOnWlan
+                + ", mSignalStrength="
+                + mSignalStrength
+                + ", mSignalQuality="
+                + mSignalQuality
+                + ", mSignalNoise="
+                + mSignalNoise
+                + ", mIwlanSignalStrength="
+                + mIwlanSignalStrength
+                + ", mUpdateReason="
+                + mUpdateReason
+                + ", mImsCallType="
+                + mImsCallType
+                + ", mImsCallQuality="
+                + mImsCallQuality
+                + ", mSlotIndex="
+                + mSlotIndex
+                + '}';
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof AtomsQualifiedRatListChangedInfo)) return false;
+        AtomsQualifiedRatListChangedInfo that = (AtomsQualifiedRatListChangedInfo) o;
+        return mNetCapability == that.mNetCapability
+                && mFirstQualifiedRat == that.mFirstQualifiedRat
+                && mSecondQualifiedRat == that.mSecondQualifiedRat
+                && mCurrentTransportType == that.mCurrentTransportType
+                && mWfcEnabled == that.mWfcEnabled
+                && mWfcMode == that.mWfcMode
+                && mCellularNetworkType == that.mCellularNetworkType
+                && mIwlanNetworkType == that.mIwlanNetworkType
+                && mRestrictionsOnWwan == that.mRestrictionsOnWwan
+                && mRestrictionsOnWlan == that.mRestrictionsOnWlan
+                && mSignalStrength == that.mSignalStrength
+                && mSignalQuality == that.mSignalQuality
+                && mSignalNoise == that.mSignalNoise
+                && mIwlanSignalStrength == that.mIwlanSignalStrength
+                && mUpdateReason == that.mUpdateReason
+                && mImsCallType == that.mImsCallType
+                && mImsCallQuality == that.mImsCallQuality
+                && mSlotIndex == that.mSlotIndex;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(
+                mNetCapability,
+                mFirstQualifiedRat,
+                mSecondQualifiedRat,
+                mCurrentTransportType,
+                mWfcEnabled,
+                mWfcMode,
+                mCellularNetworkType,
+                mIwlanNetworkType,
+                mRestrictionsOnWwan,
+                mRestrictionsOnWlan,
+                mSignalStrength,
+                mSignalQuality,
+                mSignalNoise,
+                mIwlanSignalStrength,
+                mUpdateReason,
+                mImsCallType,
+                mImsCallQuality,
+                mSlotIndex);
+    }
+}
diff --git a/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/AccessNetworkEvaluatorTest.java b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/AccessNetworkEvaluatorTest.java
index 8d3883e..9c59fd7 100644
--- a/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/AccessNetworkEvaluatorTest.java
+++ b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/AccessNetworkEvaluatorTest.java
@@ -29,6 +29,7 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.isNotNull;
+import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
@@ -47,6 +48,7 @@
 import android.telephony.SignalThresholdInfo;
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
+import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.ProvisioningManager;
 
 import com.android.telephony.qns.AccessNetworkSelectionPolicy.PreCondition;
@@ -63,6 +65,7 @@
 import org.mockito.stubbing.Answer;
 
 import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -71,6 +74,7 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.IntStream;
 
 @RunWith(JUnit4.class)
 public class AccessNetworkEvaluatorTest extends QnsTest {
@@ -86,6 +90,7 @@
     private static final int EVENT_WFC_ACTIVATION_WITH_IWLAN_CONNECTION_REQUIRED = EVENT_BASE + 9;
     private static final int EVENT_IMS_REGISTRATION_STATE_CHANGED = EVENT_BASE + 10;
     private static final int EVENT_SIP_DIALOG_SESSION_STATE_CHANGED = EVENT_BASE + 12;
+    private static final int EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED = EVENT_BASE + 13;
 
     @Mock private RestrictManager mRestrictManager;
     @Mock private DataConnectionStatusTracker mDataConnectionStatusTracker;
@@ -164,6 +169,8 @@
 
         when(mMockQnsTelephonyListener.getLastQnsTelephonyInfo())
                 .thenReturn(mMockQnsTelephonyListener.new QnsTelephonyInfo());
+        when(mMockQnsTelephonyListener.getLastQnsTelephonyInfo())
+                .thenReturn(mMockQnsTelephonyListener.new QnsTelephonyInfo());
     }
 
     @After
@@ -282,6 +289,8 @@
         when(mDataConnectionStatusTracker.isActiveState()).thenReturn(true);
         when(mDataConnectionStatusTracker.getLastTransportType())
                 .thenReturn(AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+        when(mMockQnsCallStatusTracker.isCallIdle(NetworkCapabilities.NET_CAPABILITY_IMS))
+                .thenReturn(true);
         List<Integer> accessNetworks = new ArrayList<>();
         accessNetworks.add(AccessNetworkConstants.AccessNetworkType.IWLAN);
         QnsTelephonyListener.QnsTelephonyInfo info =
@@ -342,6 +351,8 @@
         when(mDataConnectionStatusTracker.isActiveState()).thenReturn(true);
         when(mDataConnectionStatusTracker.getLastTransportType())
                 .thenReturn(AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+        when(mMockQnsCallStatusTracker.isCallIdle(NetworkCapabilities.NET_CAPABILITY_IMS))
+                .thenReturn(true);
         List<Integer> accessNetworks = new ArrayList<>();
         accessNetworks.add(AccessNetworkConstants.AccessNetworkType.IWLAN);
         QnsTelephonyListener.QnsTelephonyInfo info =
@@ -375,14 +386,20 @@
         mAne.onQnsTelephonyInfoChanged(infoIms);
         mAne.updateLastNotifiedQualifiedNetwork(accessNetworks);
         mAne.onSetCallType(QnsConstants.CALL_TYPE_VOICE);
+        when(mMockQnsCallStatusTracker.isCallIdle(NetworkCapabilities.NET_CAPABILITY_IMS))
+                .thenReturn(false);
         assertTrue(mAne.needHandoverPolicyCheck());
         assertFalse(mAne.moveTransportTypeAllowed());
         mAne.updateLastNotifiedQualifiedNetwork(accessNetworks);
         mAne.onSetCallType(QnsConstants.CALL_TYPE_IDLE);
+        when(mMockQnsCallStatusTracker.isCallIdle(NetworkCapabilities.NET_CAPABILITY_IMS))
+                .thenReturn(true);
         assertTrue(mAne.needHandoverPolicyCheck());
         assertTrue(mAne.moveTransportTypeAllowed());
         mAne.updateLastNotifiedQualifiedNetwork(accessNetworks);
         mAne.onSetCallType(QnsConstants.CALL_TYPE_EMERGENCY);
+        when(mMockQnsCallStatusTracker.isCallIdle(NetworkCapabilities.NET_CAPABILITY_IMS))
+                .thenReturn(false);
         assertTrue(mAne.needHandoverPolicyCheck());
         assertFalse(mAne.moveTransportTypeAllowed());
     }
@@ -1734,4 +1751,87 @@
         assertEquals(
                 QnsConstants.CALL_TYPE_VIDEO, matchedAnsp.get(0).getPreCondition().getCallType());
     }
+
+    @Test
+    public void testDataConnectionDisconnectedOnSos_ResettingThresholds() {
+        AccessNetworkEvaluator aneSos =
+                new AccessNetworkEvaluator(
+                        mQnsComponents[mSlotIndex],
+                        NetworkCapabilities.NET_CAPABILITY_EIMS,
+                        mRestrictManager,
+                        mDataConnectionStatusTracker,
+                        mSlotIndex);
+        aneSos.registerForQualifiedNetworksChanged(mHandler, QUALIFIED_NETWORKS_CHANGED);
+        waitForLastHandlerAction(aneSos.mHandler);
+
+        aneSos.onDataConnectionStateChanged(
+                new DataConnectionStatusTracker.DataConnectionChangedInfo(
+                        DataConnectionStatusTracker.EVENT_DATA_CONNECTION_DISCONNECTED,
+                        DataConnectionStatusTracker.STATE_INACTIVE,
+                        AccessNetworkConstants.TRANSPORT_TYPE_INVALID));
+        waitForLastHandlerAction(aneSos.mHandler);
+
+        aneSos.onDataConnectionStateChanged(
+                new DataConnectionStatusTracker.DataConnectionChangedInfo(
+                        DataConnectionStatusTracker.EVENT_DATA_CONNECTION_FAILED,
+                        DataConnectionStatusTracker.STATE_INACTIVE,
+                        AccessNetworkConstants.TRANSPORT_TYPE_INVALID));
+        waitForLastHandlerAction(aneSos.mHandler);
+
+        verify(mMockWifiQm, times(2)).updateThresholdsForNetCapability(
+                NetworkCapabilities.NET_CAPABILITY_EIMS, mSlotIndex, null);
+    }
+
+    @Test
+    public void testOnImsCallDisconnectCauseChanged() {
+        // ANE takes time to build ANSP.
+        waitForLastHandlerAction(mAne.mHandler);
+        when(mRestrictManager.isRestricted(anyInt())).thenReturn(false);
+        when(mRestrictManager.isAllowedOnSingleTransport(anyInt())).thenReturn(true);
+        when(mDataConnectionStatusTracker.getLastTransportType())
+                .thenReturn(AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+
+        // make iwlan and cellular available
+        IwlanNetworkStatusTracker.IwlanAvailabilityInfo iwlanInfo =
+                mMockIwlanNetworkStatusTracker.new IwlanAvailabilityInfo(true, false);
+        mAne.onIwlanNetworkStatusChanged(iwlanInfo);
+        QnsTelephonyListener.QnsTelephonyInfo cellInfo =
+                mMockQnsTelephonyListener.new QnsTelephonyInfo();
+        cellInfo.setCellularAvailable(true);
+        cellInfo.setCoverage(true);
+        cellInfo.setDataNetworkType(TelephonyManager.NETWORK_TYPE_LTE);
+        cellInfo.setVoiceNetworkType(TelephonyManager.NETWORK_TYPE_LTE);
+        cellInfo.setDataRegState(ServiceState.STATE_IN_SERVICE);
+        mAne.onQnsTelephonyInfoChanged(cellInfo);
+        assertTrue(mAne.mIwlanAvailable);
+        assertTrue(mAne.mCellularAvailable);
+
+        // send ims call drop event.
+        ImsReasonInfo imsReasonInfo = new ImsReasonInfo(ImsReasonInfo.CODE_MEDIA_NO_DATA, 0);
+        Message.obtain(mAne.mHandler, EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED,
+                new QnsAsyncResult(null, imsReasonInfo, null)).sendToTarget();
+        waitForLastHandlerAction(mAne.mHandler);
+
+        verify(mMockQnsMetrics, atLeast(1))
+                .reportAtomForImsCallDropStats(
+                        anyInt(), anyInt(), any(), any(), any(), anyInt(), anyInt());
+    }
+
+    @Test
+    public void testEvaluateSpecificReasonToString() throws Exception {
+        Method method = AccessNetworkEvaluator.class.getDeclaredMethod(
+                "evaluateSpecificReasonToString", int.class);
+        method.setAccessible(true);
+
+        IntStream.rangeClosed(0, 4).forEach(i -> {
+            try {
+                assertTrue(((String) method.invoke(mAne, i)).startsWith(
+                        "EVALUATE_SPECIFIC_REASON_"));
+            } catch (IllegalAccessException e) {
+                throw new RuntimeException(e);
+            } catch (InvocationTargetException e) {
+                throw new RuntimeException(e);
+            }
+        });
+    }
 }
diff --git a/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/AlternativeEventListenerTest.java b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/AlternativeEventListenerTest.java
deleted file mode 100644
index f3583a3..0000000
--- a/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/AlternativeEventListenerTest.java
+++ /dev/null
@@ -1,490 +0,0 @@
-/*
- * Copyright (C) 2022 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.telephony.qns;
-
-import static org.junit.Assert.*;
-import static org.mockito.Mockito.when;
-
-import android.net.LinkProperties;
-import android.net.NetworkCapabilities;
-import android.os.Handler;
-import android.os.Message;
-import android.os.test.TestLooper;
-import android.telephony.AccessNetworkConstants;
-import android.telephony.PreciseCallState;
-import android.telephony.PreciseDataConnectionState;
-import android.telephony.TelephonyManager;
-import android.telephony.data.ApnSetting;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-@RunWith(JUnit4.class)
-public class AlternativeEventListenerTest extends QnsTest {
-
-    AlternativeEventListener mListener;
-    AltEventProvider mAltEventProvider;
-    TestLooper mTestLooper;
-    private static final int SLOT_INDEX = 0;
-    private Handler mHandler;
-    CountDownLatch mLatch;
-    int[] mThresholds;
-    // QnsTelephonyListener mQtListener;
-
-    class AltEventProvider extends AlternativeEventProvider {
-
-        AltEventProvider(AlternativeEventListener altEventListener, int slotId) {
-            super(altEventListener, slotId);
-        }
-
-        @Override
-        public void requestRtpThreshold(
-                int callId,
-                int jitter,
-                int packetLossRate,
-                int packetLossTimeInMilliSec,
-                int noRtpTimeInMilliSec) {}
-
-        @Override
-        public void setEcnoSignalThreshold(int[] threshold) {
-            if (mLatch != null) {
-                mThresholds = threshold;
-                mLatch.countDown();
-            }
-        }
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        super.setUp();
-        mTestLooper = new TestLooper();
-        Mockito.when(sMockContext.getMainLooper()).thenReturn(mTestLooper.getLooper());
-        mHandler = new Handler(mTestLooper.getLooper());
-        mListener = new AlternativeEventListener(sMockContext, mMockQnsTelephonyListener, 0);
-        mAltEventProvider = new AltEventProvider(mMockAltEventListener, SLOT_INDEX);
-        mListener.setEventProvider(mAltEventProvider);
-    }
-
-    @After
-    public void tearDown() {
-        mListener.close();
-    }
-
-    @Test
-    public void testRegisterEmergencyPreferredTransportTypeChanged() {
-        int[] expectedTransports =
-                new int[] {
-                    AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
-                    AccessNetworkConstants.TRANSPORT_TYPE_WWAN
-                };
-
-        mListener.registerEmergencyPreferredTransportTypeChanged(mHandler, 1, null);
-
-        for (int expectedTransport : expectedTransports) {
-            mAltEventProvider.notifyEmergencyPreferredTransportType(expectedTransport);
-            Message msg = mTestLooper.nextMessage();
-            assertNotNull(msg);
-            QnsAsyncResult result = (QnsAsyncResult) msg.obj;
-            assertNotNull(result.mResult);
-            int actualTransport = (int) result.mResult;
-            assertEquals(expectedTransport, actualTransport);
-        }
-    }
-
-    @Test
-    public void testUnregisterEmergencyPreferredTransportTypeChanged() {
-        mListener.registerEmergencyPreferredTransportTypeChanged(mHandler, 1, null);
-        mAltEventProvider.notifyEmergencyPreferredTransportType(
-                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
-        Message msg = mTestLooper.nextMessage();
-        assertNotNull(msg);
-
-        mListener.unregisterEmergencyPreferredTransportTypeChanged();
-        mAltEventProvider.notifyEmergencyPreferredTransportType(
-                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
-        msg = mTestLooper.nextMessage();
-        assertNull(msg);
-
-        mAltEventProvider.notifyEmergencyPreferredTransportType(
-                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
-        msg = mTestLooper.nextMessage();
-        assertNull(msg);
-    }
-
-    @Test
-    public void testRegisterTryWfcConnectionStateListener() {
-        mListener.registerTryWfcConnectionStateListener(mHandler, 1, null);
-        boolean[] expectedStates = new boolean[] {false, true, false};
-
-        for (boolean expectedState : expectedStates) {
-            mAltEventProvider.notifyTryWfcConnectionState(expectedState);
-            Message msg = mTestLooper.nextMessage();
-            assertNotNull(msg);
-            QnsAsyncResult result = (QnsAsyncResult) msg.obj;
-            assertNotNull(result.mResult);
-            boolean actualState = (boolean) result.mResult;
-            assertEquals(expectedState, actualState);
-        }
-    }
-
-    @Test
-    public void testForRtpQualityScenarios() {
-        QnsCarrierConfigManager.RtpMetricsConfig rtpConfig =
-                new QnsCarrierConfigManager.RtpMetricsConfig(0, 0, 0, 0);
-        int expectedReason = QnsConstants.RTP_LOW_QUALITY_REASON_JITTER;
-
-        // register to IMS, Emergency:
-        mListener.registerLowRtpQualityEvent(
-                NetworkCapabilities.NET_CAPABILITY_IMS, mHandler, 1, null, rtpConfig);
-        mListener.registerLowRtpQualityEvent(
-                NetworkCapabilities.NET_CAPABILITY_EIMS, mHandler, 2, null, rtpConfig);
-
-        mAltEventProvider.notifyCallInfo(
-                1, QnsConstants.CALL_TYPE_VOICE, PreciseCallState.PRECISE_CALL_STATE_ACTIVE);
-        mAltEventProvider.notifyRtpLowQuality(expectedReason);
-        Message msg = mTestLooper.nextMessage();
-        assertNotNull(msg);
-        QnsAsyncResult result = (QnsAsyncResult) msg.obj;
-        assertNotNull(result.mResult);
-        int actualReason = (int) result.mResult;
-        assertEquals(expectedReason, actualReason);
-
-        // Do not notify when call is disconnected:
-        mAltEventProvider.notifyCallInfo(
-                1, QnsConstants.CALL_TYPE_VOICE, PreciseCallState.PRECISE_CALL_STATE_DISCONNECTED);
-        mAltEventProvider.notifyRtpLowQuality(expectedReason);
-        msg = mTestLooper.nextMessage();
-        assertNull(msg);
-
-        mAltEventProvider.notifyCallInfo(
-                2, QnsConstants.CALL_TYPE_EMERGENCY, PreciseCallState.PRECISE_CALL_STATE_ACTIVE);
-        mAltEventProvider.notifyRtpLowQuality(expectedReason);
-        msg = mTestLooper.nextMessage();
-        assertNotNull(msg);
-        result = (QnsAsyncResult) msg.obj;
-        assertNotNull(result.mResult);
-        actualReason = (int) result.mResult;
-        assertEquals(expectedReason, actualReason);
-
-        // Do not notify when call is disconnected:
-        mAltEventProvider.notifyCallInfo(
-                1,
-                QnsConstants.CALL_TYPE_EMERGENCY,
-                PreciseCallState.PRECISE_CALL_STATE_DISCONNECTED);
-        mAltEventProvider.notifyRtpLowQuality(expectedReason);
-        msg = mTestLooper.nextMessage();
-        assertNull(msg);
-    }
-
-    @Test
-    public void testUnregisterLowRtpQualityEvent() {
-        QnsCarrierConfigManager.RtpMetricsConfig rtpConfig =
-                new QnsCarrierConfigManager.RtpMetricsConfig(0, 0, 0, 0);
-        int expectedReason = QnsConstants.RTP_LOW_QUALITY_REASON_JITTER;
-        mAltEventProvider.notifyCallInfo(
-                1, QnsConstants.CALL_TYPE_VOICE, PreciseCallState.PRECISE_CALL_STATE_ACTIVE);
-
-        // register to IMS, Emergency:
-        mListener.registerLowRtpQualityEvent(
-                NetworkCapabilities.NET_CAPABILITY_IMS, mHandler, 1, null, rtpConfig);
-        mListener.registerLowRtpQualityEvent(
-                NetworkCapabilities.NET_CAPABILITY_EIMS, mHandler, 2, null, rtpConfig);
-        mAltEventProvider.notifyRtpLowQuality(expectedReason);
-        Message msg = mTestLooper.nextMessage();
-        assertNotNull(msg);
-
-        // unregister for IMS
-        mListener.unregisterLowRtpQualityEvent(NetworkCapabilities.NET_CAPABILITY_IMS, mHandler);
-        mAltEventProvider.notifyRtpLowQuality(expectedReason);
-        msg = mTestLooper.nextMessage();
-        assertNull(msg);
-
-        // Event still registered for emergency, hence it should be notified:
-        mAltEventProvider.notifyCallInfo(
-                1, QnsConstants.CALL_TYPE_EMERGENCY, PreciseCallState.PRECISE_CALL_STATE_ACTIVE);
-        mAltEventProvider.notifyRtpLowQuality(expectedReason);
-        msg = mTestLooper.nextMessage();
-        assertNotNull(msg);
-
-        // unregister for Emergency:
-        mListener.unregisterLowRtpQualityEvent(NetworkCapabilities.NET_CAPABILITY_EIMS, mHandler);
-        mAltEventProvider.notifyRtpLowQuality(expectedReason);
-        msg = mTestLooper.nextMessage();
-        assertNull(msg);
-    }
-
-    @Test
-    public void testForCallTypeChangedScenarios() {
-        mListener.registerCallTypeChangedListener(
-                NetworkCapabilities.NET_CAPABILITY_IMS, null, 1, null);
-        mListener.registerCallTypeChangedListener(
-                NetworkCapabilities.NET_CAPABILITY_IMS, mHandler, 1, null);
-        mListener.registerCallTypeChangedListener(
-                NetworkCapabilities.NET_CAPABILITY_EIMS, mHandler, 1, null);
-        mListener.registerCallTypeChangedListener(
-                NetworkCapabilities.NET_CAPABILITY_MMS, mHandler, 1, null);
-
-        // Test1:
-        mAltEventProvider.notifyCallInfo(
-                1, QnsConstants.CALL_TYPE_VOICE, PreciseCallState.PRECISE_CALL_STATE_ACTIVE);
-
-        Message msg = mTestLooper.nextMessage();
-        assertNotNull(msg);
-        QnsAsyncResult result = (QnsAsyncResult) msg.obj;
-        assertNotNull(result.mResult);
-        assertEquals(QnsConstants.CALL_TYPE_VOICE, (int) result.mResult);
-        assertFalse(mListener.isIdleState()); // for IMS calls only
-
-        // Test2:
-        mAltEventProvider.notifyCallInfo(
-                1, QnsConstants.CALL_TYPE_VOICE, PreciseCallState.PRECISE_CALL_STATE_ACTIVE);
-        msg = mTestLooper.nextMessage();
-
-        // Should not notify if call type is not changed
-        assertNull(msg);
-
-        // Test3:
-        mAltEventProvider.notifyCallInfo(
-                1, QnsConstants.CALL_TYPE_VOICE, PreciseCallState.PRECISE_CALL_STATE_DISCONNECTED);
-        msg = mTestLooper.nextMessage();
-        assertNotNull(msg);
-        result = (QnsAsyncResult) msg.obj;
-        assertNotNull(result.mResult);
-        assertEquals(QnsConstants.CALL_TYPE_IDLE, (int) result.mResult);
-        assertTrue(mListener.isIdleState()); // for IMS calls only
-
-        // Test4:
-        mAltEventProvider.notifyCallInfo(
-                2, QnsConstants.CALL_TYPE_EMERGENCY, PreciseCallState.PRECISE_CALL_STATE_ACTIVE);
-        msg = mTestLooper.nextMessage();
-        assertNotNull(msg);
-        result = (QnsAsyncResult) msg.obj;
-        assertNotNull(result.mResult);
-        assertEquals(QnsConstants.CALL_TYPE_EMERGENCY, (int) result.mResult);
-
-        // Test5:
-        mAltEventProvider.notifyCallInfo(
-                2,
-                QnsConstants.CALL_TYPE_EMERGENCY,
-                PreciseCallState.PRECISE_CALL_STATE_DISCONNECTED);
-        msg = mTestLooper.nextMessage();
-        assertNotNull(msg);
-        result = (QnsAsyncResult) msg.obj;
-        assertNotNull(result.mResult);
-        assertEquals(QnsConstants.CALL_TYPE_IDLE, (int) result.mResult);
-    }
-
-    @Test
-    public void testUnregisterCallTypeChangedListener() {
-        mListener.registerCallTypeChangedListener(
-                NetworkCapabilities.NET_CAPABILITY_IMS, null, 1, null);
-        mListener.registerCallTypeChangedListener(
-                NetworkCapabilities.NET_CAPABILITY_IMS, mHandler, 1, null);
-        mListener.registerCallTypeChangedListener(
-                NetworkCapabilities.NET_CAPABILITY_EIMS, mHandler, 1, null);
-        mListener.registerCallTypeChangedListener(
-                NetworkCapabilities.NET_CAPABILITY_MMS, mHandler, 1, null);
-
-        mAltEventProvider.notifyCallInfo(
-                1, QnsConstants.CALL_TYPE_VOICE, PreciseCallState.PRECISE_CALL_STATE_ACTIVE);
-        Message msg = mTestLooper.nextMessage();
-        assertNotNull(msg);
-
-        mListener.unregisterCallTypeChangedListener(
-                NetworkCapabilities.NET_CAPABILITY_IMS, mHandler);
-        mAltEventProvider.notifyCallInfo(
-                1, QnsConstants.CALL_TYPE_VOICE, PreciseCallState.PRECISE_CALL_STATE_DISCONNECTED);
-        msg = mTestLooper.nextMessage();
-        assertNull(msg);
-
-        mAltEventProvider.notifyCallInfo(
-                1, QnsConstants.CALL_TYPE_EMERGENCY, PreciseCallState.PRECISE_CALL_STATE_ACTIVE);
-        msg = mTestLooper.nextMessage();
-        assertNotNull(msg);
-
-        mListener.unregisterCallTypeChangedListener(
-                NetworkCapabilities.NET_CAPABILITY_EIMS, mHandler);
-        mAltEventProvider.notifyCallInfo(
-                1,
-                QnsConstants.CALL_TYPE_EMERGENCY,
-                PreciseCallState.PRECISE_CALL_STATE_DISCONNECTED);
-        msg = mTestLooper.nextMessage();
-        assertNull(msg);
-    }
-
-    @Test
-    public void testEmergencyOverImsCallTypeChangedScenarios() {
-        mListener.registerCallTypeChangedListener(
-                NetworkCapabilities.NET_CAPABILITY_IMS, mHandler, 1, null);
-        PreciseDataConnectionState emergencyDataStatus =
-                new PreciseDataConnectionState.Builder()
-                        .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_INVALID)
-                        .setState(TelephonyManager.DATA_DISCONNECTED)
-                        .setNetworkType(AccessNetworkConstants.AccessNetworkType.EUTRAN)
-                        .setApnSetting(
-                                new ApnSetting.Builder()
-                                        .setApnTypeBitmask(ApnSetting.TYPE_EMERGENCY)
-                                        .setApnName("sos")
-                                        .setEntryName("sos")
-                                        .build())
-                        .setLinkProperties(new LinkProperties())
-                        .build();
-        PreciseDataConnectionState imsDataStatus =
-                new PreciseDataConnectionState.Builder()
-                        .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WLAN)
-                        .setState(TelephonyManager.DATA_CONNECTED)
-                        .setNetworkType(AccessNetworkConstants.AccessNetworkType.IWLAN)
-                        .setApnSetting(
-                                new ApnSetting.Builder()
-                                        .setApnTypeBitmask(ApnSetting.TYPE_IMS)
-                                        .setApnName("ims")
-                                        .setEntryName("ims")
-                                        .build())
-                        .build();
-
-        when(mMockQnsTelephonyListener.getLastPreciseDataConnectionState(
-                        NetworkCapabilities.NET_CAPABILITY_EIMS))
-                .thenReturn(emergencyDataStatus);
-        when(mMockQnsTelephonyListener.getLastPreciseDataConnectionState(
-                        NetworkCapabilities.NET_CAPABILITY_IMS))
-                .thenReturn(imsDataStatus);
-        // Test1:
-        mAltEventProvider.notifyCallInfo(
-                1, QnsConstants.CALL_TYPE_EMERGENCY, PreciseCallState.PRECISE_CALL_STATE_DIALING);
-
-        Message msg = mTestLooper.nextMessage();
-        assertNotNull(msg);
-        QnsAsyncResult result = (QnsAsyncResult) msg.obj;
-        assertNotNull(result.mResult);
-        assertEquals(QnsConstants.CALL_TYPE_EMERGENCY, (int) result.mResult);
-
-        // Test2:
-        mAltEventProvider.notifyCallInfo(
-                1, QnsConstants.CALL_TYPE_EMERGENCY, PreciseCallState.PRECISE_CALL_STATE_ACTIVE);
-        msg = mTestLooper.nextMessage();
-
-        // Should not notify if call type is not changed
-        assertNull(msg);
-
-        // Test3:
-        imsDataStatus =
-                new PreciseDataConnectionState.Builder()
-                        .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_INVALID)
-                        .setState(TelephonyManager.DATA_DISCONNECTED)
-                        .setNetworkType(AccessNetworkConstants.AccessNetworkType.EUTRAN)
-                        .setApnSetting(
-                                new ApnSetting.Builder()
-                                        .setApnTypeBitmask(ApnSetting.TYPE_IMS)
-                                        .setApnName("ims")
-                                        .setEntryName("ims")
-                                        .build())
-                        .build();
-        Mockito.clearInvocations(mMockQnsTelephonyListener);
-        when(mMockQnsTelephonyListener.getLastPreciseDataConnectionState(
-                        NetworkCapabilities.NET_CAPABILITY_IMS))
-                .thenReturn(imsDataStatus);
-
-        mAltEventProvider.notifyCallInfo(
-                1,
-                QnsConstants.CALL_TYPE_EMERGENCY,
-                PreciseCallState.PRECISE_CALL_STATE_DISCONNECTED);
-        msg = mTestLooper.nextMessage();
-        assertNotNull(msg);
-        result = (QnsAsyncResult) msg.obj;
-        assertNotNull(result.mResult);
-        assertEquals(QnsConstants.CALL_TYPE_IDLE, (int) result.mResult);
-
-        // Test4:
-        emergencyDataStatus =
-                new PreciseDataConnectionState.Builder()
-                        .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WLAN)
-                        .setState(TelephonyManager.DATA_CONNECTED)
-                        .setNetworkType(AccessNetworkConstants.AccessNetworkType.IWLAN)
-                        .setApnSetting(
-                                new ApnSetting.Builder()
-                                        .setApnTypeBitmask(ApnSetting.TYPE_EMERGENCY)
-                                        .setApnName("sos")
-                                        .setEntryName("sos")
-                                        .build())
-                        .build();
-        Mockito.clearInvocations(mMockQnsTelephonyListener);
-        when(mMockQnsTelephonyListener.getLastPreciseDataConnectionState(
-                        NetworkCapabilities.NET_CAPABILITY_EIMS))
-                .thenReturn(emergencyDataStatus);
-        imsDataStatus =
-                new PreciseDataConnectionState.Builder()
-                        .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WLAN)
-                        .setState(TelephonyManager.DATA_CONNECTED)
-                        .setNetworkType(AccessNetworkConstants.AccessNetworkType.IWLAN)
-                        .setApnSetting(
-                                new ApnSetting.Builder()
-                                        .setApnTypeBitmask(ApnSetting.TYPE_IMS)
-                                        .setApnName("ims")
-                                        .setEntryName("ims")
-                                        .build())
-                        .build();
-        Mockito.clearInvocations(mMockQnsTelephonyListener);
-        when(mMockQnsTelephonyListener.getLastPreciseDataConnectionState(
-                        NetworkCapabilities.NET_CAPABILITY_IMS))
-                .thenReturn(imsDataStatus);
-        mAltEventProvider.notifyCallInfo(
-                2, QnsConstants.CALL_TYPE_EMERGENCY, PreciseCallState.PRECISE_CALL_STATE_ACTIVE);
-        msg = mTestLooper.nextMessage();
-        assertNull(msg);
-    }
-
-    @Test
-    public void testOnSrvccStateChanged() {
-        mListener.registerCallTypeChangedListener(
-                NetworkCapabilities.NET_CAPABILITY_IMS, mHandler, 1, null);
-
-        mListener.onSrvccStateChanged(TelephonyManager.SRVCC_STATE_HANDOVER_COMPLETED);
-        Message msg = mTestLooper.nextMessage();
-        assertNotNull(msg);
-        QnsAsyncResult result = (QnsAsyncResult) msg.obj;
-        assertNotNull(result.mResult);
-        assertEquals(QnsConstants.CALL_TYPE_IDLE, (int) result.mResult);
-
-        mListener.onSrvccStateChanged(TelephonyManager.SRVCC_STATE_HANDOVER_STARTED);
-        msg = mTestLooper.nextMessage();
-        assertNull(msg);
-    }
-
-    @Test
-    public void isIdleState() {
-        mListener.isIdleState();
-    }
-
-    @Test
-    public void setEcnoSignalThreshold() throws InterruptedException {
-        mLatch = new CountDownLatch(1);
-        int[] ecnoThresholds = new int[] {-85, -90, -95};
-        mListener.setEcnoSignalThreshold(ecnoThresholds);
-        assertTrue(mLatch.await(100, TimeUnit.MILLISECONDS));
-        assertNotNull(mThresholds);
-        assertArrayEquals(ecnoThresholds, mThresholds);
-    }
-}
diff --git a/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/CellularQualityMonitorTest.java b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/CellularQualityMonitorTest.java
index 6ab2566..e2fbb5b 100644
--- a/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/CellularQualityMonitorTest.java
+++ b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/CellularQualityMonitorTest.java
@@ -99,7 +99,10 @@
         MockitoAnnotations.initMocks(this);
         super.setUp();
         mCellularQualityMonitor =
-                new CellularQualityMonitor(sMockContext, mMockQnsTelephonyListener, mSlotIndex);
+                new CellularQualityMonitor(sMockContext,
+                        mMockQnsConfigManager,
+                        mMockQnsTelephonyListener,
+                        mSlotIndex);
         mLatch = new CountDownLatch(1);
         mThresholdListener = new ThresholdListener(mExecutor);
 
diff --git a/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/QnsCallStatusTrackerTest.java b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/QnsCallStatusTrackerTest.java
index 9dea914..a9247f0 100644
--- a/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/QnsCallStatusTrackerTest.java
+++ b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/QnsCallStatusTrackerTest.java
@@ -24,6 +24,10 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.lenient;
 import static org.mockito.Mockito.when;
 
@@ -53,8 +57,10 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.MockitoSession;
+import org.mockito.stubbing.Answer;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 
 @RunWith(JUnit4.class)
@@ -69,6 +75,8 @@
     private Handler mLowQualityHandler;
     private MockitoSession mMockSession;
     List<CallState> mTestCallStateList = new ArrayList<>();
+    int mId = 0;
+    HashMap<Integer, Message> mMessageHashMap = new HashMap<>();
 
     @Before
     public void setUp() throws Exception {
@@ -82,8 +90,27 @@
         mImsHandler = new Handler(mTestLooperListener.getLooper());
         mEmergencyHandler = new Handler(mTestLooperListener.getLooper());
         mLowQualityHandler = new Handler(mLowQualityListenerLooper.getLooper());
+        mMessageHashMap = new HashMap<>();
+        when(mMockQnsTimer.registerTimer(isA(Message.class), anyLong())).thenAnswer(
+                (Answer<Integer>) invocation -> {
+                    Message msg = (Message) invocation.getArguments()[0];
+                    long delay = (long) invocation.getArguments()[1];
+                    msg.getTarget().sendMessageDelayed(msg, delay);
+                    mMessageHashMap.put(++mId, msg);
+                    return mId;
+                });
+
+        doAnswer(invocation -> {
+            int timerId = (int) invocation.getArguments()[0];
+            Message msg = mMessageHashMap.get(timerId);
+            if (msg != null && msg.getTarget() != null) {
+                msg.getTarget().removeMessages(msg.what, msg.obj);
+            }
+            return null;
+        }).when(mMockQnsTimer).unregisterTimer(anyInt());
         mCallTracker = new QnsCallStatusTracker(
-                mMockQnsTelephonyListener, mMockQnsConfigManager, 0, mTestLooper.getLooper());
+                mMockQnsTelephonyListener, mMockQnsConfigManager, mMockQnsTimer, 0,
+                mTestLooper.getLooper());
         mCallTracker.registerCallTypeChangedListener(
                 NetworkCapabilities.NET_CAPABILITY_IMS, mImsHandler, 1, null);
         mCallTracker.registerCallTypeChangedListener(
@@ -130,6 +157,8 @@
         assertNotNull(result.mResult);
         assertEquals(QnsConstants.CALL_TYPE_VOICE, (int) result.mResult);
         assertFalse(mCallTracker.isCallIdle());
+        assertTrue(mCallTracker.isCallIdle(NetworkCapabilities.NET_CAPABILITY_EIMS));
+        assertFalse(mCallTracker.isCallIdle(NetworkCapabilities.NET_CAPABILITY_IMS));
 
         // Test2:
         mTestCallStateList.clear();
@@ -149,7 +178,9 @@
         result = (QnsAsyncResult) msg.obj;
         assertNotNull(result.mResult);
         assertEquals(QnsConstants.CALL_TYPE_IDLE, (int) result.mResult);
-        assertTrue(mCallTracker.isCallIdle()); // for IMS calls only
+        assertTrue(mCallTracker.isCallIdle());
+        assertTrue(mCallTracker.isCallIdle(NetworkCapabilities.NET_CAPABILITY_EIMS));
+        assertTrue(mCallTracker.isCallIdle(NetworkCapabilities.NET_CAPABILITY_IMS));
     }
 
     @Test
@@ -166,6 +197,9 @@
         assertNotNull(result.mResult);
         assertEquals(QnsConstants.CALL_TYPE_VOICE, (int) result.mResult);
         assertFalse(mCallTracker.isCallIdle());
+        assertTrue(mCallTracker.isCallIdle(NetworkCapabilities.NET_CAPABILITY_EIMS));
+        assertFalse(mCallTracker.isCallIdle(NetworkCapabilities.NET_CAPABILITY_IMS));
+        assertFalse(mCallTracker.isCallIdle(NetworkCapabilities.NET_CAPABILITY_MMS));
 
         mTestCallStateList.clear();
         mTestCallStateList.add(new CallState.Builder(PreciseCallState.PRECISE_CALL_STATE_ACTIVE)
@@ -181,7 +215,8 @@
                 new CallState.Builder(PreciseCallState.PRECISE_CALL_STATE_DISCONNECTING)
                         .setImsCallType(ImsCallProfile.CALL_TYPE_VOICE)
                         .setImsCallServiceType(ImsCallProfile.SERVICE_TYPE_NORMAL).build());
-        mTestCallStateList.add(new CallState.Builder(PreciseCallState.PRECISE_CALL_STATE_ACTIVE)
+        mTestCallStateList.add(
+                new CallState.Builder(PreciseCallState.PRECISE_CALL_STATE_ACTIVE)
                 .setImsCallType(ImsCallProfile.CALL_TYPE_VOICE)
                 .setImsCallServiceType(ImsCallProfile.SERVICE_TYPE_EMERGENCY).build());
         mCallTracker.updateCallState(mTestCallStateList);
@@ -192,6 +227,8 @@
         assertNotNull(result.mResult);
         assertEquals(QnsConstants.CALL_TYPE_EMERGENCY, (int) result.mResult);
         assertFalse(mCallTracker.isCallIdle());
+        assertFalse(mCallTracker.isCallIdle(NetworkCapabilities.NET_CAPABILITY_EIMS));
+        assertFalse(mCallTracker.isCallIdle(NetworkCapabilities.NET_CAPABILITY_IMS));
 
         mTestCallStateList.clear();
         mTestCallStateList.add(new CallState.Builder(PreciseCallState.PRECISE_CALL_STATE_ACTIVE)
@@ -205,6 +242,8 @@
         assertNotNull(result.mResult);
         assertEquals(QnsConstants.CALL_TYPE_IDLE, (int) result.mResult);
         assertFalse(mCallTracker.isCallIdle());
+        assertFalse(mCallTracker.isCallIdle(NetworkCapabilities.NET_CAPABILITY_EIMS));
+        assertTrue(mCallTracker.isCallIdle(NetworkCapabilities.NET_CAPABILITY_IMS));
 
         mTestCallStateList.clear();
         mTestCallStateList.add(new CallState.Builder(PreciseCallState.PRECISE_CALL_STATE_ALERTING)
@@ -224,6 +263,8 @@
         assertNotNull(result.mResult);
         assertEquals(QnsConstants.CALL_TYPE_VOICE, (int) result.mResult);
         assertFalse(mCallTracker.isCallIdle());
+        assertTrue(mCallTracker.isCallIdle(NetworkCapabilities.NET_CAPABILITY_EIMS));
+        assertFalse(mCallTracker.isCallIdle(NetworkCapabilities.NET_CAPABILITY_IMS));
     }
 
     @Test
@@ -241,6 +282,8 @@
         assertNotNull(result.mResult);
         assertEquals(QnsConstants.CALL_TYPE_VOICE, (int) result.mResult);
         assertFalse(mCallTracker.isCallIdle()); // for IMS calls only
+        assertTrue(mCallTracker.isCallIdle(NetworkCapabilities.NET_CAPABILITY_EIMS));
+        assertFalse(mCallTracker.isCallIdle(NetworkCapabilities.NET_CAPABILITY_IMS));
 
         mTestCallStateList.clear();
         mTestCallStateList.add(new CallState.Builder(PreciseCallState.PRECISE_CALL_STATE_ACTIVE)
@@ -256,6 +299,8 @@
         assertNotNull(result.mResult);
         assertEquals(QnsConstants.CALL_TYPE_VIDEO, (int) result.mResult);
         assertFalse(mCallTracker.isCallIdle()); // for IMS calls only
+        assertTrue(mCallTracker.isCallIdle(NetworkCapabilities.NET_CAPABILITY_EIMS));
+        assertFalse(mCallTracker.isCallIdle(NetworkCapabilities.NET_CAPABILITY_IMS));
 
         mTestCallStateList.clear();
         mTestCallStateList.add(new CallState.Builder(PreciseCallState.PRECISE_CALL_STATE_ACTIVE)
@@ -268,6 +313,8 @@
         msg = mTestLooperListener.nextMessage();
         assertNull(msg);
         assertFalse(mCallTracker.isCallIdle()); // for IMS calls only
+        assertTrue(mCallTracker.isCallIdle(NetworkCapabilities.NET_CAPABILITY_EIMS));
+        assertFalse(mCallTracker.isCallIdle(NetworkCapabilities.NET_CAPABILITY_IMS));
 
         mTestCallStateList.clear();
         mTestCallStateList.add(new CallState.Builder(PreciseCallState.PRECISE_CALL_STATE_HOLDING)
@@ -281,6 +328,8 @@
         assertNotNull(result.mResult);
         assertEquals(QnsConstants.CALL_TYPE_VOICE, (int) result.mResult);
         assertFalse(mCallTracker.isCallIdle());
+        assertTrue(mCallTracker.isCallIdle(NetworkCapabilities.NET_CAPABILITY_EIMS));
+        assertFalse(mCallTracker.isCallIdle(NetworkCapabilities.NET_CAPABILITY_IMS));
 
         mTestCallStateList.clear();
         mTestCallStateList.add(new CallState.Builder(PreciseCallState.PRECISE_CALL_STATE_ACTIVE)
@@ -319,12 +368,16 @@
         assertNotNull(result.mResult);
         assertEquals(QnsConstants.CALL_TYPE_IDLE, (int) result.mResult);
         assertFalse(mCallTracker.isCallIdle());
+        assertTrue(mCallTracker.isCallIdle(NetworkCapabilities.NET_CAPABILITY_EIMS));
+        assertFalse(mCallTracker.isCallIdle(NetworkCapabilities.NET_CAPABILITY_IMS));
 
         mTestCallStateList.clear();
         mCallTracker.updateCallState(mTestCallStateList);
         msg = mTestLooperListener.nextMessage();
         assertNull(msg);
         assertTrue(mCallTracker.isCallIdle());
+        assertTrue(mCallTracker.isCallIdle(NetworkCapabilities.NET_CAPABILITY_EIMS));
+        assertTrue(mCallTracker.isCallIdle(NetworkCapabilities.NET_CAPABILITY_IMS));
     }
 
     @Test
@@ -406,6 +459,8 @@
         QnsAsyncResult result = (QnsAsyncResult) msg.obj;
         assertNotNull(result.mResult);
         assertEquals(QnsConstants.CALL_TYPE_EMERGENCY, (int) result.mResult);
+        assertFalse(mCallTracker.isCallIdle(NetworkCapabilities.NET_CAPABILITY_IMS));
+        assertTrue(mCallTracker.isCallIdle(NetworkCapabilities.NET_CAPABILITY_EIMS));
 
         // Test2:
         mTestCallStateList.clear();
@@ -417,6 +472,27 @@
         // Should not notify if call type is not changed
         assertNull(msg);
 
+        mTestCallStateList.clear();
+        mTestCallStateList.add(new CallState.Builder(PreciseCallState.PRECISE_CALL_STATE_ACTIVE)
+                .setImsCallType(ImsCallProfile.CALL_TYPE_VOICE)
+                .setImsCallServiceType(ImsCallProfile.SERVICE_TYPE_EMERGENCY).build());
+        mTestCallStateList.add(new CallState.Builder(PreciseCallState.PRECISE_CALL_STATE_WAITING)
+                .setImsCallType(ImsCallProfile.CALL_TYPE_VOICE)
+                .setImsCallServiceType(ImsCallProfile.SERVICE_TYPE_NORMAL).build());
+        mCallTracker.updateCallState(mTestCallStateList);
+        msg = mTestLooperListener.nextMessage();
+        // Should not notify if call type is not changed
+        assertNull(msg);
+
+        mTestCallStateList.clear();
+        mTestCallStateList.add(new CallState.Builder(PreciseCallState.PRECISE_CALL_STATE_ACTIVE)
+                .setImsCallType(ImsCallProfile.CALL_TYPE_VOICE)
+                .setImsCallServiceType(ImsCallProfile.SERVICE_TYPE_EMERGENCY).build());
+        mCallTracker.updateCallState(mTestCallStateList);
+        msg = mTestLooperListener.nextMessage();
+        // Should not notify if call type is not changed
+        assertNull(msg);
+
         // Test3:
         imsDataStatus =
                 new PreciseDataConnectionState.Builder()
diff --git a/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/QnsCarrierConfigManagerTest.java b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/QnsCarrierConfigManagerTest.java
index fb3bd67..f39ea40 100644
--- a/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/QnsCarrierConfigManagerTest.java
+++ b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/QnsCarrierConfigManagerTest.java
@@ -16,16 +16,16 @@
 
 package com.android.telephony.qns;
 
-import static android.hardware.radio.network.SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP;
 import static android.hardware.radio.network.SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ;
 import static android.hardware.radio.network.SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI;
 import static android.hardware.radio.network.SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSNR;
-import static android.hardware.radio.network.SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP;
 import static android.hardware.radio.network.SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRQ;
 import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
 import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WLAN;
 import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
+import static android.telephony.SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP;
 import static android.telephony.SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP;
+import static android.telephony.SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP;
 import static android.telephony.SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR;
 import static android.telephony.SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_UNKNOWN;
 
@@ -467,6 +467,90 @@
     }
 
     @Test
+    public void testGetWwanHysteresisDbLevelWithTestbundle() {
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putStringArray(
+                QnsCarrierConfigManager.KEY_QNS_CELLULAR_SIGNAL_STRENGTH_HYSTERESIS_DB_STRING_ARRAY,
+                new String[] {"eutran:rsrp:1"});
+        mConfigManager.loadQnsAneSupportConfigurations(bundle, null);
+        Assert.assertEquals(
+                1,
+                mConfigManager.getWwanHysteresisDbLevel(
+                        AccessNetworkConstants.AccessNetworkType.EUTRAN,
+                        SIGNAL_MEASUREMENT_TYPE_RSRP));
+
+        bundle.putStringArray(
+                QnsCarrierConfigManager.KEY_QNS_CELLULAR_SIGNAL_STRENGTH_HYSTERESIS_DB_STRING_ARRAY,
+                new String[] {"ngran:ssrsrp:2"});
+        mConfigManager.loadQnsAneSupportConfigurations(bundle, null);
+        Assert.assertEquals(
+                2,
+                mConfigManager.getWwanHysteresisDbLevel(
+                        AccessNetworkConstants.AccessNetworkType.NGRAN,
+                        SIGNAL_MEASUREMENT_TYPE_SSRSRP));
+
+        bundle.putStringArray(
+                QnsCarrierConfigManager.KEY_QNS_CELLULAR_SIGNAL_STRENGTH_HYSTERESIS_DB_STRING_ARRAY,
+                new String[] {"eutran:rsrp:-5"});
+        mConfigManager.loadQnsAneSupportConfigurations(bundle, null);
+        Assert.assertEquals(
+                QnsConstants.KEY_DEFAULT_VALUE,
+                mConfigManager.getWwanHysteresisDbLevel(
+                        AccessNetworkConstants.AccessNetworkType.EUTRAN,
+                        SIGNAL_MEASUREMENT_TYPE_RSRP));
+
+        bundle.putStringArray(
+                QnsCarrierConfigManager.KEY_QNS_CELLULAR_SIGNAL_STRENGTH_HYSTERESIS_DB_STRING_ARRAY,
+                new String[] {"utran:rscp:5"});
+        mConfigManager.loadQnsAneSupportConfigurations(bundle, null);
+        Assert.assertEquals(
+                5,
+                mConfigManager.getWwanHysteresisDbLevel(
+                        AccessNetworkConstants.AccessNetworkType.UTRAN,
+                        SIGNAL_MEASUREMENT_TYPE_RSCP));
+
+        bundle.putStringArray(
+                QnsCarrierConfigManager.KEY_QNS_CELLULAR_SIGNAL_STRENGTH_HYSTERESIS_DB_STRING_ARRAY,
+                new String[] {"eutran::-2"});
+        mConfigManager.loadQnsAneSupportConfigurations(bundle, null);
+        Assert.assertEquals(
+                QnsConstants.KEY_DEFAULT_VALUE,
+                mConfigManager.getWwanHysteresisDbLevel(
+                        AccessNetworkConstants.AccessNetworkType.EUTRAN,
+                        SIGNAL_MEASUREMENT_TYPE_RSRP));
+
+        bundle.putStringArray(
+                QnsCarrierConfigManager.KEY_QNS_CELLULAR_SIGNAL_STRENGTH_HYSTERESIS_DB_STRING_ARRAY,
+                new String[] {":rsrp:-2"});
+        mConfigManager.loadQnsAneSupportConfigurations(bundle, null);
+        Assert.assertEquals(
+                QnsConstants.KEY_DEFAULT_VALUE,
+                mConfigManager.getWwanHysteresisDbLevel(
+                        AccessNetworkConstants.AccessNetworkType.EUTRAN,
+                        SIGNAL_MEASUREMENT_TYPE_RSRP));
+
+        bundle.putStringArray(
+                QnsCarrierConfigManager.KEY_QNS_CELLULAR_SIGNAL_STRENGTH_HYSTERESIS_DB_STRING_ARRAY,
+                new String[] {""});
+        mConfigManager.loadQnsAneSupportConfigurations(bundle, null);
+        Assert.assertEquals(
+                QnsConstants.KEY_DEFAULT_VALUE,
+                mConfigManager.getWwanHysteresisDbLevel(
+                        AccessNetworkConstants.AccessNetworkType.EUTRAN,
+                        SIGNAL_MEASUREMENT_TYPE_RSRP));
+
+        bundle.putStringArray(
+                QnsCarrierConfigManager.KEY_QNS_CELLULAR_SIGNAL_STRENGTH_HYSTERESIS_DB_STRING_ARRAY,
+                new String[] {":"});
+        mConfigManager.loadQnsAneSupportConfigurations(bundle, null);
+        Assert.assertEquals(
+                QnsConstants.KEY_DEFAULT_VALUE,
+                mConfigManager.getWwanHysteresisDbLevel(
+                        AccessNetworkConstants.AccessNetworkType.EUTRAN,
+                        SIGNAL_MEASUREMENT_TYPE_RSRP));
+    }
+
+    @Test
     public void testGetQnsSupportedNetCapabilitiesWithDefaultValues() {
         List<Integer> imsNetCapability = new ArrayList<>();
         imsNetCapability.add(NetworkCapabilities.NET_CAPABILITY_IMS);
diff --git a/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/QnsComponentsTest.java b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/QnsComponentsTest.java
index 9924331..73a5249 100644
--- a/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/QnsComponentsTest.java
+++ b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/QnsComponentsTest.java
@@ -42,7 +42,6 @@
 
         assertNull(qnsComponents.getQnsTelephonyListener(slotId));
         assertNull(qnsComponents.getQnsImsManager(slotId));
-        assertNull(qnsComponents.getAlternativeEventListener(slotId));
         assertNull(qnsComponents.getCellularNetworkStatusTracker(slotId));
         assertNull(qnsComponents.getCellularQualityMonitor(slotId));
         assertNull(qnsComponents.getQnsProvisioningListener(slotId));
@@ -52,12 +51,12 @@
         assertNull(qnsComponents.getWifiBackhaulMonitor(slotId));
         assertNull(qnsComponents.getWifiQualityMonitor());
         assertNull(qnsComponents.getIwlanNetworkStatusTracker());
+        assertNull(qnsComponents.getQnsTimer());
 
         qnsComponents.createQnsComponents(slotId);
 
         assertNotNull(qnsComponents.getQnsTelephonyListener(slotId));
         assertNotNull(qnsComponents.getQnsImsManager(slotId));
-        assertNotNull(qnsComponents.getAlternativeEventListener(slotId));
         assertNotNull(qnsComponents.getCellularNetworkStatusTracker(slotId));
         assertNotNull(qnsComponents.getCellularQualityMonitor(slotId));
         assertNotNull(qnsComponents.getQnsProvisioningListener(slotId));
@@ -67,6 +66,7 @@
         assertNotNull(qnsComponents.getWifiBackhaulMonitor(slotId));
         assertNotNull(qnsComponents.getWifiQualityMonitor());
         assertNotNull(qnsComponents.getIwlanNetworkStatusTracker());
+        assertNotNull(qnsComponents.getQnsTimer());
     }
 
 
@@ -75,7 +75,6 @@
         int slotId = 0;
         QnsComponents qnsComponents = new QnsComponents(
                 sMockContext,
-                mMockAltEventListener,
                 mMockCellNetStatusTracker,
                 mMockCellularQm,
                 mMockIwlanNetworkStatusTracker,
@@ -85,13 +84,14 @@
                 mMockQnsProvisioningListener,
                 mMockQnsTelephonyListener,
                 mMockQnsCallStatusTracker,
+                mMockQnsTimer,
                 mMockWifiBm,
                 mMockWifiQm,
+                mMockQnsMetrics,
                 slotId);
 
         assertNotNull(qnsComponents.getQnsTelephonyListener(slotId));
         assertNotNull(qnsComponents.getQnsImsManager(slotId));
-        assertNotNull(qnsComponents.getAlternativeEventListener(slotId));
         assertNotNull(qnsComponents.getCellularNetworkStatusTracker(slotId));
         assertNotNull(qnsComponents.getCellularQualityMonitor(slotId));
         assertNotNull(qnsComponents.getQnsProvisioningListener(slotId));
@@ -101,12 +101,13 @@
         assertNotNull(qnsComponents.getWifiBackhaulMonitor(slotId));
         assertNotNull(qnsComponents.getWifiQualityMonitor());
         assertNotNull(qnsComponents.getIwlanNetworkStatusTracker());
+        assertNotNull(qnsComponents.getQnsTimer());
+        assertNotNull(qnsComponents.getQnsMetrics());
 
         qnsComponents.closeComponents(slotId);
 
         assertNull(qnsComponents.getQnsTelephonyListener(slotId));
         assertNull(qnsComponents.getQnsImsManager(slotId));
-        assertNull(qnsComponents.getAlternativeEventListener(slotId));
         assertNull(qnsComponents.getCellularNetworkStatusTracker(slotId));
         assertNull(qnsComponents.getCellularQualityMonitor(slotId));
         assertNull(qnsComponents.getQnsProvisioningListener(slotId));
@@ -116,10 +117,11 @@
         assertNull(qnsComponents.getWifiBackhaulMonitor(slotId));
         assertNull(qnsComponents.getWifiQualityMonitor());
         assertNull(qnsComponents.getIwlanNetworkStatusTracker());
+        assertNull(qnsComponents.getQnsTimer());
+        assertNull(qnsComponents.getQnsMetrics());
 
         verify(mMockQnsTelephonyListener).close();
         verify(mMockQnsImsManager).close();
-        verify(mMockAltEventListener).close();
         verify(mMockCellNetStatusTracker).close();
         verify(mMockCellularQm).close();
         verify(mMockQnsProvisioningListener).close();
@@ -129,5 +131,7 @@
         verify(mMockWifiBm).close();
         verify(mMockWifiQm).close();
         verify(mMockIwlanNetworkStatusTracker).close();
+        verify(mMockQnsTimer).close();
+        verify(mMockQnsMetrics).close();
     }
 }
diff --git a/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/QnsMetricsTest.java b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/QnsMetricsTest.java
new file mode 100644
index 0000000..e7d1d6f
--- /dev/null
+++ b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/QnsMetricsTest.java
@@ -0,0 +1,773 @@
+/*
+ * Copyright (C) 2022 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.telephony.qns;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.net.NetworkCapabilities;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.qns.QnsProtoEnums;
+
+import com.android.telephony.qns.DataConnectionStatusTracker.DataConnectionChangedInfo;
+import com.android.telephony.qns.QualifiedNetworksServiceImpl.QualifiedNetworksInfo;
+import com.android.telephony.qns.atoms.AtomsQnsFallbackRestrictionChangedInfo;
+import com.android.telephony.qns.atoms.AtomsQnsHandoverPingPongInfo;
+import com.android.telephony.qns.atoms.AtomsQnsHandoverTimeMillisInfo;
+import com.android.telephony.qns.atoms.AtomsQnsImsCallDropStats;
+import com.android.telephony.qns.atoms.AtomsQnsRatPreferenceMismatchInfo;
+import com.android.telephony.qns.atoms.AtomsQualifiedRatListChangedInfo;
+import com.android.telephony.statslib.AtomsPulled;
+import com.android.telephony.statslib.AtomsPushed;
+import com.android.telephony.statslib.StatsLib;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.stubbing.Answer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class QnsMetricsTest extends QnsTest {
+    private static final int DEFAULT_CARRIER_ID = 1;
+
+    private final int mSlotId = 0;
+    private final int mNetCapability = NetworkCapabilities.NET_CAPABILITY_IMS;
+
+    @Mock private StatsLib mMockStatsLib;
+    @Mock private RestrictManager mMockRestrictManager;
+    @Mock private QualityMonitor mMockCellularQualityMonitor;
+    @Mock private QualityMonitor mMockWifiQualityMonitor;
+
+    private MockitoSession mStaticMockSession;
+    private QnsMetrics mQnsMetrics;
+    private long mSystemElapsedRealTime;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mStaticMockSession = mockitoSession().mockStatic(QnsUtils.class).startMocking();
+
+        mSystemElapsedRealTime = 0L;
+        lenient()
+                .when(QnsUtils.getSystemElapsedRealTime())
+                .thenAnswer((Answer<Long>) invocation -> mSystemElapsedRealTime);
+
+        mQnsMetrics = new QnsMetrics(mMockStatsLib);
+    }
+
+    @After
+    public void cleanUp() {
+        mStaticMockSession.finishMocking();
+    }
+
+    private void spendSystemTime(long appendElapsedTime) {
+        mSystemElapsedRealTime += appendElapsedTime;
+    }
+
+    private void sendDataConnectionMessage(int event, int state, int transportType) {
+        DataConnectionChangedInfo info = new DataConnectionChangedInfo(event, state, transportType);
+        mQnsMetrics.log("QnsMetricsTest currentTime:" + mSystemElapsedRealTime + "ms " + info);
+        mQnsMetrics.reportAtomForDataConnectionChanged(
+                mNetCapability, mSlotId, info, DEFAULT_CARRIER_ID);
+        waitForLastHandlerAction(mQnsMetrics.getHandler());
+    }
+
+    private void sendQualifiedNetworksMessage(int accessNetworkType) {
+
+        List<Integer> list = new ArrayList<>();
+        list.add(accessNetworkType);
+
+        sendQualifiedNetworksMessage(
+                list,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                QnsConstants.COVERAGE_HOME,
+                true,
+                false,
+                QnsConstants.CELL_PREF,
+                QnsConstants.WIFI_PREF,
+                AccessNetworkConstants.AccessNetworkType.EUTRAN,
+                true,
+                false,
+                mMockRestrictManager,
+                mMockCellularQualityMonitor,
+                mMockWifiQualityMonitor,
+                QnsConstants.CALL_TYPE_IDLE);
+    }
+
+    private void sendQualifiedNetworksMessage(
+            List<Integer> accessNetworkTypes,
+            int dataConnectionCurrentTransportType,
+            int coverage,
+            boolean settingWfcEnabled,
+            boolean settingWfcRoamingEnabled,
+            int settingWfcMode,
+            int settingWfcRoamingMode,
+            int cellularAccessNetworkType,
+            boolean iwlanAvailable,
+            boolean isCrossWfc,
+            RestrictManager restrictManager,
+            QualityMonitor cellularQualityMonitor,
+            QualityMonitor wifiQualityMonitor,
+            int callType) {
+        QualifiedNetworksInfo info = new QualifiedNetworksInfo(
+                NetworkCapabilities.NET_CAPABILITY_IMS, accessNetworkTypes);
+
+        mQnsMetrics.log("QnsMetricsTest currentTime:" + mSystemElapsedRealTime + "ms " + info);
+        mQnsMetrics.reportAtomForQualifiedNetworks(
+                info,
+                mSlotId,
+                dataConnectionCurrentTransportType,
+                coverage,
+                settingWfcEnabled,
+                settingWfcRoamingEnabled,
+                settingWfcMode,
+                settingWfcRoamingMode,
+                cellularAccessNetworkType,
+                iwlanAvailable,
+                isCrossWfc,
+                restrictManager,
+                cellularQualityMonitor,
+                wifiQualityMonitor,
+                callType);
+        waitForLastHandlerAction(mQnsMetrics.getHandler());
+    }
+
+    @Test
+    public void testAtomsQnsHandoverTimeMillisInfo() {
+
+        ArgumentCaptor<AtomsPulled> capturePulled = ArgumentCaptor.forClass(AtomsPulled.class);
+        ArgumentCaptor<AtomsPushed> capturePushed = ArgumentCaptor.forClass(AtomsPushed.class);
+
+        spendSystemTime(10000L);
+        sendQualifiedNetworksMessage(AccessNetworkConstants.AccessNetworkType.EUTRAN);
+
+        spendSystemTime(4010L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_STARTED,
+                DataConnectionStatusTracker.STATE_CONNECTING,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        spendSystemTime(4020L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_CONNECTED,
+                DataConnectionStatusTracker.STATE_CONNECTED,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        spendSystemTime(501L);
+        sendQualifiedNetworksMessage(AccessNetworkConstants.AccessNetworkType.IWLAN);
+
+        spendSystemTime(4030L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_STARTED,
+                DataConnectionStatusTracker.STATE_HANDOVER,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        spendSystemTime(4040L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_SUCCESS,
+                DataConnectionStatusTracker.STATE_CONNECTED,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+
+        spendSystemTime(5002L);
+        sendQualifiedNetworksMessage(AccessNetworkConstants.AccessNetworkType.EUTRAN);
+
+        spendSystemTime(4050L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_STARTED,
+                DataConnectionStatusTracker.STATE_HANDOVER,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+
+        spendSystemTime(4060L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_SUCCESS,
+                DataConnectionStatusTracker.STATE_CONNECTED,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        spendSystemTime(50002L);
+        sendQualifiedNetworksMessage(AccessNetworkConstants.AccessNetworkType.IWLAN);
+
+        spendSystemTime(4070L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_STARTED,
+                DataConnectionStatusTracker.STATE_HANDOVER,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        spendSystemTime(4080L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_FAILED,
+                DataConnectionStatusTracker.STATE_CONNECTED,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        spendSystemTime(4090L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_STARTED,
+                DataConnectionStatusTracker.STATE_HANDOVER,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        spendSystemTime(4100L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_FAILED,
+                DataConnectionStatusTracker.STATE_CONNECTED,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        spendSystemTime(4110L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_STARTED,
+                DataConnectionStatusTracker.STATE_HANDOVER,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        spendSystemTime(4120L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_SUCCESS,
+                DataConnectionStatusTracker.STATE_CONNECTED,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+
+        spendSystemTime(4130L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_DISCONNECTED,
+                DataConnectionStatusTracker.STATE_INACTIVE,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+
+        verify(mMockStatsLib, timeout(1000).times(4)).append(capturePulled.capture());
+        verify(mMockStatsLib, timeout(1000).times(4)).write(capturePushed.capture());
+        List<AtomsQnsHandoverTimeMillisInfo> listHandoverTime = new ArrayList<>();
+        List<AtomsQualifiedRatListChangedInfo> listQualifiedRat = new ArrayList<>();
+        for (AtomsPulled pulled : capturePulled.getAllValues()) {
+            if (pulled instanceof AtomsQnsHandoverTimeMillisInfo) {
+                listHandoverTime.add((AtomsQnsHandoverTimeMillisInfo) pulled);
+                mQnsMetrics.log("QnsMetricsTest HandoverTime atom:" + pulled);
+            }
+        }
+        for (AtomsPushed pushed : capturePushed.getAllValues()) {
+            if (pushed instanceof AtomsQualifiedRatListChangedInfo) {
+                listQualifiedRat.add((AtomsQualifiedRatListChangedInfo) pushed);
+                mQnsMetrics.log("QnsMetricsTest QualifiedRat atom:" + pushed);
+            }
+        }
+        assertEquals(3, listHandoverTime.size());
+        assertEquals(8070, listHandoverTime.get(0).getTimeForHoSuccess());
+        assertEquals(8110, listHandoverTime.get(1).getTimeForHoSuccess());
+        assertEquals(24570, listHandoverTime.get(2).getTimeForHoSuccess());
+
+        assertEquals(
+                AccessNetworkConstants.AccessNetworkType.EUTRAN,
+                listQualifiedRat.get(0).getFirstQualifiedRat());
+        assertEquals(
+                AccessNetworkConstants.AccessNetworkType.IWLAN,
+                listQualifiedRat.get(1).getFirstQualifiedRat());
+        assertEquals(
+                AccessNetworkConstants.AccessNetworkType.EUTRAN,
+                listQualifiedRat.get(2).getFirstQualifiedRat());
+        assertEquals(
+                AccessNetworkConstants.AccessNetworkType.IWLAN,
+                listQualifiedRat.get(3).getFirstQualifiedRat());
+    }
+
+    @Test
+    public void testAtomsQualifiedRatListChangedInfo() {
+
+        ArgumentCaptor<AtomsQualifiedRatListChangedInfo> capture =
+                ArgumentCaptor.forClass(AtomsQualifiedRatListChangedInfo.class);
+
+        doReturn(true)
+                .when(mMockRestrictManager)
+                .hasRestrictionType(anyInt(), eq(RestrictManager.RESTRICT_TYPE_RTP_LOW_QUALITY));
+        doReturn(true)
+                .when(mMockRestrictManager)
+                .hasRestrictionType(
+                        anyInt(), eq(RestrictManager.RESTRICT_TYPE_FALLBACK_TO_WWAN_IMS_REGI_FAIL));
+        doReturn(true)
+                .when(mMockRestrictManager)
+                .hasRestrictionType(
+                        anyInt(),
+                        eq(RestrictManager.RESTRICT_TYPE_FALLBACK_TO_WWAN_RTT_BACKHAUL_FAIL));
+
+        List<Integer> cellularAccessNetwork = new ArrayList<>();
+        cellularAccessNetwork.add(AccessNetworkConstants.AccessNetworkType.EUTRAN);
+        sendQualifiedNetworksMessage(
+                cellularAccessNetwork,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                QnsConstants.COVERAGE_HOME,
+                true,
+                false,
+                QnsConstants.CELL_PREF,
+                QnsConstants.WIFI_PREF,
+                AccessNetworkConstants.AccessNetworkType.EUTRAN,
+                false,
+                true,
+                mMockRestrictManager,
+                mMockCellularQualityMonitor,
+                mMockWifiQualityMonitor,
+                QnsConstants.CALL_TYPE_IDLE);
+
+        List<Integer> wifiAccessNetwork = new ArrayList<>();
+        wifiAccessNetwork.add(AccessNetworkConstants.AccessNetworkType.IWLAN);
+        sendQualifiedNetworksMessage(
+                wifiAccessNetwork,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
+                QnsConstants.COVERAGE_ROAM,
+                false,
+                true,
+                QnsConstants.WIFI_PREF,
+                QnsConstants.CELL_PREF,
+                AccessNetworkConstants.AccessNetworkType.NGRAN,
+                true,
+                false,
+                mMockRestrictManager,
+                mMockCellularQualityMonitor,
+                mMockWifiQualityMonitor,
+                QnsConstants.CALL_TYPE_VOICE);
+
+        verify(mMockStatsLib, timeout(1000).times(2)).write(capture.capture());
+        List<AtomsQualifiedRatListChangedInfo> list = capture.getAllValues();
+        mQnsMetrics.log("QnsMetricsTest QualifiedRat atom[0]:" + list.get(0));
+        mQnsMetrics.log("QnsMetricsTest QualifiedRat atom[1]:" + list.get(1));
+        assertEquals(
+                AccessNetworkConstants.AccessNetworkType.EUTRAN,
+                list.get(0).getFirstQualifiedRat());
+        assertEquals(
+                AccessNetworkConstants.AccessNetworkType.IWLAN, list.get(1).getFirstQualifiedRat());
+        int expectedRestriction =
+                QnsProtoEnums.RESTRICT_TYPE_RTP_LOW_QUALITY
+                        | QnsProtoEnums.RESTRICT_TYPE_FALLBACK_TO_WWAN_IMS_REGI_FAIL
+                        | QnsProtoEnums.RESTRICT_TYPE_FALLBACK_TO_WWAN_RTT_BACKHAUL_FAIL;
+        assertEquals(expectedRestriction, list.get(0).getRestrictionsOnWlan());
+        assertEquals(expectedRestriction, list.get(1).getRestrictionsOnWwan());
+    }
+
+    @Test
+    public void testAtomsQnsHandoverPingPongInfo() {
+
+        ArgumentCaptor<AtomsQnsHandoverPingPongInfo> capture =
+                ArgumentCaptor.forClass(AtomsQnsHandoverPingPongInfo.class);
+
+        spendSystemTime(4000L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_STARTED,
+                DataConnectionStatusTracker.STATE_CONNECTING,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        spendSystemTime(4000L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_CONNECTED,
+                DataConnectionStatusTracker.STATE_CONNECTED,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        spendSystemTime(4000L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_STARTED,
+                DataConnectionStatusTracker.STATE_HANDOVER,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        spendSystemTime(4000L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_SUCCESS,
+                DataConnectionStatusTracker.STATE_CONNECTED,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+
+        spendSystemTime(4000L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_STARTED,
+                DataConnectionStatusTracker.STATE_HANDOVER,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+
+        spendSystemTime(4000L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_SUCCESS,
+                DataConnectionStatusTracker.STATE_CONNECTED,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        spendSystemTime(4000L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_STARTED,
+                DataConnectionStatusTracker.STATE_HANDOVER,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        spendSystemTime(4000L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_SUCCESS,
+                DataConnectionStatusTracker.STATE_CONNECTED,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+
+        spendSystemTime(4000L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_STARTED,
+                DataConnectionStatusTracker.STATE_HANDOVER,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+
+        spendSystemTime(4000L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_SUCCESS,
+                DataConnectionStatusTracker.STATE_CONNECTED,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        spendSystemTime(4000L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_STARTED,
+                DataConnectionStatusTracker.STATE_HANDOVER,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        spendSystemTime(50000L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_SUCCESS,
+                DataConnectionStatusTracker.STATE_CONNECTED,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+
+        spendSystemTime(4000L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_STARTED,
+                DataConnectionStatusTracker.STATE_HANDOVER,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+
+        spendSystemTime(4000L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_SUCCESS,
+                DataConnectionStatusTracker.STATE_CONNECTED,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        spendSystemTime(4000L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_DISCONNECTED,
+                DataConnectionStatusTracker.STATE_INACTIVE,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        verify(mMockStatsLib, timeout(1000).times(2)).append(capture.capture());
+        List<AtomsQnsHandoverPingPongInfo> list = capture.getAllValues();
+        mQnsMetrics.log("QnsMetricsTest PingPong atom[0]:" + list.get(0));
+        mQnsMetrics.log("QnsMetricsTest PingPong atom[1]:" + list.get(1));
+        assertEquals(2, list.get(0).getCountHandoverPingPong());
+        assertEquals(1, list.get(1).getCountHandoverPingPong());
+    }
+
+    @Test
+    public void testAtomsQnsRatPreferenceMismatchInfo() {
+
+        ArgumentCaptor<AtomsQnsRatPreferenceMismatchInfo> capture =
+                ArgumentCaptor.forClass(AtomsQnsRatPreferenceMismatchInfo.class);
+
+        spendSystemTime(4000L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_STARTED,
+                DataConnectionStatusTracker.STATE_CONNECTING,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        spendSystemTime(4000L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_CONNECTED,
+                DataConnectionStatusTracker.STATE_CONNECTED,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        spendSystemTime(4000L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_STARTED,
+                DataConnectionStatusTracker.STATE_HANDOVER,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        spendSystemTime(4100L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_FAILED,
+                DataConnectionStatusTracker.STATE_CONNECTED,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        spendSystemTime(4000L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_STARTED,
+                DataConnectionStatusTracker.STATE_HANDOVER,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        spendSystemTime(4200L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_FAILED,
+                DataConnectionStatusTracker.STATE_CONNECTED,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        spendSystemTime(4000L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_STARTED,
+                DataConnectionStatusTracker.STATE_HANDOVER,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        spendSystemTime(4000L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_DISCONNECTED,
+                DataConnectionStatusTracker.STATE_INACTIVE,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        spendSystemTime(50000L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_STARTED,
+                DataConnectionStatusTracker.STATE_CONNECTING,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        spendSystemTime(4000L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_CONNECTED,
+                DataConnectionStatusTracker.STATE_CONNECTED,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        spendSystemTime(4000L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_STARTED,
+                DataConnectionStatusTracker.STATE_HANDOVER,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        spendSystemTime(4000L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_SUCCESS,
+                DataConnectionStatusTracker.STATE_CONNECTED,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+
+        spendSystemTime(4000L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_STARTED,
+                DataConnectionStatusTracker.STATE_HANDOVER,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+
+        spendSystemTime(4100L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_FAILED,
+                DataConnectionStatusTracker.STATE_CONNECTED,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+
+        spendSystemTime(4000L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_STARTED,
+                DataConnectionStatusTracker.STATE_HANDOVER,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+
+        spendSystemTime(4200L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_FAILED,
+                DataConnectionStatusTracker.STATE_CONNECTED,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+
+        spendSystemTime(4000L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_STARTED,
+                DataConnectionStatusTracker.STATE_HANDOVER,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+
+        spendSystemTime(4300L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_HANDOVER_FAILED,
+                DataConnectionStatusTracker.STATE_CONNECTED,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+
+        spendSystemTime(4000L);
+        sendDataConnectionMessage(
+                DataConnectionStatusTracker.EVENT_DATA_CONNECTION_DISCONNECTED,
+                DataConnectionStatusTracker.STATE_INACTIVE,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        verify(mMockStatsLib, timeout(1000).times(2)).append(capture.capture());
+        List<AtomsQnsRatPreferenceMismatchInfo> list = capture.getAllValues();
+        mQnsMetrics.log("QnsMetricsTest RatMismatch atom[0]:" + list.get(0));
+        mQnsMetrics.log("QnsMetricsTest RatMismatch atom[1]:" + list.get(1));
+        assertEquals(2, list.get(0).getHandoverFailCount());
+        assertEquals(3, list.get(1).getHandoverFailCount());
+        assertEquals(20300, list.get(0).getDurationOfMismatch());
+        assertEquals(24600, list.get(1).getDurationOfMismatch());
+    }
+
+    private void sendCallTypeChanged(
+            int oldCallType,
+            int newCallType,
+            RestrictManager restrictManager,
+            int transportTypeOfCall) {
+        mQnsMetrics.log(
+                "QnsMetricsTest callTypeChanged "
+                        + QnsConstants.callTypeToString(oldCallType)
+                        + "->"
+                        + QnsConstants.callTypeToString(newCallType)
+                        + " transportType:"
+                        + AccessNetworkConstants.transportTypeToString(transportTypeOfCall));
+        mQnsMetrics.reportAtomForCallTypeChanged(mNetCapability, mSlotId,
+                oldCallType, newCallType, restrictManager, transportTypeOfCall);
+        waitForLastHandlerAction(mQnsMetrics.getHandler());
+    }
+
+    private void sendImsCallDropStats(int transportTypeOfCall, int cellularAccessNetworkType) {
+        mQnsMetrics.log(
+                "QnsMetricsTest ImsCallDropStats transportTypeOfCall:"
+                        + AccessNetworkConstants.transportTypeToString(transportTypeOfCall)
+                        + " cellularAccessNetworkType:"
+                        + AccessNetworkConstants.AccessNetworkType.toString(
+                                cellularAccessNetworkType));
+        mQnsMetrics.reportAtomForImsCallDropStats(mNetCapability, mSlotId, mMockRestrictManager,
+                mMockCellularQualityMonitor, mMockWifiQualityMonitor, transportTypeOfCall,
+                cellularAccessNetworkType);
+        waitForLastHandlerAction(mQnsMetrics.getHandler());
+    }
+    private void sendFallbackRestrictionChanged(
+            List<Integer> wlanRestrictions, List<Integer> wwanRestrictions) {
+        mQnsMetrics.log(
+                "QnsMetricsTest FallbackRestrictionChanged wlanRestrictions:"
+                        + wlanRestrictions
+                        + ", wwanRestrictions:"
+                        + wwanRestrictions);
+        mQnsMetrics.reportAtomForRestrictions(
+                mNetCapability, mSlotId, wlanRestrictions, wwanRestrictions, DEFAULT_CARRIER_ID);
+        waitForLastHandlerAction(mQnsMetrics.getHandler());
+    }
+
+    @Test
+    public void testAtomsQnsImsCallDropStats() {
+
+        ArgumentCaptor<AtomsQnsImsCallDropStats> capture =
+                ArgumentCaptor.forClass(AtomsQnsImsCallDropStats.class);
+
+        // call start
+        sendCallTypeChanged(
+                QnsConstants.CALL_TYPE_IDLE,
+                QnsConstants.CALL_TYPE_VOICE,
+                mMockRestrictManager,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        // got rtp low from wwan side
+        spendSystemTime(10000L);
+        doReturn(true)
+                .when(mMockRestrictManager)
+                .hasRestrictionType(
+                        eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN),
+                        eq(RestrictManager.RESTRICT_TYPE_RTP_LOW_QUALITY));
+
+        // call end
+        spendSystemTime(3000L);
+        sendCallTypeChanged(
+                QnsConstants.CALL_TYPE_VOICE,
+                QnsConstants.CALL_TYPE_IDLE,
+                mMockRestrictManager,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        // media no data cause.
+        spendSystemTime(3000L);
+        sendImsCallDropStats(
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                AccessNetworkConstants.AccessNetworkType.EUTRAN);
+
+        // call start over iwlan
+        spendSystemTime(10000L);
+        sendCallTypeChanged(
+                QnsConstants.CALL_TYPE_IDLE,
+                QnsConstants.CALL_TYPE_VOICE,
+                mMockRestrictManager,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+
+        // got rtp low from wlan side
+        spendSystemTime(10000L);
+        doReturn(true)
+                .when(mMockRestrictManager)
+                .hasRestrictionType(
+                        eq(AccessNetworkConstants.TRANSPORT_TYPE_WLAN),
+                        eq(RestrictManager.RESTRICT_TYPE_RTP_LOW_QUALITY));
+
+        // call end
+        spendSystemTime(3000L);
+        sendCallTypeChanged(
+                QnsConstants.CALL_TYPE_VOICE,
+                QnsConstants.CALL_TYPE_IDLE,
+                mMockRestrictManager,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+
+        // media no data cause
+        spendSystemTime(3000L);
+        sendImsCallDropStats(
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
+                AccessNetworkConstants.AccessNetworkType.IWLAN);
+
+        // verify
+        verify(mMockStatsLib, timeout(1000).times(2)).write(capture.capture());
+        List<AtomsQnsImsCallDropStats> list = capture.getAllValues();
+        mQnsMetrics.log("QnsMetricsTest ImsCallDrop atom[0]:" + list.get(0));
+        mQnsMetrics.log("QnsMetricsTest ImsCallDrop atom[1]:" + list.get(1));
+        assertTrue(list.get(0).getRtpThresholdBreached());
+        assertTrue(list.get(1).getRtpThresholdBreached());
+    }
+
+
+    @Test
+    public void testAtomsQnsFallbackRestrictionChangedInfo() {
+
+        ArgumentCaptor<AtomsQnsFallbackRestrictionChangedInfo> capture =
+                ArgumentCaptor.forClass(AtomsQnsFallbackRestrictionChangedInfo.class);
+
+        List<Integer> wlanRestrictions = new ArrayList<>();
+        List<Integer> wwanRestrictions = new ArrayList<>();
+        sendFallbackRestrictionChanged(wlanRestrictions, wwanRestrictions);
+
+        wlanRestrictions.add(RestrictManager.RESTRICT_TYPE_GUARDING);
+        wwanRestrictions.add(RestrictManager.RESTRICT_TYPE_GUARDING);
+        sendFallbackRestrictionChanged(wlanRestrictions, wwanRestrictions);
+
+        wlanRestrictions.clear();
+        wwanRestrictions.clear();
+        wlanRestrictions.add(RestrictManager.RESTRICT_TYPE_RTP_LOW_QUALITY);
+        sendFallbackRestrictionChanged(wlanRestrictions, wwanRestrictions);
+
+        wlanRestrictions.clear();
+        wwanRestrictions.clear();
+        wwanRestrictions.add(RestrictManager.RESTRICT_TYPE_RTP_LOW_QUALITY);
+        sendFallbackRestrictionChanged(wlanRestrictions, wwanRestrictions);
+
+        wlanRestrictions.clear();
+        wwanRestrictions.clear();
+        wlanRestrictions.add(RestrictManager.RESTRICT_TYPE_FALLBACK_TO_WWAN_IMS_REGI_FAIL);
+        sendFallbackRestrictionChanged(wlanRestrictions, wwanRestrictions);
+
+        wlanRestrictions.clear();
+        wwanRestrictions.clear();
+        wwanRestrictions.add(RestrictManager.RESTRICT_TYPE_FALLBACK_TO_WWAN_IMS_REGI_FAIL);
+        sendFallbackRestrictionChanged(wlanRestrictions, wwanRestrictions);
+
+        wlanRestrictions.clear();
+        wwanRestrictions.clear();
+        wlanRestrictions.add(RestrictManager.RESTRICT_TYPE_FALLBACK_TO_WWAN_RTT_BACKHAUL_FAIL);
+        sendFallbackRestrictionChanged(wlanRestrictions, wwanRestrictions);
+
+        wlanRestrictions.clear();
+        wwanRestrictions.clear();
+        wwanRestrictions.add(RestrictManager.RESTRICT_TYPE_FALLBACK_TO_WWAN_RTT_BACKHAUL_FAIL);
+        sendFallbackRestrictionChanged(wlanRestrictions, wwanRestrictions);
+
+        // verify
+        verify(mMockStatsLib, timeout(1000).times(4)).write(capture.capture());
+        List<AtomsQnsFallbackRestrictionChangedInfo> list = capture.getAllValues();
+        mQnsMetrics.log("QnsMetricsTest ImsCallDrop atom[0]:" + list.get(0));
+        mQnsMetrics.log("QnsMetricsTest ImsCallDrop atom[1]:" + list.get(1));
+        mQnsMetrics.log("QnsMetricsTest ImsCallDrop atom[2]:" + list.get(2));
+        mQnsMetrics.log("QnsMetricsTest ImsCallDrop atom[3]:" + list.get(3));
+        assertTrue(list.get(0).getRestrictionOnWlanByRtpThresholdBreached());
+        assertTrue(list.get(1).getRestrictionOnWwanByRtpThresholdBreached());
+        assertTrue(list.get(2).getRestrictionOnWlanByImsRegistrationFailed());
+        assertTrue(list.get(3).getRestrictionOnWlanByWifiBackhaulProblem());
+    }
+}
diff --git a/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/QnsTelephonyListenerTest.java b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/QnsTelephonyListenerTest.java
index f1d63fa..2f50ff8 100644
--- a/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/QnsTelephonyListenerTest.java
+++ b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/QnsTelephonyListenerTest.java
@@ -47,6 +47,7 @@
 import android.telephony.VopsSupportInfo;
 import android.telephony.data.ApnSetting;
 import android.telephony.ims.ImsCallProfile;
+import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.MediaQualityStatus;
 import android.util.SparseArray;
 
@@ -1035,6 +1036,20 @@
     }
 
     @Test
+    public void testOnImsCallDisconnectCauseChanged() {
+        mQtListener.registerImsCallDropDisconnectCauseListener(mMockHandler, 0, null);
+        assertTrue(mQtListener.mImsCallDropDisconnectCauseListener.size() > 0);
+        verify(mMockHandler, never()).sendMessage(any());
+
+        ImsReasonInfo imsReasonInfo = new ImsReasonInfo();
+        mQtListener.onImsCallDisconnectCauseChanged(imsReasonInfo);
+        verify(mMockHandler, times(1)).sendMessage(any());
+
+        mQtListener.unregisterImsCallDropDisconnectCauseListener(mMockHandler);
+        assertEquals(0, mQtListener.mImsCallDropDisconnectCauseListener.size());
+    }
+
+    @Test
     public void testNullTelephonyListener() {
         mQtListener.close();
         setReady(false);
diff --git a/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/QnsTest.java b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/QnsTest.java
index 2144516..0dff376 100644
--- a/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/QnsTest.java
+++ b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/QnsTest.java
@@ -32,6 +32,7 @@
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
 import android.os.Handler;
+import android.os.PowerManager;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
@@ -69,11 +70,11 @@
     @Mock protected Resources mMockResources;
 
     // qns mocks
-    @Mock AlternativeEventListener mMockAltEventListener;
     @Mock protected IwlanNetworkStatusTracker mMockIwlanNetworkStatusTracker;
     @Mock protected WifiQualityMonitor mMockWifiQm;
     @Mock protected CellularNetworkStatusTracker mMockCellNetStatusTracker;
     @Mock protected CellularQualityMonitor mMockCellularQm;
+    @Mock protected PowerManager mMockPowerManager;
     @Mock protected QnsImsManager mMockQnsImsManager;
     @Mock protected QnsCarrierConfigManager mMockQnsConfigManager;
     @Mock protected QnsEventDispatcher mMockQnsEventDispatcher;
@@ -81,6 +82,8 @@
     @Mock protected QnsTelephonyListener mMockQnsTelephonyListener;
     @Mock protected QnsCallStatusTracker mMockQnsCallStatusTracker;
     @Mock protected WifiBackhaulMonitor mMockWifiBm;
+    @Mock protected QnsTimer mMockQnsTimer;
+    @Mock protected QnsMetrics mMockQnsMetrics;
 
     protected QnsComponents[] mQnsComponents = new QnsComponents[2];
 
@@ -100,7 +103,6 @@
         mQnsComponents[0] =
                 new QnsComponents(
                         sMockContext,
-                        mMockAltEventListener,
                         mMockCellNetStatusTracker,
                         mMockCellularQm,
                         mMockIwlanNetworkStatusTracker,
@@ -110,14 +112,15 @@
                         mMockQnsProvisioningListener,
                         mMockQnsTelephonyListener,
                         mMockQnsCallStatusTracker,
+                        mMockQnsTimer,
                         mMockWifiBm,
                         mMockWifiQm,
+                        mMockQnsMetrics,
                         0);
 
         mQnsComponents[1] =
                 new QnsComponents(
                         sMockContext,
-                        mMockAltEventListener,
                         mMockCellNetStatusTracker,
                         mMockCellularQm,
                         mMockIwlanNetworkStatusTracker,
@@ -127,8 +130,10 @@
                         mMockQnsProvisioningListener,
                         mMockQnsTelephonyListener,
                         mMockQnsCallStatusTracker,
+                        mMockQnsTimer,
                         mMockWifiBm,
                         mMockWifiQm,
+                        mMockQnsMetrics,
                         1);
     }
 
@@ -144,7 +149,7 @@
         when(sMockContext.getSystemService(ImsManager.class)).thenReturn(mMockImsManager);
         when(sMockContext.getSystemService(WifiManager.class)).thenReturn(mMockWifiManager);
         when(sMockContext.getSystemService(CountryDetector.class)).thenReturn(mMockCountryDetector);
-
+        when(sMockContext.getSystemService(PowerManager.class)).thenReturn(mMockPowerManager);
         when(sMockContext.getResources()).thenReturn(mMockResources);
     }
 
@@ -165,6 +170,7 @@
 
         when(mMockCountryDetector.detectCountry())
                 .thenReturn(new Country("US", Country.COUNTRY_SOURCE_LOCATION));
+        when(mMockPowerManager.isDeviceIdleMode()).thenReturn(false);
     }
 
     private void stubOthers() {
diff --git a/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/QnsTimerTest.java b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/QnsTimerTest.java
new file mode 100644
index 0000000..50a36b5
--- /dev/null
+++ b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/QnsTimerTest.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright (C) 2023 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.telephony.qns;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.os.test.TestLooper;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.CountDownLatch;
+
+@RunWith(AndroidJUnit4.class)
+public class QnsTimerTest extends QnsTest {
+
+    private static final int EVENT_QNS_TIMER_EXPIRED = 1;
+    static final String ACTION_ALARM_TIMER_EXPIRED =
+            "com.android.telephony.qns.action.ALARM_TIMER_EXPIRED";
+    static final String KEY_TIMER_ID = "key_timer_id";
+    @Mock private Context mContext;
+    @Mock private AlarmManager mAlarmManager;
+    @Mock private PowerManager mPowerManager;
+    @Mock Message mMessage;
+    private QnsTimer mQnsTimer;
+    private BroadcastReceiver mBroadcastReceiver;
+    int mTimerId;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        when(mContext.getSystemService(AlarmManager.class)).thenReturn(mAlarmManager);
+        when(mContext.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
+        mQnsTimer = new QnsTimer(mContext);
+        ArgumentCaptor<BroadcastReceiver> args = ArgumentCaptor.forClass(BroadcastReceiver.class);
+        verify(mContext).registerReceiver(args.capture(), isA(IntentFilter.class), anyInt());
+        mBroadcastReceiver = args.getValue();
+    }
+
+    @After
+    public void tearDown() {
+        mQnsTimer.close();
+    }
+
+    @Test
+    public void testRegisterTimerForScreenOff() {
+        mBroadcastReceiver.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
+        waitForDelayedHandlerAction(mQnsTimer.mHandler, 10, 200);
+
+        mTimerId = mQnsTimer.registerTimer(mMessage, 30000);
+        waitForDelayedHandlerAction(mQnsTimer.mHandler, 10, 200);
+
+        assertTrue(mQnsTimer.getTimersInfo().contains(new QnsTimer.TimerInfo(mTimerId)));
+        assertTrue(mQnsTimer.mHandler.hasMessages(EVENT_QNS_TIMER_EXPIRED));
+        verify(mAlarmManager)
+                .setExactAndAllowWhileIdle(anyInt(), anyLong(), isA(PendingIntent.class));
+    }
+
+    @Test
+    public void testRegisterTimerForScreenOn() {
+        mBroadcastReceiver.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_ON));
+        waitForDelayedHandlerAction(mQnsTimer.mHandler, 10, 200);
+
+        mTimerId = mQnsTimer.registerTimer(mMessage, 80000);
+        waitForDelayedHandlerAction(mQnsTimer.mHandler, 10, 200);
+
+        assertTrue(mQnsTimer.getTimersInfo().contains(new QnsTimer.TimerInfo(mTimerId)));
+        assertTrue(mQnsTimer.mHandler.hasMessages(EVENT_QNS_TIMER_EXPIRED));
+        verify(mAlarmManager, never())
+                .setExactAndAllowWhileIdle(anyInt(), anyLong(), isA(PendingIntent.class));
+    }
+
+    @Test
+    public void testUnregisterForInvalidId() {
+        testRegisterTimerForScreenOn();
+        int timerInfoSize = mQnsTimer.getTimersInfo().size();
+        mQnsTimer.unregisterTimer(QnsConstants.INVALID_ID);
+        waitForDelayedHandlerAction(mQnsTimer.mHandler, 10, 200);
+        assertEquals(timerInfoSize, mQnsTimer.getTimersInfo().size());
+    }
+
+    @Test
+    public void testUnregisterTimerForScreenOff() {
+        testRegisterTimerForScreenOff();
+        mQnsTimer.unregisterTimer(mTimerId);
+        waitForDelayedHandlerAction(mQnsTimer.mHandler, 10, 200);
+        assertFalse(mQnsTimer.getTimersInfo().contains(new QnsTimer.TimerInfo(mTimerId)));
+        assertFalse(mQnsTimer.mHandler.hasMessages(EVENT_QNS_TIMER_EXPIRED));
+        verify(mAlarmManager).cancel(isA(PendingIntent.class));
+    }
+
+    @Test
+    public void testUnregisterTimerForScreenOn() {
+        testRegisterTimerForScreenOn();
+        mQnsTimer.unregisterTimer(mTimerId);
+        waitForDelayedHandlerAction(mQnsTimer.mHandler, 10, 200);
+        assertFalse(mQnsTimer.getTimersInfo().contains(new QnsTimer.TimerInfo(mTimerId)));
+        assertFalse(mQnsTimer.mHandler.hasMessages(EVENT_QNS_TIMER_EXPIRED));
+        verify(mAlarmManager, never()).cancel(isA(PendingIntent.class));
+    }
+
+    @Test
+    public void testUpdateTimerTypeToAlarm() {
+        testRegisterTimerForScreenOn();
+
+        mBroadcastReceiver.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
+        waitForDelayedHandlerAction(mQnsTimer.mHandler, 10, 200);
+
+        verify(mAlarmManager)
+                .setExactAndAllowWhileIdle(anyInt(), anyLong(), isA(PendingIntent.class));
+    }
+
+    @Test
+    public void testTimerExpired() {
+        mBroadcastReceiver.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
+        waitForDelayedHandlerAction(mQnsTimer.mHandler, 10, 200);
+
+        mTimerId = mQnsTimer.registerTimer(mMessage, 50);
+        waitForDelayedHandlerAction(mQnsTimer.mHandler, 10, 200);
+
+        assertTrue(mQnsTimer.getTimersInfo().contains(new QnsTimer.TimerInfo(mTimerId)));
+        assertTrue(mQnsTimer.mHandler.hasMessages(EVENT_QNS_TIMER_EXPIRED));
+        verify(mAlarmManager)
+                .setExactAndAllowWhileIdle(anyInt(), anyLong(), isA(PendingIntent.class));
+
+        waitForDelayedHandlerAction(mQnsTimer.mHandler, 40, 200);
+        mBroadcastReceiver.onReceive(mContext, new Intent(ACTION_ALARM_TIMER_EXPIRED));
+        waitForDelayedHandlerAction(mQnsTimer.mHandler, 10, 200);
+
+        verify(mMessage).sendToTarget();
+        assertFalse(mQnsTimer.mHandler.hasMessages(EVENT_QNS_TIMER_EXPIRED));
+        verify(mAlarmManager).cancel(isA(PendingIntent.class));
+    }
+
+    @Test
+    public void testMultipleTimerRegistered() {
+        mBroadcastReceiver.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
+        waitForDelayedHandlerAction(mQnsTimer.mHandler, 10, 200);
+        TestLooper testLooper = new TestLooper();
+        Handler h = new Handler(testLooper.getLooper());
+
+        mQnsTimer.registerTimer(Message.obtain(h, 4), 300);
+        mQnsTimer.registerTimer(Message.obtain(h, 3), 200);
+        mQnsTimer.registerTimer(Message.obtain(h, 1), 50);
+        mQnsTimer.registerTimer(Message.obtain(h, 1), 50);
+        mQnsTimer.registerTimer(Message.obtain(h, 2), 100);
+        mQnsTimer.registerTimer(Message.obtain(h, 2), 100);
+        waitForDelayedHandlerAction(mQnsTimer.mHandler, 320, 100);
+
+        // alarm timer should update for shortest delay and since the minimum timer value is 10
+        // secs for screen off condition, the alarm timer will not replace be replaced until new
+        // timer requested for less than 10 secs.
+        verify(mAlarmManager)
+                .setExactAndAllowWhileIdle(anyInt(), anyLong(), isA(PendingIntent.class));
+
+        // verify order of message received:
+        Message msg = testLooper.nextMessage();
+        assertEquals(1, msg.what);
+        msg = testLooper.nextMessage();
+        assertEquals(1, msg.what);
+        msg = testLooper.nextMessage();
+        assertEquals(2, msg.what);
+        msg = testLooper.nextMessage();
+        assertEquals(2, msg.what);
+        msg = testLooper.nextMessage();
+        assertEquals(3, msg.what);
+        msg = testLooper.nextMessage();
+        assertEquals(4, msg.what);
+    }
+
+    @Test
+    public void testCancelOngoingAlarm() {
+        mBroadcastReceiver.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
+        waitForDelayedHandlerAction(mQnsTimer.mHandler, 10, 200);
+        TestLooper testLooper = new TestLooper();
+        Handler h = new Handler(testLooper.getLooper());
+
+        int timerId1 = mQnsTimer.registerTimer(Message.obtain(h, 1), 61 * 1000);
+        int timerId2 = mQnsTimer.registerTimer(Message.obtain(h, 2), 1000);
+        waitForDelayedHandlerAction(mQnsTimer.mHandler, 10, 200);
+
+        assertEquals(2, mQnsTimer.getTimersInfo().size());
+        assertEquals(timerId2, mQnsTimer.getTimersInfo().peek().getTimerId());
+        assertTrue(mQnsTimer.mHandler.hasMessages(EVENT_QNS_TIMER_EXPIRED));
+        verify(mAlarmManager, times(2))
+                .setExactAndAllowWhileIdle(anyInt(), anyLong(), isA(PendingIntent.class));
+
+        mQnsTimer.unregisterTimer(timerId2);
+        waitForDelayedHandlerAction(mQnsTimer.mHandler, 10, 200);
+        assertEquals(1, mQnsTimer.getTimersInfo().size());
+        assertEquals(timerId1, mQnsTimer.getTimersInfo().peek().getTimerId());
+        assertTrue(mQnsTimer.mHandler.hasMessages(EVENT_QNS_TIMER_EXPIRED));
+
+        verify(mAlarmManager, times(3))
+                .setExactAndAllowWhileIdle(anyInt(), anyLong(), isA(PendingIntent.class));
+    }
+
+    @Test
+    public void testAlarmOnLiteIdleModeMinDelay() {
+        int setDelay = 20000;
+        when(mPowerManager.isDeviceLightIdleMode()).thenReturn(true);
+        mBroadcastReceiver.onReceive(
+                sMockContext, new Intent(PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED));
+        long delay = setupAlarmForDelay(setDelay);
+
+        // assume 100ms as max delay in execution
+        assertTrue(delay < 30000 && delay > 30000 - 100);
+    }
+
+    @Test
+    public void testAlarmOnLiteIdleMode() {
+        int setDelay = 40000;
+        when(mPowerManager.isDeviceLightIdleMode()).thenReturn(true);
+        mBroadcastReceiver.onReceive(
+                sMockContext, new Intent(PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED));
+        long delay = setupAlarmForDelay(setDelay);
+
+        // assume 100ms as max delay in execution
+        assertTrue(delay < setDelay && delay > setDelay - 100);
+    }
+
+    @Test
+    public void testAlarmOnIdleModeMinDelay() {
+        int setDelay = 50000;
+        when(mPowerManager.isDeviceIdleMode()).thenReturn(true);
+        mBroadcastReceiver.onReceive(
+                sMockContext, new Intent(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED));
+        long delay = setupAlarmForDelay(setDelay);
+
+        // assume 100ms as max delay in execution
+        assertTrue(delay < 60000 && delay > 60000 - 100);
+    }
+
+    @Test
+    public void testAlarmOnIdleMode() {
+        int setDelay = 70000;
+        when(mPowerManager.isDeviceIdleMode()).thenReturn(true);
+        mBroadcastReceiver.onReceive(
+                sMockContext, new Intent(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED));
+        long delay = setupAlarmForDelay(setDelay);
+
+        // assume 100ms as max delay in execution
+        assertTrue(delay < setDelay && delay > setDelay - 100);
+    }
+
+    private long setupAlarmForDelay(int setDelay) {
+        mQnsTimer.registerTimer(mMessage, setDelay);
+
+        waitForDelayedHandlerAction(mQnsTimer.mHandler, 10, 200);
+        ArgumentCaptor<Long> capture = ArgumentCaptor.forClass(Long.class);
+        verify(mAlarmManager)
+                .setExactAndAllowWhileIdle(anyInt(), capture.capture(), isA(PendingIntent.class));
+        return capture.getValue() - SystemClock.elapsedRealtime();
+    }
+
+    @Test
+    public void testAlarmInCallActiveState() {
+        mQnsTimer.updateCallState(QnsConstants.CALL_TYPE_VOICE);
+        int setDelay = 4000;
+        when(mPowerManager.isDeviceIdleMode()).thenReturn(true);
+        mBroadcastReceiver.onReceive(
+                sMockContext, new Intent(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED));
+        long delay = setupAlarmForDelay(setDelay);
+
+        // assume 100ms as max delay in execution
+        assertTrue(delay < setDelay && delay > setDelay - 100);
+    }
+
+    @Test
+    public void testDeviceMovesToActiveState() {
+        int setDelay = 30000;
+        CountDownLatch latch = new CountDownLatch(2);
+        HandlerThread ht = new HandlerThread("");
+        ht.start();
+        Handler tempHandler = spy(new Handler(ht.getLooper()));
+        when(mPowerManager.isDeviceLightIdleMode()).thenReturn(true, false);
+        mBroadcastReceiver.onReceive(sMockContext, new Intent(Intent.ACTION_SCREEN_OFF));
+        mBroadcastReceiver.onReceive(
+                sMockContext, new Intent(PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED));
+        mQnsTimer.registerTimer(mMessage, setDelay);
+        waitForDelayedHandlerAction(mQnsTimer.mHandler, 10, 200);
+        verify(mAlarmManager)
+                .setExactAndAllowWhileIdle(anyInt(), anyLong(), isA(PendingIntent.class));
+        waitForDelayedHandlerAction(mQnsTimer.mHandler, 10, 5000);
+
+        mQnsTimer.mHandler = tempHandler;
+        mBroadcastReceiver.onReceive(
+                sMockContext, new Intent(PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED));
+        mBroadcastReceiver.onReceive(sMockContext, new Intent(Intent.ACTION_SCREEN_ON));
+        waitForDelayedHandlerAction(mQnsTimer.mHandler, 10, 200);
+        verify(mAlarmManager).cancel(isA(PendingIntent.class));
+
+        // Handler should reset for the updated delay
+        verify(tempHandler).removeMessages(EVENT_QNS_TIMER_EXPIRED);
+        verify(tempHandler).sendEmptyMessageDelayed(anyInt(), anyLong());
+        assertTrue(mQnsTimer.mHandler.hasMessages(EVENT_QNS_TIMER_EXPIRED));
+        ht.quit();
+    }
+}
diff --git a/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/QnsUtilsTest.java b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/QnsUtilsTest.java
index 7b1db18..7c68069 100644
--- a/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/QnsUtilsTest.java
+++ b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/QnsUtilsTest.java
@@ -256,6 +256,8 @@
         String[] defaultStringArray = new String[] {"LTE", "UMTS"};
         String[] defaultGapOffsetStringArray =
                 new String[] {"eutran:rsrp:-5", "ngran:ssrsp:-2", "utran:rscp:-3"};
+        String[] defaultHysteresisDbLevelStringArray =
+                new String[] {"eutran:rsrp:1", "ngran:ssrsp:2", "utran:rscp:5"};
         String[] defaultHandoverPolicyArray = new String[] {HANDOVER_POLICY_1, HANDOVER_POLICY_2};
         String[] fallbackWwanRuleWithImsUnregistered =
                 new String[] {FALLBACK_RULE0, FALLBACK_RULE1};
@@ -515,6 +517,13 @@
                         QnsCarrierConfigManager
                                 .KEY_QNS_ROVEIN_THRESHOLD_GAP_WITH_GUARD_TIMER_STRING_ARRAY));
         assertArrayEquals(
+                defaultHysteresisDbLevelStringArray,
+                QnsUtils.getConfig(
+                        mTestBundle,
+                        null,
+                        QnsCarrierConfigManager
+                                .KEY_QNS_CELLULAR_SIGNAL_STRENGTH_HYSTERESIS_DB_STRING_ARRAY));
+        assertArrayEquals(
                 defaultHandoverPolicyArray,
                 QnsUtils.getConfig(
                         mTestBundle,
@@ -662,6 +671,9 @@
         mTestBundle.putStringArray(
                 QnsCarrierConfigManager.KEY_QNS_FALLBACK_ON_INITIAL_CONNECTION_FAILURE_STRING_ARRAY,
                 new String[] {"ims:2:30000:60000:5", "mms:1:10000:5000:2"});
+        mTestBundle.putStringArray(
+                QnsCarrierConfigManager.KEY_QNS_CELLULAR_SIGNAL_STRENGTH_HYSTERESIS_DB_STRING_ARRAY,
+                new String[] {"eutran:rsrp:1", "ngran:ssrsp:2", "utran:rscp:5"});
     }
 
     @Test
@@ -1079,6 +1091,13 @@
                         null,
                         QnsCarrierConfigManager
                                 .KEY_QNS_FALLBACK_ON_INITIAL_CONNECTION_FAILURE_STRING_ARRAY));
+        assertArrayEquals(
+                (String[]) null,
+                QnsUtils.getConfig(
+                        null,
+                        null,
+                        QnsCarrierConfigManager
+                                .KEY_QNS_CELLULAR_SIGNAL_STRENGTH_HYSTERESIS_DB_STRING_ARRAY));
     }
 
     @Test
diff --git a/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/QualifiedNetworksServiceImplTest.java b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/QualifiedNetworksServiceImplTest.java
index 3a82244..9a1d559 100644
--- a/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/QualifiedNetworksServiceImplTest.java
+++ b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/QualifiedNetworksServiceImplTest.java
@@ -81,7 +81,6 @@
         TestQnsComponents(int slotId) {
             super(
                     sMockContext,
-                    mMockAltEventListener,
                     mMockCellNetStatusTracker,
                     mMockCellularQm,
                     mMockIwlanNetworkStatusTracker,
@@ -91,8 +90,10 @@
                     mMockQnsProvisioningListener,
                     mMockQnsTelephonyListener,
                     mMockQnsCallStatusTracker,
+                    mMockQnsTimer,
                     mMockWifiBm,
                     mMockWifiQm,
+                    mMockQnsMetrics,
                     slotId);
         }
 
diff --git a/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/RestrictManagerTest.java b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/RestrictManagerTest.java
index 466e687..ddc193a 100644
--- a/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/RestrictManagerTest.java
+++ b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/RestrictManagerTest.java
@@ -46,7 +46,10 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.lenient;
 import static org.mockito.Mockito.when;
 
@@ -74,6 +77,11 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.MockitoSession;
+import org.mockito.stubbing.Answer;
+
+import java.util.HashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 @RunWith(JUnit4.class)
 public class RestrictManagerTest extends QnsTest {
@@ -81,7 +89,6 @@
     @Mock DataConnectionStatusTracker mMockDcst;
 
     private MockitoSession mMockSession;
-    private AlternativeEventListener mAltListener;
     private QnsTelephonyListener mTelephonyListener;
 
     private static final int DEFAULT_GUARDING_TIME = 30000;
@@ -91,15 +98,14 @@
     private QnsImsManager mQnsImsManager;
 
     protected TestLooper mTestLooper;
+    int mId = 0;
+    HashMap<Integer, Message> mMessageHashMap = new HashMap<>();
 
     HandlerThread mHandlerThread =
             new HandlerThread("") {
                 @Override
                 protected void onLooperPrepared() {
                     super.onLooperPrepared();
-                    mAltListener =
-                            new AlternativeEventListener(
-                                    sMockContext, mMockQnsTelephonyListener, 0);
                     mTelephonyListener = new QnsTelephonyListener(sMockContext, 0);
                     mQnsImsManager = new QnsImsManager(sMockContext, 0);
                     setReady(true);
@@ -124,6 +130,24 @@
         when(mMockQnsConfigManager.getWaitingTimerForPreferredTransportOnPowerOn(
                         AccessNetworkConstants.TRANSPORT_TYPE_WWAN))
                 .thenReturn(0);
+        when(mMockQnsTimer.registerTimer(isA(Message.class), anyLong())).thenAnswer(
+                (Answer<Integer>) invocation -> {
+                    Message msg = (Message) invocation.getArguments()[0];
+                    long delay = (long) invocation.getArguments()[1];
+                    msg.getTarget().sendMessageDelayed(msg, delay);
+                    mMessageHashMap.put(++mId, msg);
+                    return mId;
+                });
+
+        doAnswer(invocation -> {
+            int timerId = (int) invocation.getArguments()[0];
+            Message msg = mMessageHashMap.get(timerId);
+            if (msg != null && msg.getTarget() != null) {
+                msg.getTarget().removeMessages(msg.what, msg.obj);
+            }
+            return null;
+        }).when(mMockQnsTimer).unregisterTimer(anyInt());
+
         mTestLooper = new TestLooper();
         mHandlerThread.start();
 
@@ -132,7 +156,6 @@
         mQnsComponents[0] =
                 new QnsComponents(
                         sMockContext,
-                        mAltListener,
                         mMockCellNetStatusTracker,
                         mMockCellularQm,
                         mMockIwlanNetworkStatusTracker,
@@ -142,8 +165,10 @@
                         mMockQnsProvisioningListener,
                         mTelephonyListener,
                         mMockQnsCallStatusTracker,
+                        mMockQnsTimer,
                         mMockWifiBm,
                         mMockWifiQm,
+                        mMockQnsMetrics,
                         0);
 
         mRestrictManager =
@@ -2790,4 +2815,29 @@
         mRestrictManager.releaseRestriction(
                 AccessNetworkConstants.TRANSPORT_TYPE_WLAN, RESTRICT_TYPE_GUARDING, true);
     }
+
+    @Test
+    public void testSendRestrictionsForMetrics() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        Handler handler = new Handler(mHandlerThread.getLooper()) {
+            public void handleMessage(Message msg) {
+                latch.countDown();
+            }
+        };
+
+        mRestrictManager.registerRestrictInfoChanged(handler, 0);
+        assertNotNull(mRestrictManager.mRestrictInfoRegistrant);
+
+        mRestrictManager.addRestriction(
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
+                RESTRICT_TYPE_RTP_LOW_QUALITY,
+                sReleaseEventMap.get(RESTRICT_TYPE_NON_PREFERRED_TRANSPORT),
+                DEFAULT_RESTRICT_NON_PREFERRED_TRANSPORT_TIME);
+
+        assertTrue(latch.await(3, TimeUnit.SECONDS));
+
+        mRestrictManager.unRegisterRestrictInfoChanged(handler);
+        assertNull(mRestrictManager.mRestrictInfoRegistrant);
+    }
+
 }
diff --git a/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/WifiBackhaulMonitorTest.java b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/WifiBackhaulMonitorTest.java
index 3f81fb1..b05d3c1 100644
--- a/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/WifiBackhaulMonitorTest.java
+++ b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/WifiBackhaulMonitorTest.java
@@ -17,9 +17,11 @@
 package com.android.telephony.qns;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.telephony.qns.QnsConstants.INVALID_ID;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.anyInt;
@@ -76,6 +78,7 @@
     private StaticMockitoSession mMockitoSession;
     private LinkProperties mLinkProperties = new LinkProperties();
     private String mServerAddress;
+    private QnsTimer mQnsTimer;
     private int[] mRttConfigs;
 
     HandlerThread mHt =
@@ -85,7 +88,11 @@
                     super.onLooperPrepared();
                     mWbm =
                             new WifiBackhaulMonitor(
-                                    sMockContext, mMockQnsConfigManager, mMockQnsImsManager, 0);
+                                    sMockContext,
+                                    mMockQnsConfigManager,
+                                    mMockQnsImsManager,
+                                    mQnsTimer,
+                                    0);
                     setReady(true);
                 }
             };
@@ -121,6 +128,7 @@
         mRttConfigs = null;
         mLinkProperties.setInterfaceName("iwlan0");
         mLatch = new CountDownLatch(1);
+        mQnsTimer = new QnsTimer(sMockContext);
 
         mMockitoSession =
                 mockitoSession()
@@ -282,7 +290,7 @@
                                 null))
                 .sendToTarget();
         waitForDelayedHandlerAction(mRttHandler, 100, 100);
-        assertTrue(mRttHandler.hasMessages(EVENT_START_RTT_CHECK));
+        assertNotEquals(mWbm.getRttTimerId(), INVALID_ID);
     }
 
     @Test
diff --git a/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/WifiQualityMonitorTest.java b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/WifiQualityMonitorTest.java
index 75d8db7..8e51d2c 100644
--- a/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/WifiQualityMonitorTest.java
+++ b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/WifiQualityMonitorTest.java
@@ -53,8 +53,10 @@
 @RunWith(JUnit4.class)
 public class WifiQualityMonitorTest extends QnsTest {
 
+    private static final int EVENT_QNS_TIMER_EXPIRED = 1;
     Context mContext;
     @Mock ConnectivityManager mConnectivityManager;
+    QnsTimer mQnsTimer;
     @Mock WifiManager mWifiManager;
     @Mock NetworkCapabilities mNetworkCapabilityManager;
     @Mock private Network mMockNetwork;
@@ -96,7 +98,8 @@
         mWifiInfo = new WifiInfo.Builder().setRssi(mSetRssi).build();
         mLatch = new CountDownLatch(1);
         mThresholdListener = new ThresholdListener(mExecutor);
-        mWifiQualityMonitor = new WifiQualityMonitor(mContext);
+        mQnsTimer = new QnsTimer(mContext);
+        mWifiQualityMonitor = new WifiQualityMonitor(mContext, mQnsTimer);
     }
 
     @Test
@@ -232,7 +235,7 @@
     }
 
     @Test
-    public void testBackhaulTimer() throws InterruptedException {
+    public void testBackhaulTimer() {
         mSetRssi = -65;
         mLatch = new CountDownLatch(1);
         mWifiInfo = new WifiInfo.Builder().setRssi(mSetRssi).build();
@@ -277,8 +280,9 @@
 
         mWifiQualityMonitor.mHandler.obtainMessage(EVENT_WIFI_RSSI_CHANGED, -65, 0).sendToTarget();
         waitForDelayedHandlerAction(mWifiQualityMonitor.mHandler, 1000, 200);
-        assertTrue(mWifiQualityMonitor.mHandler.hasMessages(EVENT_WIFI_NOTIFY_TIMER_EXPIRED));
+        assertTrue(mQnsTimer.mHandler.hasMessages(EVENT_QNS_TIMER_EXPIRED));
         waitForDelayedHandlerAction(mWifiQualityMonitor.mHandler, 4000, 200);
+        assertFalse(mQnsTimer.mHandler.hasMessages(EVENT_QNS_TIMER_EXPIRED));
         assertFalse(mWifiQualityMonitor.mHandler.hasMessages(EVENT_WIFI_NOTIFY_TIMER_EXPIRED));
     }
 
@@ -329,23 +333,6 @@
         isWifiRssiChangedHandlerNotPosted();
     }
 
-    @Test
-    public void testSkipValidateWqmStatus_WithBackhaulInProgress() {
-        mSetRssi = -65;
-        mLatch = new CountDownLatch(1);
-        mWifiInfo = new WifiInfo.Builder().setRssi(mSetRssi).build();
-        when(mWifiManager.getConnectionInfo()).thenReturn(mWifiInfo);
-
-        setWqmThreshold();
-        mWifiQualityMonitor.validateWqmStatus(-65);
-
-        waitForDelayedHandlerAction(mWifiQualityMonitor.mHandler, 1000, 200);
-        assertTrue(mWifiQualityMonitor.mHandler.hasMessages(EVENT_WIFI_NOTIFY_TIMER_EXPIRED));
-
-        mWifiQualityMonitor.validateWqmStatus(-68);
-        assertFalse(mWifiQualityMonitor.mHandler.hasMessages(EVENT_WIFI_RSSI_CHANGED));
-    }
-
     private void setWqmThreshold() {
         mThs1[0] =
                 new Threshold(
diff --git a/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/atoms/AtomsQnsFallbackRestrictionChangedInfoTest.java b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/atoms/AtomsQnsFallbackRestrictionChangedInfoTest.java
new file mode 100644
index 0000000..159ab07
--- /dev/null
+++ b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/atoms/AtomsQnsFallbackRestrictionChangedInfoTest.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2022 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.telephony.qns.atoms;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.util.StatsEvent;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(JUnit4.class)
+public class AtomsQnsFallbackRestrictionChangedInfoTest {
+
+    @Mock
+    private StatsEvent.Builder mStatsEventBuilder;
+
+    private static final boolean DEFAULT_RESTRICTION_WLAN_RTP_THRESHOLD_BREACHED = true;
+    private static final boolean DEFAULT_RESTRICTION_WWAN_RTP_THRESHOLD_BREACHED = true;
+    private static final boolean DEFAULT_RESTRICTION_WWAN_IMS_REGI_FAIL = true;
+    private static final boolean DEFAULT_RESTRICTION_WWAN_WIFI_BACKHAUL_PROBLEM = true;
+    private static final int DEFAULT_CARRIER_ID = 1; // TMO
+
+    private AtomsQnsFallbackRestrictionChangedInfo mInfoEmpty;
+    private AtomsQnsFallbackRestrictionChangedInfo mInfoDefault;
+    private AtomsQnsFallbackRestrictionChangedInfo mInfoCopy;
+
+    /** atom #1 : Restriction on WLAN caused by RTP threshold breached */
+    private boolean mRestrictionWlanRtpThresholdBreached;
+
+    /** atom #2 : Restriction on WWAN caused by RTP threshold breached */
+    private boolean mRestrictionWwanRtpThresholdBreached;
+
+    /** atom #3 : Restriction on WLAN caused by IMS registration fail */
+    private boolean mRestrictionWwanImsRegiFail;
+
+    /** atom #4 : Restriction on WLAN caused by Wifi backhaul problem. */
+    private boolean mRestrictionWwanWifiBackhaulProblem;
+
+    /** atom #5 : Carrier Id */
+    private int mCarrierId;
+
+    /** atom #6 : Slot Index */
+    private int mSlotIndex;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mRestrictionWlanRtpThresholdBreached = DEFAULT_RESTRICTION_WLAN_RTP_THRESHOLD_BREACHED;
+        mRestrictionWwanRtpThresholdBreached = DEFAULT_RESTRICTION_WWAN_RTP_THRESHOLD_BREACHED;
+        mRestrictionWwanImsRegiFail = DEFAULT_RESTRICTION_WWAN_IMS_REGI_FAIL;
+        mRestrictionWwanWifiBackhaulProblem = DEFAULT_RESTRICTION_WWAN_WIFI_BACKHAUL_PROBLEM;
+        mCarrierId = DEFAULT_CARRIER_ID;
+        mSlotIndex = 0;
+        mInfoEmpty = new AtomsQnsFallbackRestrictionChangedInfo();
+        mInfoDefault =
+                new AtomsQnsFallbackRestrictionChangedInfo(
+                        mRestrictionWlanRtpThresholdBreached,
+                        mRestrictionWwanRtpThresholdBreached,
+                        mRestrictionWwanImsRegiFail,
+                        mRestrictionWwanWifiBackhaulProblem,
+                        mCarrierId,
+                        mSlotIndex);
+        mInfoCopy = new AtomsQnsFallbackRestrictionChangedInfo(mInfoDefault);
+    }
+
+    @After
+    public void tearDown() {
+        mInfoEmpty = null;
+        mInfoDefault = null;
+        mInfoCopy = null;
+    }
+
+    @Test
+    public void testGetSetRestrictionWlanRtpThresholdBreached() {
+        assertFalse(mInfoEmpty.getRestrictionOnWlanByRtpThresholdBreached());
+        assertTrue(mInfoDefault.getRestrictionOnWlanByRtpThresholdBreached());
+        assertTrue(mInfoCopy.getRestrictionOnWlanByRtpThresholdBreached());
+
+        mInfoEmpty.setRestrictionOnWlanByRtpThresholdBreached(true);
+        mInfoDefault.setRestrictionOnWlanByRtpThresholdBreached(true);
+        mInfoCopy.setRestrictionOnWlanByRtpThresholdBreached(true);
+
+        assertTrue(mInfoEmpty.getRestrictionOnWlanByRtpThresholdBreached());
+        assertTrue(mInfoDefault.getRestrictionOnWlanByRtpThresholdBreached());
+        assertTrue(mInfoCopy.getRestrictionOnWlanByRtpThresholdBreached());
+    }
+
+    @Test
+    public void testGetSetRestrictionWwanRtpThresholdBreached() {
+        assertFalse(mInfoEmpty.getRestrictionOnWwanByRtpThresholdBreached());
+        assertTrue(mInfoDefault.getRestrictionOnWwanByRtpThresholdBreached());
+        assertTrue(mInfoCopy.getRestrictionOnWwanByRtpThresholdBreached());
+
+        mInfoEmpty.setRestrictionOnWwanByRtpThresholdBreached(true);
+        mInfoDefault.setRestrictionOnWwanByRtpThresholdBreached(true);
+        mInfoCopy.setRestrictionOnWwanByRtpThresholdBreached(true);
+
+        assertTrue(mInfoEmpty.getRestrictionOnWwanByRtpThresholdBreached());
+        assertTrue(mInfoDefault.getRestrictionOnWwanByRtpThresholdBreached());
+        assertTrue(mInfoCopy.getRestrictionOnWwanByRtpThresholdBreached());
+    }
+
+    @Test
+    public void testGetSetRestrictionWwanImsRegiFail() {
+        assertFalse(mInfoEmpty.getRestrictionOnWlanByImsRegistrationFailed());
+        assertTrue(mInfoDefault.getRestrictionOnWlanByImsRegistrationFailed());
+        assertTrue(mInfoCopy.getRestrictionOnWlanByImsRegistrationFailed());
+
+        mInfoEmpty.setRestrictionOnWlanByImsRegistrationFailed(true);
+        mInfoDefault.setRestrictionOnWlanByImsRegistrationFailed(true);
+        mInfoCopy.setRestrictionOnWlanByImsRegistrationFailed(true);
+
+        assertTrue(mInfoEmpty.getRestrictionOnWlanByImsRegistrationFailed());
+        assertTrue(mInfoDefault.getRestrictionOnWlanByImsRegistrationFailed());
+        assertTrue(mInfoCopy.getRestrictionOnWlanByImsRegistrationFailed());
+    }
+
+    @Test
+    public void testGetSetRestrictionWwanWifiBackhaulProblem() {
+        assertFalse(mInfoEmpty.getRestrictionOnWlanByWifiBackhaulProblem());
+        assertTrue(mInfoDefault.getRestrictionOnWlanByWifiBackhaulProblem());
+        assertTrue(mInfoCopy.getRestrictionOnWlanByWifiBackhaulProblem());
+
+        mInfoEmpty.setRestrictionOnWlanByWifiBackhaulProblem(true);
+        mInfoDefault.setRestrictionOnWlanByWifiBackhaulProblem(true);
+        mInfoCopy.setRestrictionOnWlanByWifiBackhaulProblem(true);
+
+        assertTrue(mInfoEmpty.getRestrictionOnWlanByWifiBackhaulProblem());
+        assertTrue(mInfoDefault.getRestrictionOnWlanByWifiBackhaulProblem());
+        assertTrue(mInfoCopy.getRestrictionOnWlanByWifiBackhaulProblem());
+    }
+
+    @Test
+    public void testGetSetCarrierId() {
+        assertEquals(0, mInfoEmpty.getCarrierId());
+        assertEquals(DEFAULT_CARRIER_ID, mInfoDefault.getCarrierId());
+        assertEquals(DEFAULT_CARRIER_ID, mInfoCopy.getCarrierId());
+
+        mInfoEmpty.setCarrierId(1);
+        mInfoDefault.setCarrierId(2);
+        mInfoCopy.setCarrierId(3);
+
+        assertEquals(1, mInfoEmpty.getCarrierId());
+        assertEquals(2, mInfoDefault.getCarrierId());
+        assertEquals(3, mInfoCopy.getCarrierId());
+    }
+
+    @Test
+    public void testGetSetSlotIndex() {
+        assertEquals(0, mInfoEmpty.getSlotIndex());
+        assertEquals(0, mInfoDefault.getSlotIndex());
+        assertEquals(0, mInfoCopy.getSlotIndex());
+
+        mInfoEmpty.setSlotIndex(1);
+        mInfoDefault.setSlotIndex(2);
+        mInfoCopy.setSlotIndex(3);
+
+        assertEquals(1, mInfoEmpty.getSlotIndex());
+        assertEquals(2, mInfoDefault.getSlotIndex());
+        assertEquals(3, mInfoCopy.getSlotIndex());
+    }
+
+    @Test
+    public void testToString() {
+        String strInfoEmpty = mInfoEmpty.toString();
+        String strInfoDefault = mInfoDefault.toString();
+        String strInfoCopy = mInfoCopy.toString();
+
+        assertNotNull(strInfoEmpty);
+        assertNotNull(strInfoDefault);
+        assertNotNull(strInfoCopy);
+
+        assertTrue(strInfoDefault.startsWith("AtomsQnsFallbackRestrictionChangedInfo"));
+
+        assertEquals(strInfoDefault, strInfoCopy);
+        assertNotEquals(strInfoEmpty, strInfoDefault);
+    }
+
+    @Test
+    public void testEquals() {
+        assertEquals(mInfoDefault, mInfoDefault);
+        assertNotEquals(mInfoDefault, new Object());
+        assertEquals(mInfoDefault, mInfoCopy);
+        mInfoDefault.setRestrictionOnWlanByRtpThresholdBreached(false);
+        mInfoCopy.setRestrictionOnWlanByRtpThresholdBreached(true);
+        assertNotEquals(mInfoDefault, mInfoCopy);
+    }
+
+    @Test
+    public void testHashCode() {
+        AtomsQnsFallbackRestrictionChangedInfo a1, a2;
+
+        a1 =
+                new AtomsQnsFallbackRestrictionChangedInfo(
+                        true, true, false, false, mCarrierId, mSlotIndex);
+        a2 =
+                new AtomsQnsFallbackRestrictionChangedInfo(
+                        false, false, false, false, mCarrierId, mSlotIndex);
+        assertNotEquals(a1.hashCode(), a2.hashCode());
+
+        a1 =
+                new AtomsQnsFallbackRestrictionChangedInfo(
+                        mRestrictionWlanRtpThresholdBreached,
+                        mRestrictionWwanRtpThresholdBreached,
+                        mRestrictionWwanImsRegiFail,
+                        mRestrictionWwanWifiBackhaulProblem,
+                        mCarrierId,
+                        mSlotIndex);
+        a2 =
+                new AtomsQnsFallbackRestrictionChangedInfo(
+                        mRestrictionWlanRtpThresholdBreached,
+                        mRestrictionWwanRtpThresholdBreached,
+                        mRestrictionWwanImsRegiFail,
+                        mRestrictionWwanWifiBackhaulProblem,
+                        mCarrierId,
+                        mSlotIndex);
+        assertEquals(a1.hashCode(), a2.hashCode());
+    }
+
+    @Test
+    public void testStatsId() {
+        final int statsId = 636; // QnsFallbackRestrictionChanged
+        assertEquals(statsId, mInfoDefault.copy().getStatsId());
+    }
+
+    @Test
+    public void testStatsEventBuilder() {
+        mInfoDefault.build(mStatsEventBuilder);
+
+        verify(mStatsEventBuilder, times(4)).writeBoolean(anyBoolean());
+        verify(mStatsEventBuilder, times(2)).writeInt(anyInt());
+    }
+}
diff --git a/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/atoms/AtomsQnsHandoverPingPongInfoTest.java b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/atoms/AtomsQnsHandoverPingPongInfoTest.java
new file mode 100644
index 0000000..e914579
--- /dev/null
+++ b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/atoms/AtomsQnsHandoverPingPongInfoTest.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2022 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.telephony.qns.atoms;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.util.StatsEvent;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(JUnit4.class)
+public class AtomsQnsHandoverPingPongInfoTest {
+
+    @Mock
+    private StatsEvent.Builder mStatsEventBuilder;
+
+    private static final int DEFAULT_COUNT_HANDOVER_PING_PONG = 2;
+    private static final int DEFAULT_CARRIER_ID = 1; // TMO
+
+    private AtomsQnsHandoverPingPongInfo mInfoEmpty;
+    private AtomsQnsHandoverPingPongInfo mInfoDefault;
+    private AtomsQnsHandoverPingPongInfo mInfoCopy;
+
+    /** atom #1 : Count of handover ping-pong */
+    private int mCountHandoverPingPong;
+
+    /** atom #2 : Carrier Id */
+    private int mCarrierId;
+
+    /** atom #3 : Slot Index */
+    private int mSlotIndex;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mCountHandoverPingPong = DEFAULT_COUNT_HANDOVER_PING_PONG;
+        mCarrierId = DEFAULT_CARRIER_ID;
+        mSlotIndex = 0;
+        mInfoEmpty = new AtomsQnsHandoverPingPongInfo();
+        mInfoDefault =
+                new AtomsQnsHandoverPingPongInfo(mCountHandoverPingPong, mCarrierId, mSlotIndex);
+        mInfoCopy = new AtomsQnsHandoverPingPongInfo(mInfoDefault);
+    }
+
+    @After
+    public void tearDown() {
+        mInfoEmpty = null;
+        mInfoDefault = null;
+        mInfoCopy = null;
+    }
+
+    @Test
+    public void testGetSetCountHandoverPingPong() {
+        assertEquals(0, mInfoEmpty.getCountHandoverPingPong());
+        assertEquals(DEFAULT_COUNT_HANDOVER_PING_PONG, mInfoDefault.getCountHandoverPingPong());
+        assertEquals(DEFAULT_COUNT_HANDOVER_PING_PONG, mInfoCopy.getCountHandoverPingPong());
+
+        mInfoEmpty.setCountHandoverPingPong(3);
+        mInfoDefault.setCountHandoverPingPong(4);
+        mInfoCopy.setCountHandoverPingPong(5);
+
+        assertEquals(3, mInfoEmpty.getCountHandoverPingPong());
+        assertEquals(4, mInfoDefault.getCountHandoverPingPong());
+        assertEquals(5, mInfoCopy.getCountHandoverPingPong());
+    }
+
+    @Test
+    public void testGetSetCarrierId() {
+        assertEquals(0, mInfoEmpty.getCarrierId());
+        assertEquals(DEFAULT_CARRIER_ID, mInfoDefault.getCarrierId());
+        assertEquals(DEFAULT_CARRIER_ID, mInfoCopy.getCarrierId());
+
+        mInfoEmpty.setCarrierId(1);
+        mInfoDefault.setCarrierId(2);
+        mInfoCopy.setCarrierId(3);
+
+        assertEquals(1, mInfoEmpty.getCarrierId());
+        assertEquals(2, mInfoDefault.getCarrierId());
+        assertEquals(3, mInfoCopy.getCarrierId());
+    }
+
+    @Test
+    public void testGetSetSlotIndex() {
+        assertEquals(0, mInfoEmpty.getSlotIndex());
+        assertEquals(0, mInfoDefault.getSlotIndex());
+        assertEquals(0, mInfoCopy.getSlotIndex());
+
+        mInfoEmpty.setSlotIndex(1);
+        mInfoDefault.setSlotIndex(2);
+        mInfoCopy.setSlotIndex(3);
+
+        assertEquals(1, mInfoEmpty.getSlotIndex());
+        assertEquals(2, mInfoDefault.getSlotIndex());
+        assertEquals(3, mInfoCopy.getSlotIndex());
+    }
+
+    @Test
+    public void testToString() {
+        String strInfoEmpty = mInfoEmpty.toString();
+        String strInfoDefault = mInfoDefault.toString();
+        String strInfoCopy = mInfoCopy.toString();
+
+        assertNotNull(strInfoEmpty);
+        assertNotNull(strInfoDefault);
+        assertNotNull(strInfoCopy);
+
+        assertTrue(strInfoDefault.startsWith("AtomsQnsHandoverPingPongInfo"));
+
+        assertEquals(strInfoDefault, strInfoCopy);
+        assertNotEquals(strInfoEmpty, strInfoDefault);
+    }
+
+    @Test
+    public void testEquals() {
+        assertEquals(mInfoDefault, mInfoDefault);
+        assertNotEquals(mInfoDefault, new Object());
+        assertEquals(mInfoDefault, mInfoCopy);
+        mInfoDefault.setCountHandoverPingPong(2);
+        mInfoCopy.setCountHandoverPingPong(3);
+        assertNotEquals(mInfoDefault, mInfoCopy);
+    }
+
+    @Test
+    public void testHashCode() {
+        AtomsQnsHandoverPingPongInfo a1, a2;
+
+        a1 = new AtomsQnsHandoverPingPongInfo(2, 1, 0);
+        a2 = new AtomsQnsHandoverPingPongInfo(3, 1, 0);
+        assertNotEquals(a1.hashCode(), a2.hashCode());
+
+        a1 = new AtomsQnsHandoverPingPongInfo(mCountHandoverPingPong, mCarrierId, mSlotIndex);
+        a2 = new AtomsQnsHandoverPingPongInfo(mCountHandoverPingPong, mCarrierId, mSlotIndex);
+        assertEquals(a1.hashCode(), a2.hashCode());
+    }
+
+    @Test
+    public void testDimension() {
+        AtomsQnsHandoverPingPongInfo a1, a2;
+
+        a1 = new AtomsQnsHandoverPingPongInfo(mCountHandoverPingPong, 3, mSlotIndex);
+        a2 = new AtomsQnsHandoverPingPongInfo(mCountHandoverPingPong, 1, mSlotIndex);
+        assertNotEquals(a1.getDimension(), a2.getDimension());
+
+        a1 = new AtomsQnsHandoverPingPongInfo(mCountHandoverPingPong, mCarrierId, 0);
+        a2 = new AtomsQnsHandoverPingPongInfo(mCountHandoverPingPong, mCarrierId, 1);
+        assertNotEquals(a1.getDimension(), a2.getDimension());
+
+        a1 = new AtomsQnsHandoverPingPongInfo(1, mCarrierId, mSlotIndex);
+        a2 = new AtomsQnsHandoverPingPongInfo(2, mCarrierId, mSlotIndex);
+        assertEquals(a1.getDimension(), a2.getDimension());
+    }
+
+    @Test
+    public void testStatsId() {
+        final int statsId = 10179; // QnsHandoverPingpong
+        assertEquals(statsId, mInfoDefault.copy().getStatsId());
+    }
+
+    @Test
+    public void testStatsEventBuilder() {
+        mInfoDefault.build(mStatsEventBuilder);
+
+        verify(mStatsEventBuilder, times(3)).writeInt(anyInt());
+    }
+}
diff --git a/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/atoms/AtomsQnsHandoverTimeMillisInfoTest.java b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/atoms/AtomsQnsHandoverTimeMillisInfoTest.java
new file mode 100644
index 0000000..701874b
--- /dev/null
+++ b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/atoms/AtomsQnsHandoverTimeMillisInfoTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2022 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.telephony.qns.atoms;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.util.StatsEvent;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(JUnit4.class)
+public class AtomsQnsHandoverTimeMillisInfoTest {
+
+    @Mock
+    private StatsEvent.Builder mStatsEventBuilder;
+
+    private static final int DEFAULT_TIMER_FOR_HANDOVER_SUCCESS = 5000;
+
+    private AtomsQnsHandoverTimeMillisInfo mInfoEmpty;
+    private AtomsQnsHandoverTimeMillisInfo mInfoDefault;
+    private AtomsQnsHandoverTimeMillisInfo mInfoCopy;
+
+    /** atom #1 : Time in milliseconds from QNS RAT update to successful HO completion */
+    private int mTimeForHoSuccess;
+
+    /** atom #2 : Slot Index */
+    private int mSlotIndex;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mTimeForHoSuccess = DEFAULT_TIMER_FOR_HANDOVER_SUCCESS;
+        mSlotIndex = 0;
+        mInfoEmpty = new AtomsQnsHandoverTimeMillisInfo();
+        mInfoDefault = new AtomsQnsHandoverTimeMillisInfo(mTimeForHoSuccess, mSlotIndex);
+        mInfoCopy = new AtomsQnsHandoverTimeMillisInfo(mInfoDefault);
+    }
+
+    @After
+    public void tearDown() {
+        mInfoEmpty = null;
+        mInfoDefault = null;
+        mInfoCopy = null;
+    }
+
+    @Test
+    public void testGetSetTimeForHoSuccess() {
+        assertEquals(0, mInfoEmpty.getTimeForHoSuccess());
+        assertEquals(DEFAULT_TIMER_FOR_HANDOVER_SUCCESS, mInfoDefault.getTimeForHoSuccess());
+        assertEquals(DEFAULT_TIMER_FOR_HANDOVER_SUCCESS, mInfoCopy.getTimeForHoSuccess());
+
+        mInfoEmpty.setTimeForHoSuccess(3000);
+        mInfoDefault.setTimeForHoSuccess(4000);
+        mInfoCopy.setTimeForHoSuccess(7000);
+
+        assertEquals(3000, mInfoEmpty.getTimeForHoSuccess());
+        assertEquals(4000, mInfoDefault.getTimeForHoSuccess());
+        assertEquals(7000, mInfoCopy.getTimeForHoSuccess());
+    }
+
+    @Test
+    public void testGetSetSlotIndex() {
+        assertEquals(0, mInfoEmpty.getSlotIndex());
+        assertEquals(0, mInfoDefault.getSlotIndex());
+        assertEquals(0, mInfoCopy.getSlotIndex());
+
+        mInfoEmpty.setSlotIndex(1);
+        mInfoDefault.setSlotIndex(2);
+        mInfoCopy.setSlotIndex(3);
+
+        assertEquals(1, mInfoEmpty.getSlotIndex());
+        assertEquals(2, mInfoDefault.getSlotIndex());
+        assertEquals(3, mInfoCopy.getSlotIndex());
+    }
+
+    @Test
+    public void testToString() {
+        String strInfoEmpty = mInfoEmpty.toString();
+        String strInfoDefault = mInfoDefault.toString();
+        String strInfoCopy = mInfoCopy.toString();
+
+        assertNotNull(strInfoEmpty);
+        assertNotNull(strInfoDefault);
+        assertNotNull(strInfoCopy);
+
+        assertTrue(strInfoDefault.startsWith("AtomsQnsHandoverTimeMillisInfo"));
+
+        assertEquals(strInfoDefault, strInfoCopy);
+        assertNotEquals(strInfoEmpty, strInfoDefault);
+    }
+
+    @Test
+    public void testEquals() {
+        assertEquals(mInfoDefault, mInfoDefault);
+        assertNotEquals(mInfoDefault, new Object());
+        assertEquals(mInfoDefault, mInfoCopy);
+        mInfoDefault.setTimeForHoSuccess(2000);
+        mInfoCopy.setTimeForHoSuccess(3000);
+        assertNotEquals(mInfoDefault, mInfoCopy);
+    }
+
+    @Test
+    public void testHashCode() {
+        AtomsQnsHandoverTimeMillisInfo a1, a2;
+
+        a1 = new AtomsQnsHandoverTimeMillisInfo(2000, 0);
+        a2 = new AtomsQnsHandoverTimeMillisInfo(3000, 0);
+        assertNotEquals(a1.hashCode(), a2.hashCode());
+
+        a1 = new AtomsQnsHandoverTimeMillisInfo(mTimeForHoSuccess, mSlotIndex);
+        a2 = new AtomsQnsHandoverTimeMillisInfo(mTimeForHoSuccess, mSlotIndex);
+        assertEquals(a1.hashCode(), a2.hashCode());
+    }
+
+    @Test
+    public void testDimension() {
+        AtomsQnsHandoverTimeMillisInfo a1, a2;
+        a1 = new AtomsQnsHandoverTimeMillisInfo(1000, 0);
+        a2 = new AtomsQnsHandoverTimeMillisInfo(2000, 1);
+        assertNotEquals(a1.getDimension(), a2.getDimension());
+        assertEquals(a1.getDimension(), mInfoDefault.getDimension());
+    }
+
+    @Test
+    public void testStatsId() {
+        final int statsId = 10178; // QnsHandoverTimeMillis
+        assertEquals(statsId, mInfoDefault.copy().getStatsId());
+    }
+
+    @Test
+    public void testStatsEventBuilder() {
+        mInfoDefault.build(mStatsEventBuilder);
+
+        verify(mStatsEventBuilder, times(2)).writeInt(anyInt());
+    }
+}
diff --git a/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/atoms/AtomsQnsImsCallDropStatsTest.java b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/atoms/AtomsQnsImsCallDropStatsTest.java
new file mode 100644
index 0000000..a2b68ad
--- /dev/null
+++ b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/atoms/AtomsQnsImsCallDropStatsTest.java
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2022 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.telephony.qns.atoms;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.telephony.qns.QnsProtoEnums;
+import android.util.StatsEvent;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(JUnit4.class)
+public class AtomsQnsImsCallDropStatsTest {
+
+    @Mock
+    private StatsEvent.Builder mStatsEventBuilder;
+
+    private static final int DEFAULT_CURRENT_TRANSPORT_TYPE = QnsProtoEnums.TRANSPORT_TYPE_WWAN;
+    private static final int DEFAULT_RESTRICTIONS = QnsProtoEnums.RESTRICT_TYPE_NONE;
+    private static final int DEFAULT_SIGNAL_STRENGTH = -100;
+    private static final int DEFAULT_SIGNAL_QUALITY = -10;
+    private static final int DEFAULT_SIGNAL_NOISE = -1;
+    private static final int DEFAULT_IWLAN_SIGNAL_STRENGTH = -70;
+    private static final int DEFAULT_CELLULAR_NETWORK_TYPE = QnsProtoEnums.EUTRAN;
+
+    private AtomsQnsImsCallDropStats mInfoEmpty;
+    private AtomsQnsImsCallDropStats mInfoDefault;
+    private AtomsQnsImsCallDropStats mInfoCopy;
+
+    /** atom #1 : Transport type in where IMS call drop occurred. */
+    private int mTransportTypeCallDropped;
+
+    /** atom #2 : RTP threshold breached event occurred. */
+    private boolean mRtpThresholdBreached;
+
+    /** atom #3 : Bit mask of restrictions on another transport type */
+    private int mRestrictionsOnOtherTransportType;
+
+    /** atom #4 : Cellular network signal strength {e.g. SSRSRP in NR, RSRP in LTE} */
+    private int mSignalStrength;
+
+    /** atom #5 : Cellular network signal quality {e.g. SSRSRQ in NR, RSRQ in LTE} */
+    private int mSignalQuality;
+
+    /** atom #6 : Cellular network signal noise ratio {e.g. SSSINR in NR, RSSNR in LTE} */
+    private int mSignalNoise;
+
+    /** atom #7 : Iwlan network signal strength (Wi-Fi RSSI) */
+    private int mIwlanSignalStrength;
+
+    /** atom #8 : Slot Index */
+    private int mSlotIndex;
+
+    /** atom #9 : cellular access network type. */
+    private int mCellularNetworkType;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mTransportTypeCallDropped = DEFAULT_CURRENT_TRANSPORT_TYPE;
+        mRtpThresholdBreached = true;
+        mRestrictionsOnOtherTransportType = DEFAULT_RESTRICTIONS;
+        mSignalStrength = DEFAULT_SIGNAL_STRENGTH;
+        mSignalQuality = DEFAULT_SIGNAL_QUALITY;
+        mSignalNoise = DEFAULT_SIGNAL_NOISE;
+        mIwlanSignalStrength = DEFAULT_IWLAN_SIGNAL_STRENGTH;
+        mSlotIndex = 0;
+        mCellularNetworkType = DEFAULT_CELLULAR_NETWORK_TYPE;
+
+        mInfoEmpty = new AtomsQnsImsCallDropStats();
+        mInfoDefault =
+                new AtomsQnsImsCallDropStats(
+                        mTransportTypeCallDropped,
+                        mRtpThresholdBreached,
+                        mRestrictionsOnOtherTransportType,
+                        mSignalStrength,
+                        mSignalQuality,
+                        mSignalNoise,
+                        mIwlanSignalStrength,
+                        mSlotIndex,
+                        mCellularNetworkType);
+        mInfoCopy = new AtomsQnsImsCallDropStats(mInfoDefault);
+    }
+
+    @After
+    public void tearDown() {
+        mInfoEmpty = null;
+        mInfoDefault = null;
+        mInfoCopy = null;
+    }
+
+    @Test
+    public void testGetSetTransportTypeCallDropped() {
+        assertEquals(0, mInfoEmpty.getTransportTypeCallDropped());
+        assertEquals(DEFAULT_CURRENT_TRANSPORT_TYPE, mInfoDefault.getTransportTypeCallDropped());
+        assertEquals(DEFAULT_CURRENT_TRANSPORT_TYPE, mInfoCopy.getTransportTypeCallDropped());
+
+        mInfoEmpty.setTransportTypeCallDropped(QnsProtoEnums.TRANSPORT_TYPE_WWAN);
+        mInfoDefault.setTransportTypeCallDropped(QnsProtoEnums.TRANSPORT_TYPE_WLAN);
+        mInfoCopy.setTransportTypeCallDropped(QnsProtoEnums.TRANSPORT_TYPE_INVALID);
+
+        assertEquals(QnsProtoEnums.TRANSPORT_TYPE_WWAN, mInfoEmpty.getTransportTypeCallDropped());
+        assertEquals(QnsProtoEnums.TRANSPORT_TYPE_WLAN, mInfoDefault.getTransportTypeCallDropped());
+        assertEquals(QnsProtoEnums.TRANSPORT_TYPE_INVALID, mInfoCopy.getTransportTypeCallDropped());
+    }
+
+    @Test
+    public void testGetSetRtpThresholdBreached() {
+        assertFalse(mInfoEmpty.getRtpThresholdBreached());
+        assertTrue(mInfoDefault.getRtpThresholdBreached());
+        assertTrue(mInfoCopy.getRtpThresholdBreached());
+
+        mInfoEmpty.setRtpThresholdBreached(true);
+        mInfoDefault.setRtpThresholdBreached(true);
+        mInfoCopy.setRtpThresholdBreached(false);
+
+        assertTrue(mInfoEmpty.getRtpThresholdBreached());
+        assertTrue(mInfoDefault.getRtpThresholdBreached());
+        assertFalse(mInfoCopy.getRtpThresholdBreached());
+    }
+
+    @Test
+    public void testGetSetRestrictionsOnOtherTransportType() {
+        assertEquals(0, mInfoEmpty.getRestrictionsOnOtherTransportType());
+        assertEquals(DEFAULT_RESTRICTIONS, mInfoDefault.getRestrictionsOnOtherTransportType());
+        assertEquals(DEFAULT_RESTRICTIONS, mInfoCopy.getRestrictionsOnOtherTransportType());
+
+        mInfoEmpty.setRestrictionsOnOtherTransportType(QnsProtoEnums.RESTRICT_TYPE_GUARDING);
+        mInfoDefault.setRestrictionsOnOtherTransportType(QnsProtoEnums.RESTRICT_TYPE_THROTTLING);
+        mInfoCopy.setRestrictionsOnOtherTransportType(QnsProtoEnums.RESTRICT_TYPE_NONE);
+
+        assertEquals(
+                QnsProtoEnums.RESTRICT_TYPE_GUARDING,
+                mInfoEmpty.getRestrictionsOnOtherTransportType());
+        assertEquals(
+                QnsProtoEnums.RESTRICT_TYPE_THROTTLING,
+                mInfoDefault.getRestrictionsOnOtherTransportType());
+        assertEquals(
+                QnsProtoEnums.RESTRICT_TYPE_NONE, mInfoCopy.getRestrictionsOnOtherTransportType());
+    }
+
+    @Test
+    public void testGetSetSignalStrength() {
+        assertEquals(0, mInfoEmpty.getSignalStrength());
+        assertEquals(DEFAULT_SIGNAL_STRENGTH, mInfoDefault.getSignalStrength());
+        assertEquals(DEFAULT_SIGNAL_STRENGTH, mInfoCopy.getSignalStrength());
+
+        mInfoEmpty.setSignalStrength(DEFAULT_SIGNAL_STRENGTH);
+        mInfoDefault.setSignalStrength(-120);
+        mInfoCopy.setSignalStrength(-110);
+
+        assertEquals(DEFAULT_SIGNAL_STRENGTH, mInfoEmpty.getSignalStrength());
+        assertEquals(-120, mInfoDefault.getSignalStrength());
+        assertEquals(-110, mInfoCopy.getSignalStrength());
+    }
+
+    @Test
+    public void testGetSetSignalQuality() {
+        assertEquals(0, mInfoEmpty.getSignalQuality());
+        assertEquals(DEFAULT_SIGNAL_QUALITY, mInfoDefault.getSignalQuality());
+        assertEquals(DEFAULT_SIGNAL_QUALITY, mInfoCopy.getSignalQuality());
+
+        mInfoEmpty.setSignalQuality(DEFAULT_SIGNAL_QUALITY);
+        mInfoDefault.setSignalQuality(-10);
+        mInfoCopy.setSignalQuality(-5);
+
+        assertEquals(DEFAULT_SIGNAL_QUALITY, mInfoEmpty.getSignalQuality());
+        assertEquals(-10, mInfoDefault.getSignalQuality());
+        assertEquals(-5, mInfoCopy.getSignalQuality());
+    }
+
+    @Test
+    public void testGetSetSignalNoise() {
+        assertEquals(0, mInfoEmpty.getSignalNoise());
+        assertEquals(DEFAULT_SIGNAL_NOISE, mInfoDefault.getSignalNoise());
+        assertEquals(DEFAULT_SIGNAL_NOISE, mInfoCopy.getSignalNoise());
+
+        mInfoEmpty.setSignalNoise(DEFAULT_SIGNAL_NOISE);
+        mInfoDefault.setSignalNoise(1);
+        mInfoCopy.setSignalNoise(-1);
+
+        assertEquals(DEFAULT_SIGNAL_NOISE, mInfoEmpty.getSignalNoise());
+        assertEquals(1, mInfoDefault.getSignalNoise());
+        assertEquals(-1, mInfoCopy.getSignalNoise());
+    }
+
+    @Test
+    public void testGetSetIwlanSignalStrength() {
+        assertEquals(0, mInfoEmpty.getIwlanSignalStrength());
+        assertEquals(DEFAULT_IWLAN_SIGNAL_STRENGTH, mInfoDefault.getIwlanSignalStrength());
+        assertEquals(DEFAULT_IWLAN_SIGNAL_STRENGTH, mInfoCopy.getIwlanSignalStrength());
+
+        mInfoEmpty.setIwlanSignalStrength(DEFAULT_IWLAN_SIGNAL_STRENGTH);
+        mInfoDefault.setIwlanSignalStrength(-80);
+        mInfoCopy.setIwlanSignalStrength(-50);
+
+        assertEquals(DEFAULT_IWLAN_SIGNAL_STRENGTH, mInfoEmpty.getIwlanSignalStrength());
+        assertEquals(-80, mInfoDefault.getIwlanSignalStrength());
+        assertEquals(-50, mInfoCopy.getIwlanSignalStrength());
+    }
+
+    @Test
+    public void testGetSetSlotIndex() {
+        assertEquals(0, mInfoEmpty.getSlotIndex());
+        assertEquals(0, mInfoDefault.getSlotIndex());
+        assertEquals(0, mInfoCopy.getSlotIndex());
+
+        mInfoEmpty.setSlotIndex(1);
+        mInfoDefault.setSlotIndex(2);
+        mInfoCopy.setSlotIndex(3);
+
+        assertEquals(1, mInfoEmpty.getSlotIndex());
+        assertEquals(2, mInfoDefault.getSlotIndex());
+        assertEquals(3, mInfoCopy.getSlotIndex());
+    }
+
+    @Test
+    public void testGetSetCellularNetworkType() {
+        assertEquals(0, mInfoEmpty.getCellularNetworkType());
+        assertEquals(DEFAULT_CELLULAR_NETWORK_TYPE, mInfoDefault.getCellularNetworkType());
+        assertEquals(DEFAULT_CELLULAR_NETWORK_TYPE, mInfoCopy.getCellularNetworkType());
+
+        mInfoEmpty.setCellularNetworkType(QnsProtoEnums.NGRAN);
+        mInfoDefault.setCellularNetworkType(QnsProtoEnums.UTRAN);
+        mInfoCopy.setCellularNetworkType(QnsProtoEnums.GERAN);
+
+        assertEquals(QnsProtoEnums.NGRAN, mInfoEmpty.getCellularNetworkType());
+        assertEquals(QnsProtoEnums.UTRAN, mInfoDefault.getCellularNetworkType());
+        assertEquals(QnsProtoEnums.GERAN, mInfoCopy.getCellularNetworkType());
+    }
+
+    @Test
+    public void testToString() {
+        String strInfoEmpty = mInfoEmpty.toString();
+        String strInfoDefault = mInfoDefault.toString();
+        String strInfoCopy = mInfoCopy.toString();
+
+        assertNotNull(strInfoEmpty);
+        assertNotNull(strInfoDefault);
+        assertNotNull(strInfoCopy);
+
+        assertTrue(strInfoDefault.startsWith("AtomsQnsImsCallDropStats"));
+
+        assertEquals(strInfoDefault, strInfoCopy);
+        assertNotEquals(strInfoEmpty, strInfoDefault);
+    }
+
+    @Test
+    public void testEquals() {
+        assertEquals(mInfoDefault, mInfoDefault);
+        assertNotEquals(mInfoDefault, new Object());
+        assertEquals(mInfoDefault, mInfoCopy);
+        mInfoDefault.setSignalQuality(3);
+        mInfoCopy.setSignalQuality(1);
+        assertNotEquals(mInfoDefault, mInfoCopy);
+    }
+
+    @Test
+    public void testHashCode() {
+        AtomsQnsImsCallDropStats a1, a2;
+
+        a1 =
+                new AtomsQnsImsCallDropStats(
+                        mInfoDefault.getTransportTypeCallDropped(),
+                        mInfoDefault.getRtpThresholdBreached(),
+                        mInfoDefault.getRestrictionsOnOtherTransportType(),
+                        0,
+                        0,
+                        0,
+                        0,
+                        0,
+                        0);
+        a2 =
+                new AtomsQnsImsCallDropStats(
+                        mInfoDefault.getTransportTypeCallDropped(),
+                        mInfoDefault.getRtpThresholdBreached(),
+                        mInfoDefault.getRestrictionsOnOtherTransportType(),
+                        0,
+                        0,
+                        0,
+                        0,
+                        0,
+                        1);
+        assertNotEquals(a1.hashCode(), a2.hashCode());
+
+        a1 =
+                new AtomsQnsImsCallDropStats(
+                        mTransportTypeCallDropped,
+                        mRtpThresholdBreached,
+                        mRestrictionsOnOtherTransportType,
+                        mSignalStrength,
+                        mSignalQuality,
+                        mSignalNoise,
+                        mIwlanSignalStrength,
+                        mSlotIndex,
+                        mCellularNetworkType);
+        a2 =
+                new AtomsQnsImsCallDropStats(
+                        mTransportTypeCallDropped,
+                        mRtpThresholdBreached,
+                        mRestrictionsOnOtherTransportType,
+                        mSignalStrength,
+                        mSignalQuality,
+                        mSignalNoise,
+                        mIwlanSignalStrength,
+                        mSlotIndex,
+                        mCellularNetworkType);
+        assertEquals(a1.hashCode(), a2.hashCode());
+    }
+
+    @Test
+    public void testStatsId() {
+        final int statsId = 635; // QnsImsCallDropStats
+        assertEquals(statsId, mInfoDefault.copy().getStatsId());
+    }
+
+    @Test
+    public void testStatsEventBuilder() {
+        mInfoDefault.build(mStatsEventBuilder);
+
+        verify(mStatsEventBuilder, times(1)).writeBoolean(anyBoolean());
+        verify(mStatsEventBuilder, times(8)).writeInt(anyInt());
+    }
+}
diff --git a/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/atoms/AtomsQnsRatPreferenceMismatchInfoTest.java b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/atoms/AtomsQnsRatPreferenceMismatchInfoTest.java
new file mode 100644
index 0000000..4c5b469
--- /dev/null
+++ b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/atoms/AtomsQnsRatPreferenceMismatchInfoTest.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2022 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.telephony.qns.atoms;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.telephony.qns.QnsProtoEnums;
+import android.util.StatsEvent;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(JUnit4.class)
+public class AtomsQnsRatPreferenceMismatchInfoTest {
+
+    @Mock
+    private StatsEvent.Builder mStatsEventBuilder;
+
+    private static final int DEFAULT_NET_CAPABILITY = QnsProtoEnums.NET_CAPABILITY_IMS;
+    private static final int DEFAULT_HANDOVER_FAIL_COUNT = 1;
+    private static final int DEFAULT_DURATION_OF_MISMATCH = 13500;
+    private static final int DEFAULT_CARRIER_ID = 1; // TMO
+
+    private AtomsQnsRatPreferenceMismatchInfo mInfoEmpty;
+    private AtomsQnsRatPreferenceMismatchInfo mInfoDefault;
+    private AtomsQnsRatPreferenceMismatchInfo mInfoCopy;
+
+    /** atom #1 : Net capability of this information. */
+    private int mNetCapability;
+
+    /** atom #2 : Count of handover failed. */
+    private int mHandoverFailCount;
+
+    /** atom #3 : Duration of this mismatch. */
+    private int mDurationOfMismatch;
+
+    /** atom #4 : Carrier ID */
+    private int mCarrierId;
+
+    /** atom #5 : Slot Index */
+    private int mSlotIndex;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mNetCapability = DEFAULT_NET_CAPABILITY;
+        mHandoverFailCount = DEFAULT_HANDOVER_FAIL_COUNT;
+        mDurationOfMismatch = DEFAULT_DURATION_OF_MISMATCH;
+        mCarrierId = DEFAULT_CARRIER_ID;
+        mSlotIndex = 0;
+        mInfoEmpty = new AtomsQnsRatPreferenceMismatchInfo();
+        mInfoDefault =
+                new AtomsQnsRatPreferenceMismatchInfo(
+                        mNetCapability,
+                        mHandoverFailCount,
+                        mDurationOfMismatch,
+                        mCarrierId,
+                        mSlotIndex);
+        mInfoCopy = new AtomsQnsRatPreferenceMismatchInfo(mInfoDefault);
+    }
+
+    @After
+    public void tearDown() {
+        mInfoEmpty = null;
+        mInfoDefault = null;
+        mInfoCopy = null;
+    }
+
+    @Test
+    public void testGetSetNetCapability() {
+        assertEquals(0, mInfoEmpty.getNetCapability());
+        assertEquals(DEFAULT_NET_CAPABILITY, mInfoDefault.getNetCapability());
+        assertEquals(DEFAULT_NET_CAPABILITY, mInfoCopy.getNetCapability());
+
+        mInfoEmpty.setNetCapability(QnsProtoEnums.NET_CAPABILITY_MMS);
+        mInfoDefault.setNetCapability(QnsProtoEnums.NET_CAPABILITY_CBS);
+        mInfoCopy.setNetCapability(QnsProtoEnums.NET_CAPABILITY_XCAP);
+
+        assertEquals(QnsProtoEnums.NET_CAPABILITY_MMS, mInfoEmpty.getNetCapability());
+        assertEquals(QnsProtoEnums.NET_CAPABILITY_CBS, mInfoDefault.getNetCapability());
+        assertEquals(QnsProtoEnums.NET_CAPABILITY_XCAP, mInfoCopy.getNetCapability());
+    }
+
+    @Test
+    public void testGetSetHandoverFailCount() {
+        assertEquals(0, mInfoEmpty.getHandoverFailCount());
+        assertEquals(DEFAULT_HANDOVER_FAIL_COUNT, mInfoDefault.getHandoverFailCount());
+        assertEquals(DEFAULT_HANDOVER_FAIL_COUNT, mInfoCopy.getHandoverFailCount());
+
+        mInfoEmpty.setHandoverFailCount(DEFAULT_HANDOVER_FAIL_COUNT);
+        mInfoDefault.setHandoverFailCount(4);
+        mInfoCopy.setHandoverFailCount(0);
+
+        assertEquals(DEFAULT_HANDOVER_FAIL_COUNT, mInfoEmpty.getHandoverFailCount());
+        assertEquals(4, mInfoDefault.getHandoverFailCount());
+        assertEquals(0, mInfoCopy.getHandoverFailCount());
+    }
+
+    @Test
+    public void testGetSetDurationOfMismatch() {
+        assertEquals(0, mInfoEmpty.getDurationOfMismatch());
+        assertEquals(DEFAULT_DURATION_OF_MISMATCH, mInfoDefault.getDurationOfMismatch());
+        assertEquals(DEFAULT_DURATION_OF_MISMATCH, mInfoCopy.getDurationOfMismatch());
+
+        mInfoEmpty.setDurationOfMismatch(DEFAULT_DURATION_OF_MISMATCH);
+        mInfoDefault.setDurationOfMismatch(4000);
+        mInfoCopy.setDurationOfMismatch(0);
+
+        assertEquals(DEFAULT_DURATION_OF_MISMATCH, mInfoEmpty.getDurationOfMismatch());
+        assertEquals(4000, mInfoDefault.getDurationOfMismatch());
+        assertEquals(0, mInfoCopy.getDurationOfMismatch());
+    }
+
+    @Test
+    public void testGetSetCarrierId() {
+        assertEquals(0, mInfoEmpty.getCarrierId());
+        assertEquals(DEFAULT_CARRIER_ID, mInfoDefault.getCarrierId());
+        assertEquals(DEFAULT_CARRIER_ID, mInfoCopy.getCarrierId());
+
+        mInfoEmpty.setCarrierId(1);
+        mInfoDefault.setCarrierId(2);
+        mInfoCopy.setCarrierId(3);
+
+        assertEquals(1, mInfoEmpty.getCarrierId());
+        assertEquals(2, mInfoDefault.getCarrierId());
+        assertEquals(3, mInfoCopy.getCarrierId());
+    }
+
+    @Test
+    public void testGetSetSlotIndex() {
+        assertEquals(0, mInfoEmpty.getSlotIndex());
+        assertEquals(0, mInfoDefault.getSlotIndex());
+        assertEquals(0, mInfoCopy.getSlotIndex());
+
+        mInfoEmpty.setSlotIndex(1);
+        mInfoDefault.setSlotIndex(2);
+        mInfoCopy.setSlotIndex(3);
+
+        assertEquals(1, mInfoEmpty.getSlotIndex());
+        assertEquals(2, mInfoDefault.getSlotIndex());
+        assertEquals(3, mInfoCopy.getSlotIndex());
+    }
+
+    @Test
+    public void testToString() {
+        String strInfoEmpty = mInfoEmpty.toString();
+        String strInfoDefault = mInfoDefault.toString();
+        String strInfoCopy = mInfoCopy.toString();
+
+        assertNotNull(strInfoEmpty);
+        assertNotNull(strInfoDefault);
+        assertNotNull(strInfoCopy);
+
+        assertTrue(strInfoDefault.startsWith("AtomsQnsRatPreferenceMismatchInfo"));
+
+        assertEquals(strInfoDefault, strInfoCopy);
+        assertNotEquals(strInfoEmpty, strInfoDefault);
+    }
+
+    @Test
+    public void testEquals() {
+        assertEquals(mInfoDefault, mInfoDefault);
+        assertNotEquals(mInfoDefault, new Object());
+        assertEquals(mInfoDefault, mInfoCopy);
+        mInfoDefault.setHandoverFailCount(0);
+        mInfoCopy.setHandoverFailCount(1);
+        assertNotEquals(mInfoDefault, mInfoCopy);
+    }
+
+    @Test
+    public void testHashCode() {
+        AtomsQnsRatPreferenceMismatchInfo a1, a2;
+
+        a1 =
+                new AtomsQnsRatPreferenceMismatchInfo(
+                        QnsProtoEnums.NET_CAPABILITY_IMS, 0, 0, mCarrierId, mSlotIndex);
+        a2 =
+                new AtomsQnsRatPreferenceMismatchInfo(
+                        QnsProtoEnums.NET_CAPABILITY_MMS, 0, 0, mCarrierId, mSlotIndex);
+        assertNotEquals(a1.hashCode(), a2.hashCode());
+
+        a1 =
+                new AtomsQnsRatPreferenceMismatchInfo(
+                        mNetCapability,
+                        mHandoverFailCount,
+                        mDurationOfMismatch,
+                        mCarrierId,
+                        mSlotIndex);
+        a2 =
+                new AtomsQnsRatPreferenceMismatchInfo(
+                        mNetCapability,
+                        mHandoverFailCount,
+                        mDurationOfMismatch,
+                        mCarrierId,
+                        mSlotIndex);
+        assertEquals(a1.hashCode(), a2.hashCode());
+    }
+
+    @Test
+    public void testDimension() {
+        AtomsQnsRatPreferenceMismatchInfo a1, a2;
+
+        a1 =
+                new AtomsQnsRatPreferenceMismatchInfo(
+                        mNetCapability, mHandoverFailCount, mDurationOfMismatch, 1, mSlotIndex);
+        a2 =
+                new AtomsQnsRatPreferenceMismatchInfo(
+                        mNetCapability, mHandoverFailCount, mDurationOfMismatch, 3, mSlotIndex);
+        assertNotEquals(a1.getDimension(), a2.getDimension());
+
+        a1 =
+                new AtomsQnsRatPreferenceMismatchInfo(
+                        mNetCapability, mHandoverFailCount, mDurationOfMismatch, mCarrierId, 0);
+        a2 =
+                new AtomsQnsRatPreferenceMismatchInfo(
+                        mNetCapability, mHandoverFailCount, mDurationOfMismatch, mCarrierId, 1);
+        assertNotEquals(a1.getDimension(), a2.getDimension());
+
+        a1 = new AtomsQnsRatPreferenceMismatchInfo(9, 0, 100, mCarrierId, mSlotIndex);
+        a2 = new AtomsQnsRatPreferenceMismatchInfo(9, 1, 200, mCarrierId, mSlotIndex);
+        assertEquals(a1.getDimension(), a2.getDimension());
+    }
+
+    @Test
+    public void testStatsId() {
+        final int statsId = 10177; // QnsRatPreferenceMismatchInfo
+        assertEquals(statsId, mInfoDefault.copy().getStatsId());
+    }
+
+    @Test
+    public void testStatsEventBuilder() {
+        mInfoDefault.build(mStatsEventBuilder);
+
+        verify(mStatsEventBuilder, times(5)).writeInt(anyInt());
+    }
+}
diff --git a/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/atoms/AtomsQualifiedRatListChangedInfoTest.java b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/atoms/AtomsQualifiedRatListChangedInfoTest.java
new file mode 100644
index 0000000..5eb6a42
--- /dev/null
+++ b/services/QualifiedNetworksService/tests/src/com/android/telephony/qns/atoms/AtomsQualifiedRatListChangedInfoTest.java
@@ -0,0 +1,534 @@
+/*
+ * Copyright (C) 2022 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.telephony.qns.atoms;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.telephony.qns.QnsProtoEnums;
+import android.util.StatsEvent;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(JUnit4.class)
+public class AtomsQualifiedRatListChangedInfoTest {
+
+    @Mock
+    private StatsEvent.Builder mStatsEventBuilder;
+
+    private static final int DEFAULT_NET_CAPABILITY = QnsProtoEnums.NET_CAPABILITY_IMS;
+    private static final int DEFAULT_FIRST_QUALIFIED_RAT = QnsProtoEnums.EUTRAN;
+    private static final int DEFAULT_SECOND_QUALIFIED_RAT = QnsProtoEnums.IWLAN;
+    private static final int DEFAULT_CURRENT_TRANSPORT_TYPE = QnsProtoEnums.TRANSPORT_TYPE_WLAN;
+    private static final boolean DEFAULT_WFC_ENABLED = true;
+    private static final int DEFAULT_WFC_MODE = QnsProtoEnums.CELLULAR_PREFERRED;
+    private static final int DEFAULT_CELLULAR_NETWORK_TYPE = QnsProtoEnums.EUTRAN;
+    private static final int DEFAULT_IWLAN_NETWORK_TYPE = QnsProtoEnums.IWLAN_NETWORK_TYPE_WIFI;
+    private static final int DEFAULT_RESTRICTIONS_ON_WWAN = QnsProtoEnums.RESTRICT_TYPE_NONE;
+    private static final int DEFAULT_RESTRICTIONS_ON_WLAN = QnsProtoEnums.RESTRICT_TYPE_NONE;
+    private static final int DEFAULT_SIGNAL_STRENGTH = -100;
+    private static final int DEFAULT_SIGNAL_QUALITY = -10;
+    private static final int DEFAULT_SIGNAL_NOISE = -1;
+    private static final int DEFAULT_IWLAN_SIGNAL_STRENGTH = -70;
+    private static final int DEFAULT_UPDATE_REASON = 0;
+
+    private AtomsQualifiedRatListChangedInfo mInfoEmpty;
+    private AtomsQualifiedRatListChangedInfo mInfoDefault;
+    private AtomsQualifiedRatListChangedInfo mInfoCopy;
+
+    /** atom #1 : NetCapability of this Qualified RAT update */
+    private int mNetCapability;
+    /** atom #2 : The most preferred qualified RAT */
+    private int mFirstQualifiedRat;
+    /** atom #3 : Second preferred qualified RAT */
+    private int mSecondQualifiedRat;
+    /** atom #4 : Current actual transport type of Data session for this NetCapability */
+    private int mCurrentTransportType;
+    /** atom #5 : Indicates whether WFC is enabled */
+    private boolean mWfcEnabled;
+    /** atom #6 : Indicates the user's WFC mode */
+    private int mWfcMode;
+    /** atom #7 : Current Cellular AccessNetwork Type */
+    private int mCellularNetworkType;
+    /** atom #8 : Available IWLAN AccessNetwork */
+    private int mIwlanNetworkType;
+    /** atom #9 : Bit mask of restrictions on WWAN */
+    private int mRestrictionsOnWwan;
+    /** atom #10 : Bit mask of restrictions on WLAN */
+    private int mRestrictionsOnWlan;
+    /**
+     * atom #11 : Cellular network signal strength {e.g. SSRSRP in NR, RSRP in LTE, RSCP in UMTS}
+     */
+    private int mSignalStrength;
+    /** atom #12 : Cellular network signal quality {e.g. SSRSRQ in NR, RSRQ in LTE} */
+    private int mSignalQuality;
+    /** atom #13 : Cellular network signal noise ratio {e.g. SSSINR in NR, RSSNR in LTE} */
+    private int mSignalNoise;
+    /** atom #14 : Iwlan network signal strength (Wi-Fi RSSI) */
+    private int mIwlanSignalStrength;
+    /** atom #15 : Reason for preferred RAT update */
+    private int mUpdateReason;
+    /** atom #16: IMS Call Type */
+    private int mImsCallType;
+    /** atom #17 : IMS Call Quality */
+    private int mImsCallQuality;
+    /** atom #18 : Slot Index */
+    private int mSlotIndex;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mNetCapability = DEFAULT_NET_CAPABILITY;
+        mFirstQualifiedRat = DEFAULT_FIRST_QUALIFIED_RAT;
+        mSecondQualifiedRat = DEFAULT_SECOND_QUALIFIED_RAT;
+        mCurrentTransportType = DEFAULT_CURRENT_TRANSPORT_TYPE;
+        mWfcEnabled = DEFAULT_WFC_ENABLED;
+        mWfcMode = DEFAULT_WFC_MODE;
+        mCellularNetworkType = DEFAULT_CELLULAR_NETWORK_TYPE;
+        mIwlanNetworkType = DEFAULT_IWLAN_NETWORK_TYPE;
+        mRestrictionsOnWwan = DEFAULT_RESTRICTIONS_ON_WWAN;
+        mRestrictionsOnWlan = DEFAULT_RESTRICTIONS_ON_WLAN;
+        mSignalStrength = DEFAULT_SIGNAL_STRENGTH;
+        mSignalQuality = DEFAULT_SIGNAL_QUALITY;
+        mSignalNoise = DEFAULT_SIGNAL_NOISE;
+        mIwlanSignalStrength = DEFAULT_IWLAN_SIGNAL_STRENGTH;
+        mUpdateReason = DEFAULT_UPDATE_REASON;
+        mImsCallType = 1;
+        mImsCallQuality = 0;
+        mSlotIndex = 0;
+        mInfoEmpty = new AtomsQualifiedRatListChangedInfo();
+        mInfoDefault =
+                new AtomsQualifiedRatListChangedInfo(
+                        mNetCapability,
+                        mFirstQualifiedRat,
+                        mSecondQualifiedRat,
+                        mCurrentTransportType,
+                        mWfcEnabled,
+                        mWfcMode,
+                        mCellularNetworkType,
+                        mIwlanNetworkType,
+                        mRestrictionsOnWwan,
+                        mRestrictionsOnWlan,
+                        mSignalStrength,
+                        mSignalQuality,
+                        mSignalNoise,
+                        mIwlanSignalStrength,
+                        mUpdateReason,
+                        mImsCallType,
+                        mImsCallQuality,
+                        mSlotIndex);
+        mInfoCopy = new AtomsQualifiedRatListChangedInfo(mInfoDefault);
+    }
+
+    @After
+    public void tearDown() {
+        mInfoEmpty = null;
+        mInfoDefault = null;
+        mInfoCopy = null;
+    }
+
+    @Test
+    public void testGetSetNetCapability() {
+        assertEquals(0, mInfoEmpty.getNetCapability());
+        assertEquals(DEFAULT_NET_CAPABILITY, mInfoDefault.getNetCapability());
+        assertEquals(DEFAULT_NET_CAPABILITY, mInfoCopy.getNetCapability());
+
+        mInfoEmpty.setNetCapability(QnsProtoEnums.NET_CAPABILITY_IMS);
+        mInfoDefault.setNetCapability(QnsProtoEnums.NET_CAPABILITY_EIMS);
+        mInfoCopy.setNetCapability(QnsProtoEnums.NET_CAPABILITY_XCAP);
+
+        assertEquals(QnsProtoEnums.NET_CAPABILITY_IMS, mInfoEmpty.getNetCapability());
+        assertEquals(QnsProtoEnums.NET_CAPABILITY_EIMS, mInfoDefault.getNetCapability());
+        assertEquals(QnsProtoEnums.NET_CAPABILITY_XCAP, mInfoCopy.getNetCapability());
+    }
+
+    @Test
+    public void testGetSetFirstQualifiedRat() {
+        assertEquals(0, mInfoEmpty.getFirstQualifiedRat());
+        assertEquals(DEFAULT_FIRST_QUALIFIED_RAT, mInfoDefault.getFirstQualifiedRat());
+        assertEquals(DEFAULT_FIRST_QUALIFIED_RAT, mInfoCopy.getFirstQualifiedRat());
+
+        mInfoEmpty.setFirstQualifiedRat(QnsProtoEnums.IWLAN);
+        mInfoDefault.setFirstQualifiedRat(QnsProtoEnums.EUTRAN);
+        mInfoCopy.setFirstQualifiedRat(QnsProtoEnums.NGRAN);
+
+        assertEquals(QnsProtoEnums.IWLAN, mInfoEmpty.getFirstQualifiedRat());
+        assertEquals(QnsProtoEnums.EUTRAN, mInfoDefault.getFirstQualifiedRat());
+        assertEquals(QnsProtoEnums.NGRAN, mInfoCopy.getFirstQualifiedRat());
+    }
+
+    @Test
+    public void testGetSetSecondQualifiedRat() {
+        assertEquals(0, mInfoEmpty.getSecondQualifiedRat());
+        assertEquals(DEFAULT_SECOND_QUALIFIED_RAT, mInfoDefault.getSecondQualifiedRat());
+        assertEquals(DEFAULT_SECOND_QUALIFIED_RAT, mInfoCopy.getSecondQualifiedRat());
+
+        mInfoEmpty.setSecondQualifiedRat(QnsProtoEnums.IWLAN);
+        mInfoDefault.setSecondQualifiedRat(QnsProtoEnums.EUTRAN);
+        mInfoCopy.setSecondQualifiedRat(QnsProtoEnums.NGRAN);
+
+        assertEquals(QnsProtoEnums.IWLAN, mInfoEmpty.getSecondQualifiedRat());
+        assertEquals(QnsProtoEnums.EUTRAN, mInfoDefault.getSecondQualifiedRat());
+        assertEquals(QnsProtoEnums.NGRAN, mInfoCopy.getSecondQualifiedRat());
+    }
+
+    @Test
+    public void testGetSetCurrentTransportType() {
+        assertEquals(0, mInfoEmpty.getCurrentTransportType());
+        assertEquals(DEFAULT_CURRENT_TRANSPORT_TYPE, mInfoDefault.getCurrentTransportType());
+        assertEquals(DEFAULT_CURRENT_TRANSPORT_TYPE, mInfoCopy.getCurrentTransportType());
+
+        mInfoEmpty.setCurrentTransportType(QnsProtoEnums.TRANSPORT_TYPE_INVALID);
+        mInfoDefault.setCurrentTransportType(QnsProtoEnums.TRANSPORT_TYPE_WWAN);
+        mInfoCopy.setCurrentTransportType(QnsProtoEnums.TRANSPORT_TYPE_WLAN);
+
+        assertEquals(QnsProtoEnums.TRANSPORT_TYPE_INVALID, mInfoEmpty.getCurrentTransportType());
+        assertEquals(QnsProtoEnums.TRANSPORT_TYPE_WWAN, mInfoDefault.getCurrentTransportType());
+        assertEquals(QnsProtoEnums.TRANSPORT_TYPE_WLAN, mInfoCopy.getCurrentTransportType());
+    }
+
+    @Test
+    public void testGetSetWfcEnabled() {
+        assertFalse(mInfoEmpty.getWfcEnabled());
+        assertTrue(mInfoDefault.getWfcEnabled());
+        assertTrue(mInfoCopy.getWfcEnabled());
+
+        mInfoEmpty.setWfcEnabled(true);
+        mInfoDefault.setWfcEnabled(true);
+        mInfoCopy.setWfcEnabled(true);
+
+        assertTrue(mInfoEmpty.getWfcEnabled());
+        assertTrue(mInfoDefault.getWfcEnabled());
+        assertTrue(mInfoCopy.getWfcEnabled());
+    }
+
+    @Test
+    public void testGetSetWfcMode() {
+        assertEquals(0, mInfoEmpty.getWfcMode());
+        assertEquals(DEFAULT_WFC_MODE, mInfoDefault.getWfcMode());
+        assertEquals(DEFAULT_WFC_MODE, mInfoCopy.getWfcMode());
+
+        mInfoEmpty.setWfcMode(QnsProtoEnums.CELLULAR_PREFERRED);
+        mInfoDefault.setWfcMode(QnsProtoEnums.WIFI_ONLY);
+        mInfoCopy.setWfcMode(QnsProtoEnums.WIFI_PREFERRED);
+
+        assertEquals(QnsProtoEnums.CELLULAR_PREFERRED, mInfoEmpty.getWfcMode());
+        assertEquals(QnsProtoEnums.WIFI_ONLY, mInfoDefault.getWfcMode());
+        assertEquals(QnsProtoEnums.WIFI_PREFERRED, mInfoCopy.getWfcMode());
+    }
+
+    @Test
+    public void testGetSetCellularNetworkType() {
+        assertEquals(0, mInfoEmpty.getCellularNetworkType());
+        assertEquals(DEFAULT_CELLULAR_NETWORK_TYPE, mInfoDefault.getCellularNetworkType());
+        assertEquals(DEFAULT_CELLULAR_NETWORK_TYPE, mInfoCopy.getCellularNetworkType());
+
+        mInfoEmpty.setCellularNetworkType(QnsProtoEnums.EUTRAN);
+        mInfoDefault.setCellularNetworkType(QnsProtoEnums.NGRAN);
+        mInfoCopy.setCellularNetworkType(QnsProtoEnums.UTRAN);
+
+        assertEquals(QnsProtoEnums.EUTRAN, mInfoEmpty.getCellularNetworkType());
+        assertEquals(QnsProtoEnums.NGRAN, mInfoDefault.getCellularNetworkType());
+        assertEquals(QnsProtoEnums.UTRAN, mInfoCopy.getCellularNetworkType());
+    }
+
+    @Test
+    public void testGetSetIwlanNetworkType() {
+        assertEquals(0, mInfoEmpty.getIwlanNetworkType());
+        assertEquals(DEFAULT_IWLAN_NETWORK_TYPE, mInfoDefault.getIwlanNetworkType());
+        assertEquals(DEFAULT_IWLAN_NETWORK_TYPE, mInfoCopy.getIwlanNetworkType());
+
+        mInfoEmpty.setIwlanNetworkType(QnsProtoEnums.EUTRAN);
+        mInfoDefault.setIwlanNetworkType(QnsProtoEnums.NGRAN);
+        mInfoCopy.setIwlanNetworkType(QnsProtoEnums.UTRAN);
+
+        assertEquals(QnsProtoEnums.EUTRAN, mInfoEmpty.getIwlanNetworkType());
+        assertEquals(QnsProtoEnums.NGRAN, mInfoDefault.getIwlanNetworkType());
+        assertEquals(QnsProtoEnums.UTRAN, mInfoCopy.getIwlanNetworkType());
+    }
+
+    @Test
+    public void testGetSetRestrictionsOnWwan() {
+        assertEquals(0, mInfoEmpty.getRestrictionsOnWwan());
+        assertEquals(DEFAULT_RESTRICTIONS_ON_WWAN, mInfoDefault.getRestrictionsOnWwan());
+        assertEquals(DEFAULT_RESTRICTIONS_ON_WWAN, mInfoCopy.getRestrictionsOnWwan());
+
+        mInfoEmpty.setRestrictionsOnWwan(QnsProtoEnums.RESTRICT_TYPE_GUARDING);
+        mInfoDefault.setRestrictionsOnWwan(QnsProtoEnums.RESTRICT_TYPE_THROTTLING);
+        mInfoCopy.setRestrictionsOnWwan(QnsProtoEnums.RESTRICT_TYPE_NONE);
+
+        assertEquals(QnsProtoEnums.RESTRICT_TYPE_GUARDING, mInfoEmpty.getRestrictionsOnWwan());
+        assertEquals(QnsProtoEnums.RESTRICT_TYPE_THROTTLING, mInfoDefault.getRestrictionsOnWwan());
+        assertEquals(QnsProtoEnums.RESTRICT_TYPE_NONE, mInfoCopy.getRestrictionsOnWwan());
+    }
+
+    @Test
+    public void testGetSetRestrictionsOnWlan() {
+        assertEquals(0, mInfoEmpty.getRestrictionsOnWlan());
+        assertEquals(DEFAULT_RESTRICTIONS_ON_WWAN, mInfoDefault.getRestrictionsOnWlan());
+        assertEquals(DEFAULT_RESTRICTIONS_ON_WWAN, mInfoCopy.getRestrictionsOnWlan());
+
+        mInfoEmpty.setRestrictionsOnWlan(QnsProtoEnums.RESTRICT_TYPE_GUARDING);
+        mInfoDefault.setRestrictionsOnWlan(QnsProtoEnums.RESTRICT_TYPE_THROTTLING);
+        mInfoCopy.setRestrictionsOnWlan(QnsProtoEnums.RESTRICT_TYPE_NONE);
+
+        assertEquals(QnsProtoEnums.RESTRICT_TYPE_GUARDING, mInfoEmpty.getRestrictionsOnWlan());
+        assertEquals(QnsProtoEnums.RESTRICT_TYPE_THROTTLING, mInfoDefault.getRestrictionsOnWlan());
+        assertEquals(QnsProtoEnums.RESTRICT_TYPE_NONE, mInfoCopy.getRestrictionsOnWlan());
+    }
+
+    @Test
+    public void testGetSetSignalStrength() {
+        assertEquals(0, mInfoEmpty.getSignalStrength());
+        assertEquals(DEFAULT_SIGNAL_STRENGTH, mInfoDefault.getSignalStrength());
+        assertEquals(DEFAULT_SIGNAL_STRENGTH, mInfoCopy.getSignalStrength());
+
+        mInfoEmpty.setSignalStrength(DEFAULT_SIGNAL_STRENGTH);
+        mInfoDefault.setSignalStrength(-120);
+        mInfoCopy.setSignalStrength(-110);
+
+        assertEquals(DEFAULT_SIGNAL_STRENGTH, mInfoEmpty.getSignalStrength());
+        assertEquals(-120, mInfoDefault.getSignalStrength());
+        assertEquals(-110, mInfoCopy.getSignalStrength());
+    }
+
+    @Test
+    public void testGetSetSignalQuality() {
+        assertEquals(0, mInfoEmpty.getSignalQuality());
+        assertEquals(DEFAULT_SIGNAL_QUALITY, mInfoDefault.getSignalQuality());
+        assertEquals(DEFAULT_SIGNAL_QUALITY, mInfoCopy.getSignalQuality());
+
+        mInfoEmpty.setSignalQuality(DEFAULT_SIGNAL_QUALITY);
+        mInfoDefault.setSignalQuality(-10);
+        mInfoCopy.setSignalQuality(-5);
+
+        assertEquals(DEFAULT_SIGNAL_QUALITY, mInfoEmpty.getSignalQuality());
+        assertEquals(-10, mInfoDefault.getSignalQuality());
+        assertEquals(-5, mInfoCopy.getSignalQuality());
+    }
+
+    @Test
+    public void testGetSetSignalNoise() {
+        assertEquals(0, mInfoEmpty.getSignalNoise());
+        assertEquals(DEFAULT_SIGNAL_NOISE, mInfoDefault.getSignalNoise());
+        assertEquals(DEFAULT_SIGNAL_NOISE, mInfoCopy.getSignalNoise());
+
+        mInfoEmpty.setSignalNoise(DEFAULT_SIGNAL_NOISE);
+        mInfoDefault.setSignalNoise(1);
+        mInfoCopy.setSignalNoise(-1);
+
+        assertEquals(DEFAULT_SIGNAL_NOISE, mInfoEmpty.getSignalNoise());
+        assertEquals(1, mInfoDefault.getSignalNoise());
+        assertEquals(-1, mInfoCopy.getSignalNoise());
+    }
+
+    @Test
+    public void testGetSetIwlanSignalStrength() {
+        assertEquals(0, mInfoEmpty.getIwlanSignalStrength());
+        assertEquals(DEFAULT_IWLAN_SIGNAL_STRENGTH, mInfoDefault.getIwlanSignalStrength());
+        assertEquals(DEFAULT_IWLAN_SIGNAL_STRENGTH, mInfoCopy.getIwlanSignalStrength());
+
+        mInfoEmpty.setIwlanSignalStrength(DEFAULT_IWLAN_SIGNAL_STRENGTH);
+        mInfoDefault.setIwlanSignalStrength(-80);
+        mInfoCopy.setIwlanSignalStrength(-50);
+
+        assertEquals(DEFAULT_IWLAN_SIGNAL_STRENGTH, mInfoEmpty.getIwlanSignalStrength());
+        assertEquals(-80, mInfoDefault.getIwlanSignalStrength());
+        assertEquals(-50, mInfoCopy.getIwlanSignalStrength());
+    }
+
+    @Test
+    public void testGetSetUpdateReason() {
+        assertEquals(0, mInfoEmpty.getUpdateReason());
+        assertEquals(DEFAULT_UPDATE_REASON, mInfoDefault.getUpdateReason());
+        assertEquals(DEFAULT_UPDATE_REASON, mInfoCopy.getUpdateReason());
+
+        mInfoEmpty.setUpdateReason(0);
+        mInfoDefault.setUpdateReason(0);
+        mInfoCopy.setUpdateReason(0);
+
+        assertEquals(0, mInfoEmpty.getUpdateReason());
+        assertEquals(0, mInfoDefault.getUpdateReason());
+        assertEquals(0, mInfoCopy.getUpdateReason());
+    }
+
+    @Test
+    public void testGetSetImsCallType() {
+        assertEquals(0, mInfoEmpty.getImsCallType());
+        assertEquals(1, mInfoDefault.getImsCallType());
+        assertEquals(1, mInfoCopy.getImsCallType());
+
+        mInfoEmpty.setImsCallType(1);
+        mInfoDefault.setImsCallType(2);
+        mInfoCopy.setImsCallType(3);
+
+        assertEquals(1, mInfoEmpty.getImsCallType());
+        assertEquals(2, mInfoDefault.getImsCallType());
+        assertEquals(3, mInfoCopy.getImsCallType());
+    }
+
+    @Test
+    public void testGetSetImsCallQuality() {
+        assertEquals(0, mInfoEmpty.getImsCallQuality());
+        assertEquals(0, mInfoDefault.getImsCallQuality());
+        assertEquals(0, mInfoCopy.getImsCallQuality());
+
+        mInfoEmpty.setImsCallQuality(1);
+        mInfoDefault.setImsCallQuality(2);
+        mInfoCopy.setImsCallQuality(3);
+
+        assertEquals(1, mInfoEmpty.getImsCallQuality());
+        assertEquals(2, mInfoDefault.getImsCallQuality());
+        assertEquals(3, mInfoCopy.getImsCallQuality());
+    }
+
+    @Test
+    public void testGetSetSlotIndex() {
+        assertEquals(0, mInfoEmpty.getSlotIndex());
+        assertEquals(0, mInfoDefault.getSlotIndex());
+        assertEquals(0, mInfoCopy.getSlotIndex());
+
+        mInfoEmpty.setSlotIndex(1);
+        mInfoDefault.setSlotIndex(2);
+        mInfoCopy.setSlotIndex(3);
+
+        assertEquals(1, mInfoEmpty.getSlotIndex());
+        assertEquals(2, mInfoDefault.getSlotIndex());
+        assertEquals(3, mInfoCopy.getSlotIndex());
+    }
+
+    @Test
+    public void testToString() {
+        String strInfoEmpty = mInfoEmpty.toString();
+        String strInfoDefault = mInfoDefault.toString();
+        String strInfoCopy = mInfoCopy.toString();
+
+        assertNotNull(strInfoEmpty);
+        assertNotNull(strInfoDefault);
+        assertNotNull(strInfoCopy);
+
+        assertTrue(strInfoDefault.startsWith("AtomsQualifiedRatListChangedInfo"));
+
+        assertEquals(strInfoDefault, strInfoCopy);
+        assertNotEquals(strInfoEmpty, strInfoDefault);
+    }
+
+    @Test
+    public void testEquals() {
+        assertEquals(mInfoDefault, mInfoDefault);
+        assertNotEquals(mInfoDefault, new Object());
+        assertEquals(mInfoDefault, mInfoCopy);
+        mInfoDefault.setWfcEnabled(true);
+        mInfoCopy.setWfcEnabled(false);
+        assertNotEquals(mInfoDefault, mInfoCopy);
+    }
+
+    @Test
+    public void testHashCode() {
+        AtomsQualifiedRatListChangedInfo a1, a2;
+
+        a1 =
+                new AtomsQualifiedRatListChangedInfo(
+                        mNetCapability,
+                        mFirstQualifiedRat,
+                        mSecondQualifiedRat,
+                        mCurrentTransportType,
+                        mWfcEnabled,
+                        mWfcMode,
+                        mCellularNetworkType,
+                        mIwlanNetworkType,
+                        mRestrictionsOnWwan,
+                        mRestrictionsOnWlan,
+                        mSignalStrength,
+                        mSignalQuality,
+                        mSignalNoise,
+                        mIwlanSignalStrength,
+                        mUpdateReason,
+                        mImsCallType,
+                        mImsCallQuality,
+                        mSlotIndex);
+        a2 =
+                new AtomsQualifiedRatListChangedInfo(
+                        mNetCapability,
+                        mSecondQualifiedRat,
+                        mFirstQualifiedRat,
+                        mCurrentTransportType,
+                        mWfcEnabled,
+                        mWfcMode,
+                        mCellularNetworkType,
+                        mIwlanNetworkType,
+                        mRestrictionsOnWwan,
+                        mRestrictionsOnWlan,
+                        mSignalStrength,
+                        mSignalQuality,
+                        mSignalNoise,
+                        mIwlanSignalStrength,
+                        mUpdateReason,
+                        mImsCallType,
+                        mImsCallQuality,
+                        mSlotIndex);
+        assertNotEquals(a1.hashCode(), a2.hashCode());
+        a2 =
+                new AtomsQualifiedRatListChangedInfo(
+                        mNetCapability,
+                        mFirstQualifiedRat,
+                        mSecondQualifiedRat,
+                        mCurrentTransportType,
+                        mWfcEnabled,
+                        mWfcMode,
+                        mCellularNetworkType,
+                        mIwlanNetworkType,
+                        mRestrictionsOnWwan,
+                        mRestrictionsOnWlan,
+                        mSignalStrength,
+                        mSignalQuality,
+                        mSignalNoise,
+                        mIwlanSignalStrength,
+                        mUpdateReason,
+                        mImsCallType,
+                        mImsCallQuality,
+                        mSlotIndex);
+        assertEquals(a1.hashCode(), a2.hashCode());
+    }
+
+    @Test
+    public void testStatsId() {
+        final int statsId = 634; // QualifiedRatListChanged
+        assertEquals(statsId, mInfoDefault.copy().getStatsId());
+    }
+
+    @Test
+    public void testStatsEventBuilder() {
+        mInfoDefault.build(mStatsEventBuilder);
+
+        verify(mStatsEventBuilder, times(1)).writeBoolean(anyBoolean());
+        verify(mStatsEventBuilder, times(17)).writeInt(anyInt());
+    }
+}