Merge changes I71545bee,Ic26cddab,I57b36e66,I71367761,I07e7aa8c into main

* changes:
  Use NetlinkUtil to process dump messages in InetDiagMessage.
  Add mif6ctl struct definition.
  Add mf6cctl struct definition.
  Add Mrt6Msg struct for multicast routing support.
  Add support to dump all multicast routes in NetlinkUtils
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index 414e50a..73c11ba 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -94,14 +94,17 @@
         "ConnectivityNextEnableDefaults",
         "TetheringAndroidLibraryDefaults",
         "TetheringApiLevel",
-        "TetheringReleaseTargetSdk"
+        "TetheringReleaseTargetSdk",
     ],
     static_libs: [
         "NetworkStackApiCurrentShims",
         "net-utils-device-common-struct",
     ],
     apex_available: ["com.android.tethering"],
-    lint: { strict_updatability_linting: true },
+    lint: {
+        strict_updatability_linting: true,
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 android_library {
@@ -109,14 +112,17 @@
     defaults: [
         "TetheringAndroidLibraryDefaults",
         "TetheringApiLevel",
-        "TetheringReleaseTargetSdk"
+        "TetheringReleaseTargetSdk",
     ],
     static_libs: [
         "NetworkStackApiStableShims",
         "net-utils-device-common-struct",
     ],
     apex_available: ["com.android.tethering"],
-    lint: { strict_updatability_linting: true },
+    lint: {
+        strict_updatability_linting: true,
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 // Due to b/143733063, APK can't access a jni lib that is in APEX (but not in the APK).
@@ -189,20 +195,28 @@
     optimize: {
         proguard_flags_files: ["proguard.flags"],
     },
-    lint: { strict_updatability_linting: true },
+    lint: {
+        strict_updatability_linting: true,
+    },
 }
 
 // Updatable tethering packaged for finalized API
 android_app {
     name: "Tethering",
-    defaults: ["TetheringAppDefaults", "TetheringApiLevel"],
+    defaults: [
+        "TetheringAppDefaults",
+        "TetheringApiLevel",
+    ],
     static_libs: ["TetheringApiStableLib"],
     certificate: "networkstack",
     manifest: "AndroidManifest.xml",
     use_embedded_native_libs: true,
     privapp_allowlist: ":privapp_allowlist_com.android.tethering",
     apex_available: ["com.android.tethering"],
-    lint: { strict_updatability_linting: true },
+    lint: {
+        strict_updatability_linting: true,
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 android_app {
@@ -221,6 +235,7 @@
     lint: {
         strict_updatability_linting: true,
         error_checks: ["NewApi"],
+        baseline_filename: "lint-baseline.xml",
     },
 }
 
@@ -239,19 +254,24 @@
 
 java_library_static {
     name: "tetheringstatsprotos",
-    proto: {type: "lite"},
+    proto: {
+        type: "lite",
+    },
     srcs: [
         "src/com/android/networkstack/tethering/metrics/stats.proto",
     ],
     static_libs: ["tetheringprotos"],
     apex_available: ["com.android.tethering"],
     min_sdk_version: "30",
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 genrule {
     name: "statslog-tethering-java-gen",
     tools: ["stats-log-api-gen"],
     cmd: "$(location stats-log-api-gen) --java $(out) --module network_tethering" +
-         " --javaPackage com.android.networkstack.tethering.metrics --javaClass TetheringStatsLog",
+        " --javaPackage com.android.networkstack.tethering.metrics --javaClass TetheringStatsLog",
     out: ["com/android/networkstack/tethering/metrics/TetheringStatsLog.java"],
 }
diff --git a/framework-t/Android.bp b/framework-t/Android.bp
index deda74e..0e1921a 100644
--- a/framework-t/Android.bp
+++ b/framework-t/Android.bp
@@ -57,6 +57,10 @@
         "app-compat-annotations",
         "androidx.annotation_annotation",
     ],
+    static_libs: [
+        // Cannot go to framework-connectivity because mid_sdk checks require 31.
+        "modules-utils-binary-xml",
+    ],
     impl_only_libs: [
         // The build system will use framework-bluetooth module_current stubs, because
         // of sdk_version: "module_current" above.
diff --git a/framework-t/src/android/net/NetworkStatsCollection.java b/framework-t/src/android/net/NetworkStatsCollection.java
index b6f6dbb..cb8328b 100644
--- a/framework-t/src/android/net/NetworkStatsCollection.java
+++ b/framework-t/src/android/net/NetworkStatsCollection.java
@@ -60,6 +60,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FileRotator;
+import com.android.modules.utils.FastDataInput;
 import com.android.net.module.util.CollectionUtils;
 import com.android.net.module.util.NetworkStatsUtils;
 
@@ -116,15 +117,28 @@
     private long mEndMillis;
     private long mTotalBytes;
     private boolean mDirty;
+    private final boolean mUseFastDataInput;
 
     /**
      * Construct a {@link NetworkStatsCollection} object.
      *
-     * @param bucketDuration duration of the buckets in this object, in milliseconds.
+     * @param bucketDurationMillis duration of the buckets in this object, in milliseconds.
      * @hide
      */
     public NetworkStatsCollection(long bucketDurationMillis) {
+        this(bucketDurationMillis, false /* useFastDataInput */);
+    }
+
+    /**
+     * Construct a {@link NetworkStatsCollection} object.
+     *
+     * @param bucketDurationMillis duration of the buckets in this object, in milliseconds.
+     * @param useFastDataInput true if using {@link FastDataInput} is preferred. Otherwise, false.
+     * @hide
+     */
+    public NetworkStatsCollection(long bucketDurationMillis, boolean useFastDataInput) {
         mBucketDurationMillis = bucketDurationMillis;
+        mUseFastDataInput = useFastDataInput;
         reset();
     }
 
@@ -483,7 +497,11 @@
     /** @hide */
     @Override
     public void read(InputStream in) throws IOException {
-        read((DataInput) new DataInputStream(in));
+        if (mUseFastDataInput) {
+            read(FastDataInput.obtain(in));
+        } else {
+            read((DataInput) new DataInputStream(in));
+        }
     }
 
     private void read(DataInput in) throws IOException {
diff --git a/framework/Android.bp b/framework/Android.bp
index 1e6262d..7ec3971 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -105,7 +105,9 @@
     apex_available: [
         "com.android.tethering",
     ],
-    lint: { strict_updatability_linting: true },
+    lint: {
+        strict_updatability_linting: true,
+    },
 }
 
 java_library {
@@ -134,7 +136,10 @@
         "framework-tethering.impl",
         "framework-wifi.stubs.module_lib",
     ],
-    visibility: ["//packages/modules/Connectivity:__subpackages__"]
+    visibility: ["//packages/modules/Connectivity:__subpackages__"],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 java_defaults {
@@ -189,6 +194,9 @@
         "//packages/modules/NetworkStack/tests:__subpackages__",
         "//packages/modules/Wifi/service/tests/wifitests",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 platform_compat_config {
@@ -248,6 +256,9 @@
     apex_available: [
         "com.android.tethering",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 java_genrule {
@@ -293,9 +304,9 @@
     ],
     flags: [
         "--show-for-stub-purposes-annotation android.annotation.SystemApi" +
-        "\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\)",
+            "\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\)",
         "--show-for-stub-purposes-annotation android.annotation.SystemApi" +
-        "\\(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES\\)",
+            "\\(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES\\)",
     ],
     aidl: {
         include_dirs: [
@@ -308,6 +319,9 @@
 java_library {
     name: "framework-connectivity-module-api-stubs-including-flagged",
     srcs: [":framework-connectivity-module-api-stubs-including-flagged-droidstubs"],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 // Library providing limited APIs within the connectivity module, so that R+ components like
@@ -332,4 +346,7 @@
     visibility: [
         "//packages/modules/Connectivity/Tethering:__subpackages__",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
diff --git a/nearby/service/Android.bp b/nearby/service/Android.bp
index 4630902..17b80b0 100644
--- a/nearby/service/Android.bp
+++ b/nearby/service/Android.bp
@@ -30,7 +30,7 @@
     srcs: [":nearby-service-srcs"],
 
     defaults: [
-        "framework-system-server-module-defaults"
+        "framework-system-server-module-defaults",
     ],
     libs: [
         "androidx.annotation_annotation",
@@ -66,13 +66,16 @@
     apex_available: [
         "com.android.tethering",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 genrule {
     name: "statslog-nearby-java-gen",
     tools: ["stats-log-api-gen"],
     cmd: "$(location stats-log-api-gen) --java $(out) --module nearby " +
-         " --javaPackage com.android.server.nearby.proto --javaClass NearbyStatsLog" +
-         " --minApiLevel 33",
+        " --javaPackage com.android.server.nearby.proto --javaClass NearbyStatsLog" +
+        " --minApiLevel 33",
     out: ["com/android/server/nearby/proto/NearbyStatsLog.java"],
 }
diff --git a/netd/NetdUpdatable.cpp b/netd/NetdUpdatable.cpp
index 8b9e5a7..3b15916 100644
--- a/netd/NetdUpdatable.cpp
+++ b/netd/NetdUpdatable.cpp
@@ -31,8 +31,8 @@
 
     android::netdutils::Status ret = sBpfHandler.init(cg2_path);
     if (!android::netdutils::isOk(ret)) {
-        LOG(ERROR) << __func__ << ": Failed. " << ret.code() << " " << ret.msg();
-        return -ret.code();
+        LOG(ERROR) << __func__ << ": Failed: (" << ret.code() << ") " << ret.msg();
+        abort();
     }
     return 0;
 }
diff --git a/service-t/Android.bp b/service-t/Android.bp
index bc49f0e..de879f3 100644
--- a/service-t/Android.bp
+++ b/service-t/Android.bp
@@ -31,6 +31,7 @@
     ],
     visibility: ["//visibility:private"],
 }
+
 // The above filegroup can be used to specify different sources depending
 // on the branch, while minimizing merge conflicts in the rest of the
 // build rules.
@@ -78,6 +79,9 @@
         "//packages/modules/Connectivity/tests:__subpackages__",
         "//packages/modules/IPsec/tests/iketests",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 // Test building mDNS as a standalone, so that it can be imported into other repositories as-is.
@@ -94,11 +98,12 @@
     min_sdk_version: "21",
     lint: {
         error_checks: ["NewApi"],
+        baseline_filename: "lint-baseline.xml",
     },
     srcs: [
         "src/com/android/server/connectivity/mdns/**/*.java",
         ":framework-connectivity-t-mdns-standalone-build-sources",
-        ":service-mdns-droidstubs"
+        ":service-mdns-droidstubs",
     ],
     exclude_srcs: [
         "src/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitor.java",
@@ -127,7 +132,7 @@
     srcs: ["src/com/android/server/connectivity/mdns/SocketNetLinkMonitorFactory.java"],
     libs: [
         "net-utils-device-common-mdns-standalone-build-test",
-        "service-connectivity-tiramisu-pre-jarjar"
+        "service-connectivity-tiramisu-pre-jarjar",
     ],
     visibility: [
         "//visibility:private",
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index 6086207..630fa37 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -1716,6 +1716,8 @@
                         mContext, MdnsFeatureFlags.NSD_EXPIRED_SERVICES_REMOVAL))
                 .setIsLabelCountLimitEnabled(mDeps.isTetheringFeatureNotChickenedOut(
                         mContext, MdnsFeatureFlags.NSD_LIMIT_LABEL_COUNT))
+                .setIsKnownAnswerSuppressionEnabled(mDeps.isFeatureEnabled(
+                        mContext, MdnsFeatureFlags.NSD_KNOWN_ANSWER_SUPPRESSION))
                 .build();
         mMdnsSocketClient =
                 new MdnsMultinetworkSocketClient(handler.getLooper(), mMdnsSocketProvider,
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java b/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
index 0a6d8c1..1ad47a3 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
@@ -41,6 +41,11 @@
      */
     public static final String NSD_LIMIT_LABEL_COUNT = "nsd_limit_label_count";
 
+    /**
+     * A feature flag to control whether the known-answer suppression should be enabled.
+     */
+    public static final String NSD_KNOWN_ANSWER_SUPPRESSION = "nsd_known_answer_suppression";
+
     // Flag for offload feature
     public final boolean mIsMdnsOffloadFeatureEnabled;
 
@@ -53,17 +58,22 @@
     // Flag for label count limit
     public final boolean mIsLabelCountLimitEnabled;
 
+    // Flag for known-answer suppression
+    public final boolean mIsKnownAnswerSuppressionEnabled;
+
     /**
      * The constructor for {@link MdnsFeatureFlags}.
      */
     public MdnsFeatureFlags(boolean isOffloadFeatureEnabled,
             boolean includeInetAddressRecordsInProbing,
             boolean isExpiredServicesRemovalEnabled,
-            boolean isLabelCountLimitEnabled) {
+            boolean isLabelCountLimitEnabled,
+            boolean isKnownAnswerSuppressionEnabled) {
         mIsMdnsOffloadFeatureEnabled = isOffloadFeatureEnabled;
         mIncludeInetAddressRecordsInProbing = includeInetAddressRecordsInProbing;
         mIsExpiredServicesRemovalEnabled = isExpiredServicesRemovalEnabled;
         mIsLabelCountLimitEnabled = isLabelCountLimitEnabled;
+        mIsKnownAnswerSuppressionEnabled = isKnownAnswerSuppressionEnabled;
     }
 
 
@@ -79,6 +89,7 @@
         private boolean mIncludeInetAddressRecordsInProbing;
         private boolean mIsExpiredServicesRemovalEnabled;
         private boolean mIsLabelCountLimitEnabled;
+        private boolean mIsKnownAnswerSuppressionEnabled;
 
         /**
          * The constructor for {@link Builder}.
@@ -88,6 +99,7 @@
             mIncludeInetAddressRecordsInProbing = false;
             mIsExpiredServicesRemovalEnabled = false;
             mIsLabelCountLimitEnabled = true; // Default enabled.
+            mIsKnownAnswerSuppressionEnabled = false;
         }
 
         /**
@@ -132,13 +144,24 @@
         }
 
         /**
+         * Set whether the known-answer suppression is enabled.
+         *
+         * @see #NSD_KNOWN_ANSWER_SUPPRESSION
+         */
+        public Builder setIsKnownAnswerSuppressionEnabled(boolean isKnownAnswerSuppressionEnabled) {
+            mIsKnownAnswerSuppressionEnabled = isKnownAnswerSuppressionEnabled;
+            return this;
+        }
+
+        /**
          * Builds a {@link MdnsFeatureFlags} with the arguments supplied to this builder.
          */
         public MdnsFeatureFlags build() {
             return new MdnsFeatureFlags(mIsMdnsOffloadFeatureEnabled,
                     mIncludeInetAddressRecordsInProbing,
                     mIsExpiredServicesRemovalEnabled,
-                    mIsLabelCountLimitEnabled);
+                    mIsLabelCountLimitEnabled,
+                    mIsKnownAnswerSuppressionEnabled);
         }
     }
 }
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
index 1909208..d46a7b5 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
@@ -90,6 +90,7 @@
     private final Looper mLooper;
     @NonNull
     private final String[] mDeviceHostname;
+    @NonNull
     private final MdnsFeatureFlags mMdnsFeatureFlags;
 
     public MdnsRecordRepository(@NonNull Looper looper, @NonNull String[] deviceHostname,
@@ -502,7 +503,7 @@
             // Add answers from general records
             addReplyFromService(question, mGeneralRecords, null /* servicePtrRecord */,
                     null /* serviceSrvRecord */, null /* serviceTxtRecord */, replyUnicast, now,
-                    answerInfo, additionalAnswerRecords);
+                    answerInfo, additionalAnswerRecords, Collections.emptyList());
 
             // Add answers from each service
             for (int i = 0; i < mServices.size(); i++) {
@@ -510,7 +511,7 @@
                 if (registration.exiting || registration.isProbing) continue;
                 if (addReplyFromService(question, registration.allRecords, registration.ptrRecords,
                         registration.srvRecord, registration.txtRecord, replyUnicast, now,
-                        answerInfo, additionalAnswerRecords)) {
+                        answerInfo, additionalAnswerRecords, packet.answers)) {
                     registration.repliedServiceCount++;
                     registration.sentPacketCount++;
                 }
@@ -563,6 +564,15 @@
         return new MdnsReplyInfo(answerRecords, additionalAnswerRecords, delayMs, dest);
     }
 
+    private boolean isKnownAnswer(MdnsRecord answer, @NonNull List<MdnsRecord> knownAnswerRecords) {
+        for (MdnsRecord knownAnswer : knownAnswerRecords) {
+            if (answer.equals(knownAnswer) && knownAnswer.getTtl() > (answer.getTtl() / 2)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Add answers and additional answers for a question, from a ServiceRegistration.
      */
@@ -572,7 +582,8 @@
             @Nullable RecordInfo<MdnsServiceRecord> serviceSrvRecord,
             @Nullable RecordInfo<MdnsTextRecord> serviceTxtRecord,
             boolean replyUnicast, long now, @NonNull List<RecordInfo<?>> answerInfo,
-            @NonNull List<MdnsRecord> additionalAnswerRecords) {
+            @NonNull List<MdnsRecord> additionalAnswerRecords,
+            @NonNull List<MdnsRecord> knownAnswerRecords) {
         boolean hasDnsSdPtrRecordAnswer = false;
         boolean hasDnsSdSrvRecordAnswer = false;
         boolean hasFullyOwnedNameMatch = false;
@@ -601,6 +612,20 @@
             }
 
             hasKnownAnswer = true;
+
+            // RFC6762 7.1. Known-Answer Suppression:
+            // A Multicast DNS responder MUST NOT answer a Multicast DNS query if
+            // the answer it would give is already included in the Answer Section
+            // with an RR TTL at least half the correct value.  If the RR TTL of the
+            // answer as given in the Answer Section is less than half of the true
+            // RR TTL as known by the Multicast DNS responder, the responder MUST
+            // send an answer so as to update the querier's cache before the record
+            // becomes in danger of expiration.
+            if (mMdnsFeatureFlags.mIsKnownAnswerSuppressionEnabled
+                    && isKnownAnswer(info.record, knownAnswerRecords)) {
+                continue;
+            }
+
             hasDnsSdPtrRecordAnswer |= (servicePtrRecords != null
                     && CollectionUtils.any(servicePtrRecords, r -> info == r));
             hasDnsSdSrvRecordAnswer |= (info == serviceSrvRecord);
@@ -612,8 +637,6 @@
                 continue;
             }
 
-            // TODO: Don't reply if in known answers of the querier (7.1) if TTL is > half
-
             answerInfo.add(info);
         }
 
diff --git a/service-t/src/com/android/server/ethernet/EthernetTracker.java b/service-t/src/com/android/server/ethernet/EthernetTracker.java
index 48e86d8..458d64f 100644
--- a/service-t/src/com/android/server/ethernet/EthernetTracker.java
+++ b/service-t/src/com/android/server/ethernet/EthernetTracker.java
@@ -48,6 +48,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.NetdUtils;
 import com.android.net.module.util.PermissionUtils;
 import com.android.net.module.util.SharedLog;
@@ -237,7 +238,18 @@
         mDeps = deps;
 
         // Interface match regex.
-        mIfaceMatch = mDeps.getInterfaceRegexFromResource(mContext);
+        String ifaceMatchRegex = mDeps.getInterfaceRegexFromResource(mContext);
+        // "*" is a magic string to indicate "pick the default".
+        if (ifaceMatchRegex.equals("*")) {
+            if (SdkLevel.isAtLeastV()) {
+                // On V+, include both usb%d and eth%d interfaces.
+                ifaceMatchRegex = "(usb|eth)\\d+";
+            } else {
+                // On T and U, include only eth%d interfaces.
+                ifaceMatchRegex = "eth\\d+";
+            }
+        }
+        mIfaceMatch = ifaceMatchRegex;
 
         // Read default Ethernet interface configuration from resources
         final String[] interfaceConfigs = mDeps.getInterfaceConfigFromResource(context);
diff --git a/service-t/src/com/android/server/net/NetworkStatsRecorder.java b/service-t/src/com/android/server/net/NetworkStatsRecorder.java
index 3da1585..5f9b65c 100644
--- a/service-t/src/com/android/server/net/NetworkStatsRecorder.java
+++ b/service-t/src/com/android/server/net/NetworkStatsRecorder.java
@@ -79,6 +79,7 @@
     private final long mBucketDuration;
     private final boolean mOnlyTags;
     private final boolean mWipeOnError;
+    private final boolean mUseFastDataInput;
 
     private long mPersistThresholdBytes = 2 * MB_IN_BYTES;
     private NetworkStats mLastSnapshot;
@@ -104,6 +105,7 @@
         mBucketDuration = YEAR_IN_MILLIS;
         mOnlyTags = false;
         mWipeOnError = true;
+        mUseFastDataInput = false;
 
         mPending = null;
         mSinceBoot = new NetworkStatsCollection(mBucketDuration);
@@ -116,7 +118,7 @@
      */
     public NetworkStatsRecorder(FileRotator rotator, NonMonotonicObserver<String> observer,
             DropBoxManager dropBox, String cookie, long bucketDuration, boolean onlyTags,
-            boolean wipeOnError) {
+            boolean wipeOnError, boolean useFastDataInput) {
         mRotator = Objects.requireNonNull(rotator, "missing FileRotator");
         mObserver = Objects.requireNonNull(observer, "missing NonMonotonicObserver");
         mDropBox = Objects.requireNonNull(dropBox, "missing DropBoxManager");
@@ -125,6 +127,7 @@
         mBucketDuration = bucketDuration;
         mOnlyTags = onlyTags;
         mWipeOnError = wipeOnError;
+        mUseFastDataInput = useFastDataInput;
 
         mPending = new NetworkStatsCollection(bucketDuration);
         mSinceBoot = new NetworkStatsCollection(bucketDuration);
@@ -195,8 +198,12 @@
     }
 
     private NetworkStatsCollection loadLocked(long start, long end) {
-        if (LOGD) Log.d(TAG, "loadLocked() reading from disk for " + mCookie);
-        final NetworkStatsCollection res = new NetworkStatsCollection(mBucketDuration);
+        if (LOGD) {
+            Log.d(TAG, "loadLocked() reading from disk for " + mCookie
+                    + " useFastDataInput: " + mUseFastDataInput);
+        }
+        final NetworkStatsCollection res =
+                new NetworkStatsCollection(mBucketDuration, mUseFastDataInput);
         try {
             mRotator.readMatching(res, start, end);
             res.recordCollection(mPending);
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index 2c9f30c..ad83e7f 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -969,7 +969,7 @@
         return new NetworkStatsRecorder(new FileRotator(
                 baseDir, prefix, config.rotateAgeMillis, config.deleteAgeMillis),
                 mNonMonotonicObserver, dropBox, prefix, config.bucketDuration, includeTags,
-                wipeOnError);
+                wipeOnError, false /* useFastDataInput */);
     }
 
     @GuardedBy("mStatsLock")
diff --git a/service/Android.bp b/service/Android.bp
index e2dab9e..7c5da0d 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -70,6 +70,9 @@
     apex_available: [
         "com.android.tethering",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 // The library name match the service-connectivity jarjar rules that put the JNI utils in the
@@ -200,7 +203,10 @@
     apex_available: [
         "com.android.tethering",
     ],
-    lint: { strict_updatability_linting: true },
+    lint: {
+        strict_updatability_linting: true,
+        baseline_filename: "lint-baseline.xml",
+    },
     visibility: [
         "//packages/modules/Connectivity/service-t",
         "//packages/modules/Connectivity/tests:__subpackages__",
@@ -225,6 +231,7 @@
     ],
     lint: {
         strict_updatability_linting: true,
+        baseline_filename: "lint-baseline.xml",
     },
 }
 
@@ -283,12 +290,18 @@
 java_library {
     name: "service-connectivity-for-tests",
     defaults: ["service-connectivity-defaults"],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 java_library {
     name: "service-connectivity",
     defaults: ["service-connectivity-defaults"],
     installable: true,
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 java_library_static {
@@ -303,6 +316,9 @@
     ],
     static_libs: ["ConnectivityServiceprotos"],
     apex_available: ["com.android.tethering"],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 genrule {
diff --git a/service/ServiceConnectivityResources/res/values/config.xml b/service/ServiceConnectivityResources/res/values/config.xml
index f30abc6..6f9d46f 100644
--- a/service/ServiceConnectivityResources/res/values/config.xml
+++ b/service/ServiceConnectivityResources/res/values/config.xml
@@ -194,8 +194,11 @@
         -->
     </string-array>
 
-    <!-- Regex of wired ethernet ifaces -->
-    <string translatable="false" name="config_ethernet_iface_regex">eth\\d</string>
+    <!-- Regex of wired ethernet ifaces. Network interfaces that match this regex will be tracked
+         by ethernet service.
+         If set to "*", ethernet service uses "(eth|usb)\\d+" on Android V+ and eth\\d+ on
+         Android T and U. -->
+    <string translatable="false" name="config_ethernet_iface_regex">*</string>
 
     <!-- Ignores Wi-Fi validation failures after roam.
     If validation fails on a Wi-Fi network after a roam to a new BSSID,
diff --git a/service/src/com/android/metrics/NetworkRequestStateInfo.java b/service/src/com/android/metrics/NetworkRequestStateInfo.java
new file mode 100644
index 0000000..e3e172a
--- /dev/null
+++ b/service/src/com/android/metrics/NetworkRequestStateInfo.java
@@ -0,0 +1,99 @@
+/*
+ * 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.metrics;
+
+import static com.android.server.ConnectivityStatsLog.NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_RECEIVED;
+import static com.android.server.ConnectivityStatsLog.NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_REMOVED;
+import static com.android.server.ConnectivityStatsLog.NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_UNKNOWN;
+
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.os.SystemClock;
+
+import com.android.net.module.util.BitUtils;
+
+
+class NetworkRequestStateInfo {
+    private final NetworkRequest mNetworkRequest;
+    private final long mNetworkRequestReceivedTime;
+
+    private enum NetworkRequestState {
+        RECEIVED,
+        REMOVED
+    }
+    private NetworkRequestState mNetworkRequestState;
+    private int mNetworkRequestDurationMillis;
+    private final Dependencies mDependencies;
+
+    NetworkRequestStateInfo(NetworkRequest networkRequest,
+            Dependencies deps) {
+        mDependencies = deps;
+        mNetworkRequest = networkRequest;
+        mNetworkRequestReceivedTime = mDependencies.getElapsedRealtime();
+        mNetworkRequestDurationMillis = 0;
+        mNetworkRequestState = NetworkRequestState.RECEIVED;
+    }
+
+    public void setNetworkRequestRemoved() {
+        mNetworkRequestState = NetworkRequestState.REMOVED;
+        mNetworkRequestDurationMillis = (int) (
+                mDependencies.getElapsedRealtime() - mNetworkRequestReceivedTime);
+    }
+
+    public int getNetworkRequestStateStatsType() {
+        if (mNetworkRequestState == NetworkRequestState.RECEIVED) {
+            return NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_RECEIVED;
+        } else if (mNetworkRequestState == NetworkRequestState.REMOVED) {
+            return NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_REMOVED;
+        } else {
+            return NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_UNKNOWN;
+        }
+    }
+
+    public int getRequestId() {
+        return mNetworkRequest.requestId;
+    }
+
+    public int getPackageUid() {
+        return mNetworkRequest.networkCapabilities.getRequestorUid();
+    }
+
+    public int getTransportTypes() {
+        return (int) BitUtils.packBits(mNetworkRequest.networkCapabilities.getTransportTypes());
+    }
+
+    public boolean getNetCapabilityNotMetered() {
+        return mNetworkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+    }
+
+    public boolean getNetCapabilityInternet() {
+        return mNetworkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+    }
+
+    public int getNetworkRequestDurationMillis() {
+        return mNetworkRequestDurationMillis;
+    }
+
+    /** Dependency class */
+    public static class Dependencies {
+        // Returns a timestamp with the time base of SystemClock.elapsedRealtime to keep durations
+        // relative to start time and avoid timezone change, including time spent in deep sleep.
+        public long getElapsedRealtime() {
+            return SystemClock.elapsedRealtime();
+        }
+    }
+}
diff --git a/service/src/com/android/metrics/NetworkRequestStateStatsMetrics.java b/service/src/com/android/metrics/NetworkRequestStateStatsMetrics.java
new file mode 100644
index 0000000..361ad22
--- /dev/null
+++ b/service/src/com/android/metrics/NetworkRequestStateStatsMetrics.java
@@ -0,0 +1,192 @@
+/*
+ * 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.metrics;
+
+import static com.android.server.ConnectivityStatsLog.NETWORK_REQUEST_STATE_CHANGED;
+
+import android.annotation.NonNull;
+import android.net.NetworkRequest;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.ConnectivityStatsLog;
+
+/**
+ * A Connectivity Service helper class to push atoms capturing network requests have been received
+ * and removed and its metadata.
+ *
+ * Atom events are logged in the ConnectivityStatsLog. Network request id: network request metadata
+ * hashmap is stored to calculate network request duration when it is removed.
+ *
+ * Note that this class is not thread-safe. The instance of the class needs to be
+ * synchronized in the callers when being used in multiple threads.
+ */
+public class NetworkRequestStateStatsMetrics {
+
+    private static final String TAG = "NetworkRequestStateStatsMetrics";
+    private static final int MSG_NETWORK_REQUEST_STATE_CHANGED = 0;
+
+    // 1 second internal is suggested by experiment team
+    private static final int ATOM_INTERVAL_MS = 1000;
+    private final SparseArray<NetworkRequestStateInfo> mNetworkRequestsActive;
+
+    private final Handler mStatsLoggingHandler;
+
+    private final Dependencies mDependencies;
+
+    private final NetworkRequestStateInfo.Dependencies mNRStateInfoDeps;
+
+    public NetworkRequestStateStatsMetrics() {
+        this(new Dependencies(), new NetworkRequestStateInfo.Dependencies());
+    }
+
+    @VisibleForTesting
+    NetworkRequestStateStatsMetrics(Dependencies deps,
+            NetworkRequestStateInfo.Dependencies nrStateInfoDeps) {
+        mNetworkRequestsActive = new SparseArray<>();
+        mDependencies = deps;
+        mNRStateInfoDeps = nrStateInfoDeps;
+        HandlerThread handlerThread = mDependencies.makeHandlerThread(TAG);
+        handlerThread.start();
+        mStatsLoggingHandler = new StatsLoggingHandler(handlerThread.getLooper());
+    }
+
+    /**
+     * Register network request receive event, push RECEIVE atom
+     *
+     * @param networkRequest network request received
+     */
+    public void onNetworkRequestReceived(NetworkRequest networkRequest) {
+        if (mNetworkRequestsActive.contains(networkRequest.requestId)) {
+            Log.w(TAG, "Received already registered network request, id = "
+                    + networkRequest.requestId);
+        } else {
+            Log.d(TAG, "Registered nr with ID = " + networkRequest.requestId
+                    + ", package_uid = " + networkRequest.networkCapabilities.getRequestorUid());
+            NetworkRequestStateInfo networkRequestStateInfo = new NetworkRequestStateInfo(
+                    networkRequest, mNRStateInfoDeps);
+            mNetworkRequestsActive.put(networkRequest.requestId, networkRequestStateInfo);
+            mStatsLoggingHandler.sendMessage(
+                    Message.obtain(
+                            mStatsLoggingHandler,
+                            MSG_NETWORK_REQUEST_STATE_CHANGED,
+                            networkRequestStateInfo));
+        }
+    }
+
+    /**
+     * Register network request remove event, push REMOVE atom
+     *
+     * @param networkRequest network request removed
+     */
+    public void onNetworkRequestRemoved(NetworkRequest networkRequest) {
+        NetworkRequestStateInfo networkRequestStateInfo = mNetworkRequestsActive.get(
+                networkRequest.requestId);
+        if (networkRequestStateInfo == null) {
+            Log.w(TAG, "This NR hasn't been registered. NR id = " + networkRequest.requestId);
+        } else {
+            Log.d(TAG, "Removed nr with ID = " + networkRequest.requestId);
+
+            mNetworkRequestsActive.remove(networkRequest.requestId);
+            networkRequestStateInfo.setNetworkRequestRemoved();
+            mStatsLoggingHandler.sendMessage(
+                    Message.obtain(
+                            mStatsLoggingHandler,
+                            MSG_NETWORK_REQUEST_STATE_CHANGED,
+                            networkRequestStateInfo));
+
+        }
+    }
+
+    /** Dependency class */
+    public static class Dependencies {
+        /**
+         * Creates a thread with provided tag.
+         *
+         * @param tag for the thread.
+         */
+        public HandlerThread makeHandlerThread(@NonNull final String tag) {
+            return new HandlerThread(tag);
+        }
+
+        /**
+         * Sleeps the thread for provided intervalMs millis.
+         *
+         * @param intervalMs number of millis for the thread sleep.
+         */
+        public void threadSleep(int intervalMs) {
+            try {
+                Thread.sleep(intervalMs);
+            } catch (InterruptedException e) {
+                Log.w(TAG, "Cool down interrupted!", e);
+            }
+        }
+
+        /**
+         * Writes a NETWORK_REQUEST_STATE_CHANGED event to ConnectivityStatsLog.
+         *
+         * @param networkRequestStateInfo NetworkRequestStateInfo containing network request info.
+         */
+        public void writeStats(NetworkRequestStateInfo networkRequestStateInfo) {
+            ConnectivityStatsLog.write(
+                    NETWORK_REQUEST_STATE_CHANGED,
+                    networkRequestStateInfo.getPackageUid(),
+                    networkRequestStateInfo.getTransportTypes(),
+                    networkRequestStateInfo.getNetCapabilityNotMetered(),
+                    networkRequestStateInfo.getNetCapabilityInternet(),
+                    networkRequestStateInfo.getNetworkRequestStateStatsType(),
+                    networkRequestStateInfo.getNetworkRequestDurationMillis());
+        }
+    }
+
+    private class StatsLoggingHandler extends Handler {
+        private static final String TAG = "NetworkRequestsStateStatsLoggingHandler";
+        private long mLastLogTime = 0;
+
+        StatsLoggingHandler(Looper looper) {
+            super(looper);
+        }
+
+        private void checkStatsLoggingTimeout() {
+            // Cool down before next execution. Required by atom logging frequency.
+            long now = SystemClock.elapsedRealtime();
+            if (now - mLastLogTime < ATOM_INTERVAL_MS) {
+                mDependencies.threadSleep(ATOM_INTERVAL_MS);
+            }
+            mLastLogTime = now;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            NetworkRequestStateInfo loggingInfo;
+            switch (msg.what) {
+                case MSG_NETWORK_REQUEST_STATE_CHANGED:
+                    checkStatsLoggingTimeout();
+                    loggingInfo = (NetworkRequestStateInfo) msg.obj;
+                    mDependencies.writeStats(loggingInfo);
+                    break;
+                default: // fall out
+            }
+        }
+    }
+}
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 0ec0f13..b4efa34 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -281,6 +281,7 @@
 import com.android.metrics.NetworkDescription;
 import com.android.metrics.NetworkList;
 import com.android.metrics.NetworkRequestCount;
+import com.android.metrics.NetworkRequestStateStatsMetrics;
 import com.android.metrics.RequestCountForType;
 import com.android.modules.utils.BasicShellCommandHandler;
 import com.android.modules.utils.build.SdkLevel;
@@ -941,6 +942,8 @@
 
     private final IpConnectivityLog mMetricsLog;
 
+    private final NetworkRequestStateStatsMetrics mNetworkRequestStateStatsMetrics;
+
     @GuardedBy("mBandwidthRequests")
     private final SparseArray<Integer> mBandwidthRequests = new SparseArray<>(10);
 
@@ -1422,6 +1425,19 @@
         }
 
         /**
+         * @see NetworkRequestStateStatsMetrics
+         */
+        public NetworkRequestStateStatsMetrics makeNetworkRequestStateStatsMetrics(
+                Context context) {
+            // We currently have network requests metric for Watch devices only
+            if (context.getPackageManager().hasSystemFeature(FEATURE_WATCH)) {
+                return  new NetworkRequestStateStatsMetrics();
+            } else {
+                return null;
+            }
+        }
+
+        /**
          * @see BatteryStatsManager
          */
         public void reportNetworkInterfaceForTransports(Context context, String iface,
@@ -1654,6 +1670,7 @@
                 new RequestInfoPerUidCounter(MAX_NETWORK_REQUESTS_PER_SYSTEM_UID - 1);
 
         mMetricsLog = logger;
+        mNetworkRequestStateStatsMetrics = mDeps.makeNetworkRequestStateStatsMetrics(mContext);
         final NetworkRequest defaultInternetRequest = createDefaultRequest();
         mDefaultRequest = new NetworkRequestInfo(
                 Process.myUid(), defaultInternetRequest, null,
@@ -3517,7 +3534,7 @@
                 NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
     }
 
-    private boolean checkSystemBarServicePermission(int pid, int uid) {
+    private boolean checkStatusBarServicePermission(int pid, int uid) {
         return checkAnyPermissionOf(mContext, pid, uid,
                 android.Manifest.permission.STATUS_BAR_SERVICE);
     }
@@ -5324,6 +5341,8 @@
                             updateSignalStrengthThresholds(network, "REGISTER", req);
                         }
                     }
+                } else if (req.isRequest() && mNetworkRequestStateStatsMetrics != null) {
+                    mNetworkRequestStateStatsMetrics.onNetworkRequestReceived(req);
                 }
             }
 
@@ -5541,6 +5560,8 @@
             }
             if (req.isListen()) {
                 removeListenRequestFromNetworks(req);
+            } else if (req.isRequest() && mNetworkRequestStateStatsMetrics != null) {
+                mNetworkRequestStateStatsMetrics.onNetworkRequestRemoved(req);
             }
         }
         nri.unlinkDeathRecipient();
@@ -11702,7 +11723,7 @@
             return true;
         }
         if (mAllowSysUiConnectivityReports
-                && checkSystemBarServicePermission(callbackPid, callbackUid)) {
+                && checkStatusBarServicePermission(callbackPid, callbackUid)) {
             return true;
         }
 
diff --git a/staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java b/staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java
index c6a2042..dbd83d0 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java
@@ -27,6 +27,7 @@
 import static com.android.net.module.util.netlink.NetlinkConstants.NLMSG_DONE;
 import static com.android.net.module.util.netlink.NetlinkConstants.SOCK_DESTROY;
 import static com.android.net.module.util.netlink.NetlinkConstants.SOCK_DIAG_BY_FAMILY;
+import static com.android.net.module.util.netlink.NetlinkConstants.SOCKDIAG_MSG_HEADER_SIZE;
 import static com.android.net.module.util.netlink.NetlinkConstants.hexify;
 import static com.android.net.module.util.netlink.NetlinkConstants.stringForAddressFamily;
 import static com.android.net.module.util.netlink.NetlinkConstants.stringForProtocol;
@@ -59,6 +60,7 @@
 import java.net.UnknownHostException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -156,7 +158,8 @@
     }
 
     public StructInetDiagMsg inetDiagMsg;
-
+    // The netlink attributes.
+    public List<StructNlAttr> nlAttrs = new ArrayList<>();
     @VisibleForTesting
     public InetDiagMessage(@NonNull StructNlMsgHdr header) {
         super(header);
@@ -174,6 +177,16 @@
         if (msg.inetDiagMsg == null) {
             return null;
         }
+        final int payloadLength = header.nlmsg_len - SOCKDIAG_MSG_HEADER_SIZE;
+        final ByteBuffer payload = byteBuffer.slice();
+        while (payload.position() < payloadLength) {
+            final StructNlAttr attr = StructNlAttr.parse(payload);
+            // Stop parsing for truncated or malformed attribute
+            if (attr == null)  return null;
+
+            msg.nlAttrs.add(attr);
+        }
+
         return msg;
     }
 
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/InetDiagSocketTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/InetDiagSocketTest.java
index 65e99f8..b44e428 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/InetDiagSocketTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/InetDiagSocketTest.java
@@ -32,6 +32,7 @@
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -345,28 +346,28 @@
     // Hexadecimal representation of InetDiagMessage
     private static final String INET_DIAG_MSG_HEX1 =
             // struct nlmsghdr
-            "58000000" +     // length = 88
-            "1400" +         // type = SOCK_DIAG_BY_FAMILY
-            "0200" +         // flags = NLM_F_MULTI
-            "00000000" +     // seqno
-            "f5220000" +     // pid
+            "58000000"     // length = 88
+            + "1400"         // type = SOCK_DIAG_BY_FAMILY
+            + "0200"         // flags = NLM_F_MULTI
+            + "00000000"     // seqno
+            + "f5220000"     // pid
             // struct inet_diag_msg
-            "0a" +           // family = AF_INET6
-            "01" +           // idiag_state = 1
-            "02" +           // idiag_timer = 2
-            "ff" +           // idiag_retrans = 255
+            + "0a"           // family = AF_INET6
+            + "01"           // idiag_state = 1
+            + "02"           // idiag_timer = 2
+            + "ff"           // idiag_retrans = 255
                 // inet_diag_sockid
-                "a817" +     // idiag_sport = 43031
-                "960f" +     // idiag_dport = 38415
-                "20010db8000000000000000000000001" + // idiag_src = 2001:db8::1
-                "20010db8000000000000000000000002" + // idiag_dst = 2001:db8::2
-                "07000000" + // idiag_if = 7
-                "5800000000000000" + // idiag_cookie = 88
-            "04000000" +     // idiag_expires = 4
-            "05000000" +     // idiag_rqueue = 5
-            "06000000" +     // idiag_wqueue = 6
-            "a3270000" +     // idiag_uid = 10147
-            "a57e19f0";      // idiag_inode = 4028202661
+                + "a817"     // idiag_sport = 43031
+                + "960f"     // idiag_dport = 38415
+                + "20010db8000000000000000000000001" // idiag_src = 2001:db8::1
+                + "20010db8000000000000000000000002" // idiag_dst = 2001:db8::2
+                + "07000000" // idiag_if = 7
+                + "5800000000000000" // idiag_cookie = 88
+            + "04000000"     // idiag_expires = 4
+            + "05000000"     // idiag_rqueue = 5
+            + "06000000"     // idiag_wqueue = 6
+            + "a3270000"     // idiag_uid = 10147
+            + "a57e19f0";    // idiag_inode = 4028202661
 
     private void assertInetDiagMsg1(final NetlinkMessage msg) {
         assertNotNull(msg);
@@ -394,33 +395,45 @@
         assertEquals(6, inetDiagMsg.inetDiagMsg.idiag_wqueue);
         assertEquals(10147, inetDiagMsg.inetDiagMsg.idiag_uid);
         assertEquals(4028202661L, inetDiagMsg.inetDiagMsg.idiag_inode);
+
+        // Verify the length of attribute list is 0 as expected since message doesn't
+        // take any attributes
+        assertEquals(0, inetDiagMsg.nlAttrs.size());
     }
 
     // Hexadecimal representation of InetDiagMessage
     private static final String INET_DIAG_MSG_HEX2 =
             // struct nlmsghdr
-            "58000000" +     // length = 88
-            "1400" +         // type = SOCK_DIAG_BY_FAMILY
-            "0200" +         // flags = NLM_F_MULTI
-            "00000000" +     // seqno
-            "f5220000" +     // pid
+            "6C000000"       // length = 108
+            + "1400"         // type = SOCK_DIAG_BY_FAMILY
+            + "0200"         // flags = NLM_F_MULTI
+            + "00000000"     // seqno
+            + "f5220000"     // pid
             // struct inet_diag_msg
-            "0a" +           // family = AF_INET6
-            "02" +           // idiag_state = 2
-            "10" +           // idiag_timer = 16
-            "20" +           // idiag_retrans = 32
+            + "0a"           // family = AF_INET6
+            + "02"           // idiag_state = 2
+            + "10"           // idiag_timer = 16
+            + "20"           // idiag_retrans = 32
                 // inet_diag_sockid
-                "a845" +     // idiag_sport = 43077
-                "01bb" +     // idiag_dport = 443
-                "20010db8000000000000000000000003" + // idiag_src = 2001:db8::3
-                "20010db8000000000000000000000004" + // idiag_dst = 2001:db8::4
-                "08000000" + // idiag_if = 8
-                "6300000000000000" + // idiag_cookie = 99
-            "30000000" +     // idiag_expires = 48
-            "40000000" +     // idiag_rqueue = 64
-            "50000000" +     // idiag_wqueue = 80
-            "39300000" +     // idiag_uid = 12345
-            "851a0000";      // idiag_inode = 6789
+                + "a845"     // idiag_sport = 43077
+                + "01bb"     // idiag_dport = 443
+                + "20010db8000000000000000000000003" // idiag_src = 2001:db8::3
+                + "20010db8000000000000000000000004" // idiag_dst = 2001:db8::4
+                + "08000000" // idiag_if = 8
+                + "6300000000000000" // idiag_cookie = 99
+            + "30000000"     // idiag_expires = 48
+            + "40000000"     // idiag_rqueue = 64
+            + "50000000"     // idiag_wqueue = 80
+            + "39300000"     // idiag_uid = 12345
+            + "851a0000"     // idiag_inode = 6789
+            + "0500"           // len = 5
+            + "0800"         // type = 8
+            + "00000000"     // data
+            + "0800"         // len = 8
+            + "0F00"         // type = 15(INET_DIAG_MARK)
+            + "850A0C00"     // data, socket mark=789125
+            + "0400"         // len = 4
+            + "0200";        // type = 2
 
     private void assertInetDiagMsg2(final NetlinkMessage msg) {
         assertNotNull(msg);
@@ -448,6 +461,104 @@
         assertEquals(80, inetDiagMsg.inetDiagMsg.idiag_wqueue);
         assertEquals(12345, inetDiagMsg.inetDiagMsg.idiag_uid);
         assertEquals(6789, inetDiagMsg.inetDiagMsg.idiag_inode);
+
+        // Verify the number of nlAttr and their content.
+        assertEquals(3, inetDiagMsg.nlAttrs.size());
+
+        assertEquals(5, inetDiagMsg.nlAttrs.get(0).nla_len);
+        assertEquals(8, inetDiagMsg.nlAttrs.get(0).nla_type);
+        assertArrayEquals(
+                HexEncoding.decode("00".toCharArray(), false),
+                inetDiagMsg.nlAttrs.get(0).nla_value);
+        assertEquals(8, inetDiagMsg.nlAttrs.get(1).nla_len);
+        assertEquals(15, inetDiagMsg.nlAttrs.get(1).nla_type);
+        assertArrayEquals(
+                HexEncoding.decode("850A0C00".toCharArray(), false),
+                inetDiagMsg.nlAttrs.get(1).nla_value);
+        assertEquals(4, inetDiagMsg.nlAttrs.get(2).nla_len);
+        assertEquals(2, inetDiagMsg.nlAttrs.get(2).nla_type);
+        assertNull(inetDiagMsg.nlAttrs.get(2).nla_value);
+    }
+
+    // Hexadecimal representation of InetDiagMessage
+    private static final String INET_DIAG_MSG_HEX_MALFORMED =
+            // struct nlmsghdr
+            "6E000000"       // length = 110
+            + "1400"         // type = SOCK_DIAG_BY_FAMILY
+            + "0200"         // flags = NLM_F_MULTI
+            + "00000000"     // seqno
+            + "f5220000"     // pid
+            // struct inet_diag_msg
+            + "0a"           // family = AF_INET6
+            + "02"           // idiag_state = 2
+            + "10"           // idiag_timer = 16
+            + "20"           // idiag_retrans = 32
+            // inet_diag_sockid
+            + "a845"     // idiag_sport = 43077
+            + "01bb"     // idiag_dport = 443
+            + "20010db8000000000000000000000005" // idiag_src = 2001:db8::5
+            + "20010db8000000000000000000000006" // idiag_dst = 2001:db8::6
+            + "08000000" // idiag_if = 8
+            + "6300000000000000" // idiag_cookie = 99
+            + "30000000"     // idiag_expires = 48
+            + "40000000"     // idiag_rqueue = 64
+            + "50000000"     // idiag_wqueue = 80
+            + "39300000"     // idiag_uid = 12345
+            + "851a0000"     // idiag_inode = 6789
+            + "0500"           // len = 5
+            + "0800"         // type = 8
+            + "00000000"     // data
+            + "0800"         // len = 8
+            + "0F00"         // type = 15(INET_DIAG_MARK)
+            + "850A0C00"     // data, socket mark=789125
+            + "0400"         // len = 4
+            + "0200"         // type = 2
+            + "0100"         // len = 1, malformed value
+            + "0100";        // type = 1
+
+    @Test
+    public void testParseInetDiagResponseMalformedNlAttr() throws Exception {
+        final ByteBuffer byteBuffer = ByteBuffer.wrap(
+                HexEncoding.decode((INET_DIAG_MSG_HEX_MALFORMED).toCharArray(), false));
+        byteBuffer.order(ByteOrder.nativeOrder());
+        assertNull(NetlinkMessage.parse(byteBuffer, NETLINK_INET_DIAG));
+    }
+
+    // Hexadecimal representation of InetDiagMessage
+    private static final String INET_DIAG_MSG_HEX_TRUNCATED =
+            // struct nlmsghdr
+            "5E000000"       // length = 96
+            + "1400"         // type = SOCK_DIAG_BY_FAMILY
+            + "0200"         // flags = NLM_F_MULTI
+            + "00000000"     // seqno
+            + "f5220000"     // pid
+            // struct inet_diag_msg
+            + "0a"           // family = AF_INET6
+            + "02"           // idiag_state = 2
+            + "10"           // idiag_timer = 16
+            + "20"           // idiag_retrans = 32
+            // inet_diag_sockid
+            + "a845"     // idiag_sport = 43077
+            + "01bb"     // idiag_dport = 443
+            + "20010db8000000000000000000000005" // idiag_src = 2001:db8::5
+            + "20010db8000000000000000000000006" // idiag_dst = 2001:db8::6
+            + "08000000" // idiag_if = 8
+            + "6300000000000000" // idiag_cookie = 99
+            + "30000000"     // idiag_expires = 48
+            + "40000000"     // idiag_rqueue = 64
+            + "50000000"     // idiag_wqueue = 80
+            + "39300000"     // idiag_uid = 12345
+            + "851a0000"     // idiag_inode = 6789
+            + "0800"         // len = 8
+            + "0100"         // type = 1
+            + "000000";      // data, less than the expected length
+
+    @Test
+    public void testParseInetDiagResponseTruncatedNlAttr() throws Exception {
+        final ByteBuffer byteBuffer = ByteBuffer.wrap(
+                HexEncoding.decode((INET_DIAG_MSG_HEX_TRUNCATED).toCharArray(), false));
+        byteBuffer.order(ByteOrder.nativeOrder());
+        assertNull(NetlinkMessage.parse(byteBuffer, NETLINK_INET_DIAG));
     }
 
     private static final byte[] INET_DIAG_MSG_BYTES =
diff --git a/tests/benchmark/src/android/net/netstats/benchmarktests/NetworkStatsTest.kt b/tests/benchmark/src/android/net/netstats/benchmarktests/NetworkStatsTest.kt
index 585157f..c3ea9f4 100644
--- a/tests/benchmark/src/android/net/netstats/benchmarktests/NetworkStatsTest.kt
+++ b/tests/benchmark/src/android/net/netstats/benchmarktests/NetworkStatsTest.kt
@@ -133,7 +133,16 @@
     }
 
     @Test
-    fun testReadFromRecorder_manyUids() {
+    fun testReadFromRecorder_manyUids_useDataInput() {
+        doTestReadFromRecorder_manyUids(useFastDataInput = false)
+    }
+
+    @Test
+    fun testReadFromRecorder_manyUids_useFastDataInput() {
+        doTestReadFromRecorder_manyUids(useFastDataInput = true)
+    }
+
+    fun doTestReadFromRecorder_manyUids(useFastDataInput: Boolean) {
         val mockObserver = mock<NonMonotonicObserver<String>>()
         val mockDropBox = mock<DropBoxManager>()
         testFilesAssets.forEach {
@@ -146,7 +155,8 @@
                 PREFIX_UID,
                 UID_COLLECTION_BUCKET_DURATION_MS,
                 false /* includeTags */,
-                false /* wipeOnError */
+                false /* wipeOnError */,
+                useFastDataInput /* useFastDataInput */
             )
             recorder.orLoadCompleteLocked
         }
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
index 82f4a65..ab956bf 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
@@ -203,6 +203,7 @@
         // Initial state
         setBatterySaverMode(false);
         setRestrictBackground(false);
+        setAppIdle(false);
 
         // Get transports of the active network, this has to be done before changing meteredness,
         // since wifi will be disconnected when changing from non-metered to metered.
diff --git a/tests/unit/java/android/net/NetworkStatsCollectionTest.java b/tests/unit/java/android/net/NetworkStatsCollectionTest.java
index a6e9e95..81557f8 100644
--- a/tests/unit/java/android/net/NetworkStatsCollectionTest.java
+++ b/tests/unit/java/android/net/NetworkStatsCollectionTest.java
@@ -64,6 +64,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mockito;
@@ -90,7 +91,8 @@
 @SmallTest
 @DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
 public class NetworkStatsCollectionTest {
-
+    @Rule
+    public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
     private static final String TEST_FILE = "test.bin";
     private static final String TEST_IMSI = "310260000000000";
     private static final int TEST_SUBID = 1;
@@ -199,6 +201,33 @@
                 77017831L, 100995L, 35436758L, 92344L);
     }
 
+    private InputStream getUidInputStreamFromRes(int uidRes) throws Exception {
+        final File testFile =
+                new File(InstrumentationRegistry.getContext().getFilesDir(), TEST_FILE);
+        stageFile(uidRes, testFile);
+
+        final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
+        collection.readLegacyUid(testFile, true);
+
+        // now export into a unified format
+        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        collection.write(bos);
+        return new ByteArrayInputStream(bos.toByteArray());
+    }
+
+    @Test
+    public void testFastDataInputRead() throws Exception {
+        final NetworkStatsCollection legacyCollection =
+                new NetworkStatsCollection(30 * MINUTE_IN_MILLIS, false /* useFastDataInput */);
+        final NetworkStatsCollection fastReadCollection =
+                new NetworkStatsCollection(30 * MINUTE_IN_MILLIS, true /* useFastDataInput */);
+        final InputStream bis = getUidInputStreamFromRes(R.raw.netstats_uid_v4);
+        legacyCollection.read(bis);
+        bis.reset();
+        fastReadCollection.read(bis);
+        assertCollectionEntries(legacyCollection.getEntries(), fastReadCollection);
+    }
+
     @Test
     public void testStartEndAtomicBuckets() throws Exception {
         final NetworkStatsCollection collection = new NetworkStatsCollection(HOUR_IN_MILLIS);
diff --git a/tests/unit/java/android/net/NetworkStatsRecorderTest.java b/tests/unit/java/android/net/NetworkStatsRecorderTest.java
index fad11a3..e8f853e 100644
--- a/tests/unit/java/android/net/NetworkStatsRecorderTest.java
+++ b/tests/unit/java/android/net/NetworkStatsRecorderTest.java
@@ -64,7 +64,8 @@
 
     private NetworkStatsRecorder buildRecorder(FileRotator rotator, boolean wipeOnError) {
         return new NetworkStatsRecorder(rotator, mObserver, mDropBox, TEST_PREFIX,
-                    HOUR_IN_MILLIS, false /* includeTags */, wipeOnError);
+                HOUR_IN_MILLIS, false /* includeTags */, wipeOnError,
+                false /* useFastDataInput */);
     }
 
     @Test
diff --git a/tests/unit/java/com/android/metrics/NetworkRequestStateInfoTest.java b/tests/unit/java/com/android/metrics/NetworkRequestStateInfoTest.java
new file mode 100644
index 0000000..5709ed1
--- /dev/null
+++ b/tests/unit/java/com/android/metrics/NetworkRequestStateInfoTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.metrics;
+
+import static com.android.server.ConnectivityStatsLog.NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_RECEIVED;
+import static com.android.server.ConnectivityStatsLog.NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_REMOVED;
+
+import static org.junit.Assert.assertEquals;
+
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.os.Build;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+public class NetworkRequestStateInfoTest {
+
+    @Mock
+    private NetworkRequestStateInfo.Dependencies mDependencies;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+    }
+    @Test
+    public void testSetNetworkRequestRemoved() {
+        final long nrStartTime = 1L;
+        final long nrEndTime = 101L;
+
+        NetworkRequest notMeteredWifiNetworkRequest = new NetworkRequest(
+                new NetworkCapabilities()
+                        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                        .setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, true),
+                0, 1, NetworkRequest.Type.REQUEST
+        );
+
+        // This call will be used to calculate NR received time
+        Mockito.when(mDependencies.getElapsedRealtime()).thenReturn(nrStartTime);
+        NetworkRequestStateInfo mNetworkRequestStateInfo = new NetworkRequestStateInfo(
+                notMeteredWifiNetworkRequest, mDependencies);
+
+        // This call will be used to calculate NR removed time
+        Mockito.when(mDependencies.getElapsedRealtime()).thenReturn(nrEndTime);
+        mNetworkRequestStateInfo.setNetworkRequestRemoved();
+        assertEquals(
+                nrEndTime - nrStartTime,
+                mNetworkRequestStateInfo.getNetworkRequestDurationMillis());
+        assertEquals(mNetworkRequestStateInfo.getNetworkRequestStateStatsType(),
+                NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_REMOVED);
+    }
+
+    @Test
+    public void testCheckInitialState() {
+        NetworkRequestStateInfo mNetworkRequestStateInfo = new NetworkRequestStateInfo(
+                new NetworkRequest(new NetworkCapabilities(), 0, 1, NetworkRequest.Type.REQUEST),
+                mDependencies);
+        assertEquals(mNetworkRequestStateInfo.getNetworkRequestStateStatsType(),
+                NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_RECEIVED);
+    }
+}
diff --git a/tests/unit/java/com/android/metrics/NetworkRequestStateStatsMetricsTest.java b/tests/unit/java/com/android/metrics/NetworkRequestStateStatsMetricsTest.java
new file mode 100644
index 0000000..17a0719
--- /dev/null
+++ b/tests/unit/java/com/android/metrics/NetworkRequestStateStatsMetricsTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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.metrics;
+
+
+import static com.android.server.ConnectivityStatsLog.NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_RECEIVED;
+import static com.android.server.ConnectivityStatsLog.NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_REMOVED;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.os.HandlerThread;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.testutils.HandlerUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class NetworkRequestStateStatsMetricsTest {
+    @Mock
+    private NetworkRequestStateStatsMetrics.Dependencies mNRStateStatsDeps;
+    @Mock
+    private NetworkRequestStateInfo.Dependencies mNRStateInfoDeps;
+    @Captor
+    private ArgumentCaptor<NetworkRequestStateInfo> mNetworkRequestStateInfoCaptor;
+    private NetworkRequestStateStatsMetrics mNetworkRequestStateStatsMetrics;
+    private HandlerThread mHandlerThread;
+    private static final int TEST_REQUEST_ID = 10;
+    private static final int TEST_PACKAGE_UID = 20;
+    private static final int TIMEOUT_MS = 30_000;
+    private static final NetworkRequest NOT_METERED_WIFI_NETWORK_REQUEST = new NetworkRequest(
+            new NetworkCapabilities()
+                    .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                    .setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, true)
+                    .setCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET, false)
+                    .setRequestorUid(TEST_PACKAGE_UID),
+            0, TEST_REQUEST_ID, NetworkRequest.Type.REQUEST
+    );
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mHandlerThread = new HandlerThread("NetworkRequestStateStatsMetrics");
+        Mockito.when(mNRStateStatsDeps.makeHandlerThread("NetworkRequestStateStatsMetrics"))
+                .thenReturn(mHandlerThread);
+        mNetworkRequestStateStatsMetrics = new NetworkRequestStateStatsMetrics(
+                mNRStateStatsDeps, mNRStateInfoDeps);
+    }
+
+    @Test
+    public void testNetworkRequestReceivedRemoved() {
+        final long nrStartTime = 1L;
+        final long nrEndTime = 101L;
+        // This call will be used to calculate NR received time
+        Mockito.when(mNRStateInfoDeps.getElapsedRealtime()).thenReturn(nrStartTime);
+        mNetworkRequestStateStatsMetrics.onNetworkRequestReceived(NOT_METERED_WIFI_NETWORK_REQUEST);
+        HandlerUtils.waitForIdle(mHandlerThread, TIMEOUT_MS);
+
+        verify(mNRStateStatsDeps, times(1))
+                .writeStats(mNetworkRequestStateInfoCaptor.capture());
+
+        NetworkRequestStateInfo nrStateInfoSent = mNetworkRequestStateInfoCaptor.getValue();
+        assertEquals(NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_RECEIVED,
+                nrStateInfoSent.getNetworkRequestStateStatsType());
+        assertEquals(NOT_METERED_WIFI_NETWORK_REQUEST.requestId, nrStateInfoSent.getRequestId());
+        assertEquals(TEST_PACKAGE_UID, nrStateInfoSent.getPackageUid());
+        assertEquals(1 << NetworkCapabilities.TRANSPORT_WIFI, nrStateInfoSent.getTransportTypes());
+        assertTrue(nrStateInfoSent.getNetCapabilityNotMetered());
+        assertFalse(nrStateInfoSent.getNetCapabilityInternet());
+        assertEquals(0, nrStateInfoSent.getNetworkRequestDurationMillis());
+
+        clearInvocations(mNRStateStatsDeps);
+        // This call will be used to calculate NR removed time
+        Mockito.when(mNRStateInfoDeps.getElapsedRealtime()).thenReturn(nrEndTime);
+        mNetworkRequestStateStatsMetrics.onNetworkRequestRemoved(NOT_METERED_WIFI_NETWORK_REQUEST);
+        HandlerUtils.waitForIdle(mHandlerThread, TIMEOUT_MS);
+
+        verify(mNRStateStatsDeps, times(1))
+                .writeStats(mNetworkRequestStateInfoCaptor.capture());
+
+        nrStateInfoSent = mNetworkRequestStateInfoCaptor.getValue();
+        assertEquals(NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_REMOVED,
+                nrStateInfoSent.getNetworkRequestStateStatsType());
+        assertEquals(NOT_METERED_WIFI_NETWORK_REQUEST.requestId, nrStateInfoSent.getRequestId());
+        assertEquals(TEST_PACKAGE_UID, nrStateInfoSent.getPackageUid());
+        assertEquals(1 << NetworkCapabilities.TRANSPORT_WIFI, nrStateInfoSent.getTransportTypes());
+        assertTrue(nrStateInfoSent.getNetCapabilityNotMetered());
+        assertFalse(nrStateInfoSent.getNetCapabilityInternet());
+        assertEquals(nrEndTime - nrStartTime, nrStateInfoSent.getNetworkRequestDurationMillis());
+    }
+
+    @Test
+    public void testUnreceivedNetworkRequestRemoved() {
+        mNetworkRequestStateStatsMetrics.onNetworkRequestRemoved(NOT_METERED_WIFI_NETWORK_REQUEST);
+        HandlerUtils.waitForIdle(mHandlerThread, TIMEOUT_MS);
+        verify(mNRStateStatsDeps, never())
+                .writeStats(any(NetworkRequestStateInfo.class));
+    }
+
+    @Test
+    public void testExistingNetworkRequestReceived() {
+        mNetworkRequestStateStatsMetrics.onNetworkRequestReceived(NOT_METERED_WIFI_NETWORK_REQUEST);
+        HandlerUtils.waitForIdle(mHandlerThread, TIMEOUT_MS);
+        verify(mNRStateStatsDeps, times(1))
+                .writeStats(any(NetworkRequestStateInfo.class));
+
+        clearInvocations(mNRStateStatsDeps);
+        mNetworkRequestStateStatsMetrics.onNetworkRequestReceived(NOT_METERED_WIFI_NETWORK_REQUEST);
+        HandlerUtils.waitForIdle(mHandlerThread, TIMEOUT_MS);
+        verify(mNRStateStatsDeps, never())
+                .writeStats(any(NetworkRequestStateInfo.class));
+
+    }
+}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
index 85e361d..196f73f 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
@@ -36,6 +36,7 @@
 import kotlin.test.assertFailsWith
 import kotlin.test.assertFalse
 import kotlin.test.assertNotNull
+import kotlin.test.assertNull
 import kotlin.test.assertTrue
 import org.junit.After
 import org.junit.Before
@@ -48,6 +49,13 @@
 private const val TEST_PORT = 12345
 private const val TEST_SUBTYPE = "_subtype"
 private const val TEST_SUBTYPE2 = "_subtype2"
+// RFC6762 10. Resource Record TTL Values and Cache Coherency
+// The recommended TTL value for Multicast DNS resource records with a host name as the resource
+// record's name (e.g., A, AAAA, HINFO) or a host name contained within the resource record's rdata
+// (e.g., SRV, reverse mapping PTR record) SHOULD be 120 seconds. The recommended TTL value for
+// other Multicast DNS resource records is 75 minutes.
+private const val LONG_TTL = 4_500_000L
+private const val SHORT_TTL = 120_000L
 private val TEST_HOSTNAME = arrayOf("Android_000102030405060708090A0B0C0D0E0F", "local")
 private val TEST_ADDRESSES = listOf(
         LinkAddress(parseNumericAddress("192.0.2.111"), 24),
@@ -120,7 +128,7 @@
         assertEquals(MdnsServiceRecord(expectedName,
                 0L /* receiptTimeMillis */,
                 false /* cacheFlush */,
-                120_000L /* ttlMillis */,
+                SHORT_TTL /* ttlMillis */,
                 0 /* servicePriority */, 0 /* serviceWeight */,
                 TEST_PORT, TEST_HOSTNAME), packet.authorityRecords[0])
 
@@ -524,9 +532,6 @@
         assertEquals(MdnsConstants.getMdnsIPv4Address(), reply.destination.address)
         assertEquals(MdnsConstants.MDNS_PORT, reply.destination.port)
 
-        // TTLs as per RFC6762 10.
-        val longTtl = 4_500_000L
-        val shortTtl = 120_000L
         val serviceName = arrayOf("MyTestService", "_testservice", "_tcp", "local")
 
         assertEquals(listOf(
@@ -534,7 +539,7 @@
                         queriedName,
                         0L /* receiptTimeMillis */,
                         false /* cacheFlush */,
-                        longTtl,
+                        LONG_TTL,
                         serviceName),
         ), reply.answers)
 
@@ -543,13 +548,13 @@
                     serviceName,
                     0L /* receiptTimeMillis */,
                     true /* cacheFlush */,
-                    longTtl,
+                    LONG_TTL,
                     listOf() /* entries */),
             MdnsServiceRecord(
                     serviceName,
                     0L /* receiptTimeMillis */,
                     true /* cacheFlush */,
-                    shortTtl,
+                    SHORT_TTL,
                     0 /* servicePriority */,
                     0 /* serviceWeight */,
                     TEST_PORT,
@@ -558,32 +563,32 @@
                     TEST_HOSTNAME,
                     0L /* receiptTimeMillis */,
                     true /* cacheFlush */,
-                    shortTtl,
+                    SHORT_TTL,
                     TEST_ADDRESSES[0].address),
             MdnsInetAddressRecord(
                     TEST_HOSTNAME,
                     0L /* receiptTimeMillis */,
                     true /* cacheFlush */,
-                    shortTtl,
+                    SHORT_TTL,
                     TEST_ADDRESSES[1].address),
             MdnsInetAddressRecord(
                     TEST_HOSTNAME,
                     0L /* receiptTimeMillis */,
                     true /* cacheFlush */,
-                    shortTtl,
+                    SHORT_TTL,
                     TEST_ADDRESSES[2].address),
             MdnsNsecRecord(
                     serviceName,
                     0L /* receiptTimeMillis */,
                     true /* cacheFlush */,
-                    longTtl,
+                    LONG_TTL,
                     serviceName /* nextDomain */,
                     intArrayOf(MdnsRecord.TYPE_TXT, MdnsRecord.TYPE_SRV)),
             MdnsNsecRecord(
                     TEST_HOSTNAME,
                     0L /* receiptTimeMillis */,
                     true /* cacheFlush */,
-                    shortTtl,
+                    SHORT_TTL,
                     TEST_HOSTNAME /* nextDomain */,
                     intArrayOf(MdnsRecord.TYPE_A, MdnsRecord.TYPE_AAAA)),
         ), reply.additionalAnswers)
@@ -760,7 +765,7 @@
                 expectedName,
                 0L /* receiptTimeMillis */,
                 false /* cacheFlush */,
-                120_000L /* ttlMillis */,
+                SHORT_TTL /* ttlMillis */,
                 0 /* servicePriority */,
                 0 /* serviceWeight */,
                 TEST_PORT,
@@ -769,24 +774,290 @@
                 TEST_HOSTNAME,
                 0L /* receiptTimeMillis */,
                 false /* cacheFlush */,
-                120_000L /* ttlMillis */,
+                SHORT_TTL /* ttlMillis */,
                 TEST_ADDRESSES[0].address),
             MdnsInetAddressRecord(
                 TEST_HOSTNAME,
                 0L /* receiptTimeMillis */,
                 false /* cacheFlush */,
-                120_000L /* ttlMillis */,
+                SHORT_TTL /* ttlMillis */,
                 TEST_ADDRESSES[1].address),
             MdnsInetAddressRecord(
                 TEST_HOSTNAME,
                 0L /* receiptTimeMillis */,
                 false /* cacheFlush */,
-                120_000L /* ttlMillis */,
+                SHORT_TTL /* ttlMillis */,
                 TEST_ADDRESSES[2].address)
         ), packet.authorityRecords)
 
         assertContentEquals(intArrayOf(TEST_SERVICE_ID_1), repository.clearServices())
     }
+
+    private fun doGetReplyWithAnswersTest(
+            questions: List<MdnsRecord>,
+            knownAnswers: List<MdnsRecord>,
+            replyAnswers: List<MdnsRecord>,
+            additionalAnswers: List<MdnsRecord>,
+            expectReply: Boolean
+    ) {
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME,
+                MdnsFeatureFlags.newBuilder().setIsKnownAnswerSuppressionEnabled(true).build())
+        repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
+        val query = MdnsPacket(0 /* flags */, questions, knownAnswers,
+                listOf() /* authorityRecords */, listOf() /* additionalRecords */)
+        val src = InetSocketAddress(parseNumericAddress("192.0.2.123"), 5353)
+        val reply = repository.getReply(query, src)
+
+        if (!expectReply) {
+            assertNull(reply)
+            return
+        }
+
+        assertNotNull(reply)
+        // Source address is IPv4
+        assertEquals(MdnsConstants.getMdnsIPv4Address(), reply.destination.address)
+        assertEquals(MdnsConstants.MDNS_PORT, reply.destination.port)
+        assertEquals(replyAnswers, reply.answers)
+        assertEquals(additionalAnswers, reply.additionalAnswers)
+    }
+
+    @Test
+    fun testGetReply_HasAnswers() {
+        val queriedName = arrayOf("_testservice", "_tcp", "local")
+        val questions = listOf(MdnsPointerRecord(queriedName, false /* isUnicast */))
+        val knownAnswers = listOf(MdnsPointerRecord(
+                arrayOf("_testservice", "_tcp", "local"),
+                0L /* receiptTimeMillis */,
+                false /* cacheFlush */,
+                LONG_TTL,
+                arrayOf("MyTestService", "_testservice", "_tcp", "local")))
+        doGetReplyWithAnswersTest(questions, knownAnswers, listOf() /* replyAnswers */,
+                listOf() /* additionalAnswers */, false /* expectReply */)
+    }
+
+    @Test
+    fun testGetReply_HasAnswers_TtlLessThanHalf() {
+        val queriedName = arrayOf("_testservice", "_tcp", "local")
+        val serviceName = arrayOf("MyTestService", "_testservice", "_tcp", "local")
+        val questions = listOf(MdnsPointerRecord(queriedName, false /* isUnicast */))
+        val knownAnswers = listOf(MdnsPointerRecord(
+                arrayOf("_testservice", "_tcp", "local"),
+                0L /* receiptTimeMillis */,
+                false /* cacheFlush */,
+                (LONG_TTL / 2 - 1000L),
+                arrayOf("MyTestService", "_testservice", "_tcp", "local")))
+        val replyAnswers = listOf(MdnsPointerRecord(
+                queriedName,
+                0L /* receiptTimeMillis */,
+                false /* cacheFlush */,
+                LONG_TTL,
+                serviceName))
+        val additionalAnswers = listOf(
+                MdnsTextRecord(
+                        serviceName,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        LONG_TTL,
+                        listOf() /* entries */),
+                MdnsServiceRecord(
+                        serviceName,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        SHORT_TTL,
+                        0 /* servicePriority */,
+                        0 /* serviceWeight */,
+                        TEST_PORT,
+                        TEST_HOSTNAME),
+                MdnsInetAddressRecord(
+                        TEST_HOSTNAME,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        SHORT_TTL,
+                        TEST_ADDRESSES[0].address),
+                MdnsInetAddressRecord(
+                        TEST_HOSTNAME,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        SHORT_TTL,
+                        TEST_ADDRESSES[1].address),
+                MdnsInetAddressRecord(
+                        TEST_HOSTNAME,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        SHORT_TTL,
+                        TEST_ADDRESSES[2].address),
+                MdnsNsecRecord(
+                        serviceName,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        LONG_TTL,
+                        serviceName /* nextDomain */,
+                        intArrayOf(MdnsRecord.TYPE_TXT, MdnsRecord.TYPE_SRV)),
+                MdnsNsecRecord(
+                        TEST_HOSTNAME,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        SHORT_TTL,
+                        TEST_HOSTNAME /* nextDomain */,
+                        intArrayOf(MdnsRecord.TYPE_A, MdnsRecord.TYPE_AAAA)))
+        doGetReplyWithAnswersTest(questions, knownAnswers, replyAnswers, additionalAnswers,
+                true /* expectReply */)
+    }
+
+    @Test
+    fun testGetReply_HasAnotherAnswer() {
+        val queriedName = arrayOf("_testservice", "_tcp", "local")
+        val serviceName = arrayOf("MyTestService", "_testservice", "_tcp", "local")
+        val questions = listOf(MdnsPointerRecord(queriedName, false /* isUnicast */))
+        val knownAnswers = listOf(MdnsPointerRecord(
+                queriedName,
+                0L /* receiptTimeMillis */,
+                false /* cacheFlush */,
+                LONG_TTL,
+                arrayOf("MyOtherTestService", "_testservice", "_tcp", "local")))
+        val replyAnswers = listOf(MdnsPointerRecord(
+                queriedName,
+                0L /* receiptTimeMillis */,
+                false /* cacheFlush */,
+                LONG_TTL,
+                serviceName))
+        val additionalAnswers = listOf(
+                MdnsTextRecord(
+                        serviceName,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        LONG_TTL,
+                        listOf() /* entries */),
+                MdnsServiceRecord(
+                        serviceName,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        SHORT_TTL,
+                        0 /* servicePriority */,
+                        0 /* serviceWeight */,
+                        TEST_PORT,
+                        TEST_HOSTNAME),
+                MdnsInetAddressRecord(
+                        TEST_HOSTNAME,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        SHORT_TTL,
+                        TEST_ADDRESSES[0].address),
+                MdnsInetAddressRecord(
+                        TEST_HOSTNAME,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        SHORT_TTL,
+                        TEST_ADDRESSES[1].address),
+                MdnsInetAddressRecord(
+                        TEST_HOSTNAME,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        SHORT_TTL,
+                        TEST_ADDRESSES[2].address),
+                MdnsNsecRecord(
+                        serviceName,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        LONG_TTL,
+                        serviceName /* nextDomain */,
+                        intArrayOf(MdnsRecord.TYPE_TXT, MdnsRecord.TYPE_SRV)),
+                MdnsNsecRecord(
+                        TEST_HOSTNAME,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        SHORT_TTL,
+                        TEST_HOSTNAME /* nextDomain */,
+                        intArrayOf(MdnsRecord.TYPE_A, MdnsRecord.TYPE_AAAA)))
+        doGetReplyWithAnswersTest(questions, knownAnswers, replyAnswers, additionalAnswers,
+                true /* expectReply */)
+    }
+
+    @Test
+    fun testGetReply_HasAnswers_MultiQuestions() {
+        val queriedName = arrayOf("_testservice", "_tcp", "local")
+        val serviceName = arrayOf("MyTestService", "_testservice", "_tcp", "local")
+        val questions = listOf(
+                MdnsPointerRecord(queriedName, false /* isUnicast */),
+                MdnsServiceRecord(serviceName, false /* isUnicast */))
+        val knownAnswers = listOf(MdnsPointerRecord(
+                queriedName,
+                0L /* receiptTimeMillis */,
+                false /* cacheFlush */,
+                LONG_TTL - 1000L,
+                serviceName))
+        val replyAnswers = listOf(MdnsServiceRecord(
+                serviceName,
+                0L /* receiptTimeMillis */,
+                false /* cacheFlush */,
+                SHORT_TTL /* ttlMillis */,
+                0 /* servicePriority */,
+                0 /* serviceWeight */,
+                TEST_PORT,
+                TEST_HOSTNAME))
+        val additionalAnswers = listOf(
+                MdnsInetAddressRecord(
+                        TEST_HOSTNAME,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        SHORT_TTL,
+                        TEST_ADDRESSES[0].address),
+                MdnsInetAddressRecord(
+                        TEST_HOSTNAME,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        SHORT_TTL,
+                        TEST_ADDRESSES[1].address),
+                MdnsInetAddressRecord(
+                        TEST_HOSTNAME,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        SHORT_TTL,
+                        TEST_ADDRESSES[2].address),
+                MdnsNsecRecord(
+                        serviceName,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        LONG_TTL,
+                        serviceName /* nextDomain */,
+                        intArrayOf(MdnsRecord.TYPE_SRV)),
+                MdnsNsecRecord(
+                        TEST_HOSTNAME,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        SHORT_TTL,
+                        TEST_HOSTNAME /* nextDomain */,
+                        intArrayOf(MdnsRecord.TYPE_A, MdnsRecord.TYPE_AAAA)))
+        doGetReplyWithAnswersTest(questions, knownAnswers, replyAnswers, additionalAnswers,
+                true /* expectReply */)
+    }
+
+    @Test
+    fun testGetReply_HasAnswers_MultiQuestions_NoReply() {
+        val queriedName = arrayOf("_testservice", "_tcp", "local")
+        val serviceName = arrayOf("MyTestService", "_testservice", "_tcp", "local")
+        val questions = listOf(
+                MdnsPointerRecord(queriedName, false /* isUnicast */),
+                MdnsServiceRecord(serviceName, false /* isUnicast */))
+        val knownAnswers = listOf(
+                MdnsPointerRecord(
+                        queriedName,
+                        0L /* receiptTimeMillis */,
+                        false /* cacheFlush */,
+                        LONG_TTL - 1000L,
+                        serviceName),
+                MdnsServiceRecord(
+                        serviceName,
+                        0L /* receiptTimeMillis */,
+                        false /* cacheFlush */,
+                        SHORT_TTL - 15_000L,
+                        0 /* servicePriority */,
+                        0 /* serviceWeight */,
+                        TEST_PORT,
+                        TEST_HOSTNAME))
+        doGetReplyWithAnswersTest(questions, knownAnswers, listOf() /* replyAnswers */,
+                listOf() /* additionalAnswers */, false /* expectReply */)
+    }
 }
 
 private fun MdnsRecordRepository.initWithService(
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSNetworkRequestStateStatsMetricsTests.kt b/tests/unit/java/com/android/server/connectivityservice/CSNetworkRequestStateStatsMetricsTests.kt
new file mode 100644
index 0000000..35f8ae5
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/CSNetworkRequestStateStatsMetricsTests.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.server
+
+import android.net.NetworkCapabilities
+import android.net.NetworkRequest
+import android.os.Build
+import android.os.Process
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.TestableNetworkCallback
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.argThat
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+class CSNetworkRequestStateStatsMetricsTests : CSTest() {
+    private val CELL_INTERNET_NOT_METERED_NC = NetworkCapabilities.Builder()
+            .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+            .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+            .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
+            .build().setRequestorUidAndPackageName(Process.myUid(), context.getPackageName())
+
+    private val CELL_INTERNET_NOT_METERED_NR = NetworkRequest.Builder()
+            .setCapabilities(CELL_INTERNET_NOT_METERED_NC).build()
+
+    @Before
+    fun setup() {
+        waitForIdle()
+        clearInvocations(networkRequestStateStatsMetrics)
+    }
+
+    @Test
+    fun testRequestTypeNRProduceMetrics() {
+        cm.requestNetwork(CELL_INTERNET_NOT_METERED_NR, TestableNetworkCallback())
+        waitForIdle()
+
+        verify(networkRequestStateStatsMetrics).onNetworkRequestReceived(
+                argThat{req -> req.networkCapabilities.equals(
+                        CELL_INTERNET_NOT_METERED_NR.networkCapabilities)})
+    }
+
+    @Test
+    fun testListenTypeNRProduceNoMetrics() {
+        cm.registerNetworkCallback(CELL_INTERNET_NOT_METERED_NR, TestableNetworkCallback())
+        waitForIdle()
+        verify(networkRequestStateStatsMetrics, never()).onNetworkRequestReceived(any())
+    }
+
+    @Test
+    fun testRemoveRequestTypeNRProduceMetrics() {
+        val cb = TestableNetworkCallback()
+        cm.requestNetwork(CELL_INTERNET_NOT_METERED_NR, cb)
+
+        waitForIdle()
+        clearInvocations(networkRequestStateStatsMetrics)
+
+        cm.unregisterNetworkCallback(cb)
+        waitForIdle()
+        verify(networkRequestStateStatsMetrics).onNetworkRequestRemoved(
+                argThat{req -> req.networkCapabilities.equals(
+                        CELL_INTERNET_NOT_METERED_NR.networkCapabilities)})
+    }
+}
diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
index 958c4f2..5c9a762 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
@@ -54,6 +54,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.internal.app.IBatteryStats
 import com.android.internal.util.test.BroadcastInterceptingContext
+import com.android.metrics.NetworkRequestStateStatsMetrics
 import com.android.modules.utils.build.SdkLevel
 import com.android.net.module.util.ArrayTrackRecord
 import com.android.networkstack.apishim.common.UnsupportedApiLevelException
@@ -157,6 +158,7 @@
     val netd = mock<INetd>()
     val bpfNetMaps = mock<BpfNetMaps>()
     val clatCoordinator = mock<ClatCoordinator>()
+    val networkRequestStateStatsMetrics = mock<NetworkRequestStateStatsMetrics>()
     val proxyTracker = ProxyTracker(context, mock<Handler>(), 16 /* EVENT_PROXY_HAS_CHANGED */)
     val alarmManager = makeMockAlarmManager()
     val systemConfigManager = makeMockSystemConfigManager()
@@ -197,6 +199,9 @@
                 MultinetworkPolicyTracker(c, h, r,
                         MultinetworkPolicyTrackerTestDependencies(connResources.get()))
 
+        override fun makeNetworkRequestStateStatsMetrics(c: Context) =
+                this@CSTest.networkRequestStateStatsMetrics
+
         // All queried features must be mocked, because the test cannot hold the
         // READ_DEVICE_CONFIG permission and device config utils use static methods for
         // checking permissions.
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index 7a4dfed..19216a7 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -169,7 +169,6 @@
 
 import java.io.File;
 import java.io.FileDescriptor;
-import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.nio.file.Files;
@@ -424,131 +423,132 @@
     }
 
     @NonNull
-    private NetworkStatsService.Dependencies makeDependencies() {
-        return new NetworkStatsService.Dependencies() {
-            @Override
-            public File getLegacyStatsDir() {
-                return mLegacyStatsDir;
-            }
+    private TestDependencies makeDependencies() {
+        return new TestDependencies();
+    }
 
-            @Override
-            public File getOrCreateStatsDir() {
-                return mStatsDir;
-            }
+    class TestDependencies extends NetworkStatsService.Dependencies {
+        @Override
+        public File getLegacyStatsDir() {
+            return mLegacyStatsDir;
+        }
 
-            @Override
-            public boolean getStoreFilesInApexData() {
-                return mStoreFilesInApexData;
-            }
+        @Override
+        public File getOrCreateStatsDir() {
+            return mStatsDir;
+        }
 
-            @Override
-            public int getImportLegacyTargetAttempts() {
-                return mImportLegacyTargetAttempts;
-            }
+        @Override
+        public boolean getStoreFilesInApexData() {
+            return mStoreFilesInApexData;
+        }
 
-            @Override
-            public PersistentInt createPersistentCounter(@androidx.annotation.NonNull Path dir,
-                    @androidx.annotation.NonNull String name) throws IOException {
-                switch (name) {
-                    case NETSTATS_IMPORT_ATTEMPTS_COUNTER_NAME:
-                        return mImportLegacyAttemptsCounter;
-                    case NETSTATS_IMPORT_SUCCESSES_COUNTER_NAME:
-                        return mImportLegacySuccessesCounter;
-                    case NETSTATS_IMPORT_FALLBACKS_COUNTER_NAME:
-                        return mImportLegacyFallbacksCounter;
-                    default:
-                        throw new IllegalArgumentException("Unknown counter name: " + name);
-                }
-            }
+        @Override
+        public int getImportLegacyTargetAttempts() {
+            return mImportLegacyTargetAttempts;
+        }
 
-            @Override
-            public NetworkStatsCollection readPlatformCollection(
-                    @NonNull String prefix, long bucketDuration) {
-                return mPlatformNetworkStatsCollection.get(prefix);
+        @Override
+        public PersistentInt createPersistentCounter(@NonNull Path dir, @NonNull String name) {
+            switch (name) {
+                case NETSTATS_IMPORT_ATTEMPTS_COUNTER_NAME:
+                    return mImportLegacyAttemptsCounter;
+                case NETSTATS_IMPORT_SUCCESSES_COUNTER_NAME:
+                    return mImportLegacySuccessesCounter;
+                case NETSTATS_IMPORT_FALLBACKS_COUNTER_NAME:
+                    return mImportLegacyFallbacksCounter;
+                default:
+                    throw new IllegalArgumentException("Unknown counter name: " + name);
             }
+        }
 
-            @Override
-            public HandlerThread makeHandlerThread() {
-                return mHandlerThread;
-            }
+        @Override
+        public NetworkStatsCollection readPlatformCollection(
+                @NonNull String prefix, long bucketDuration) {
+            return mPlatformNetworkStatsCollection.get(prefix);
+        }
 
-            @Override
-            public NetworkStatsSubscriptionsMonitor makeSubscriptionsMonitor(
-                    @NonNull Context context, @NonNull Executor executor,
-                    @NonNull NetworkStatsService service) {
+        @Override
+        public HandlerThread makeHandlerThread() {
+            return mHandlerThread;
+        }
 
-                return mNetworkStatsSubscriptionsMonitor;
-            }
+        @Override
+        public NetworkStatsSubscriptionsMonitor makeSubscriptionsMonitor(
+                @NonNull Context context, @NonNull Executor executor,
+                @NonNull NetworkStatsService service) {
 
-            @Override
-            public ContentObserver makeContentObserver(Handler handler,
-                    NetworkStatsSettings settings, NetworkStatsSubscriptionsMonitor monitor) {
-                mHandler = handler;
-                return mContentObserver = super.makeContentObserver(handler, settings, monitor);
-            }
+            return mNetworkStatsSubscriptionsMonitor;
+        }
 
-            @Override
-            public LocationPermissionChecker makeLocationPermissionChecker(final Context context) {
-                return mLocationPermissionChecker;
-            }
+        @Override
+        public ContentObserver makeContentObserver(Handler handler,
+                NetworkStatsSettings settings, NetworkStatsSubscriptionsMonitor monitor) {
+            mHandler = handler;
+            return mContentObserver = super.makeContentObserver(handler, settings, monitor);
+        }
 
-            @Override
-            public BpfInterfaceMapUpdater makeBpfInterfaceMapUpdater(
-                    @NonNull Context ctx, @NonNull Handler handler) {
-                return mBpfInterfaceMapUpdater;
-            }
+        @Override
+        public LocationPermissionChecker makeLocationPermissionChecker(final Context context) {
+            return mLocationPermissionChecker;
+        }
 
-            @Override
-            public IBpfMap<S32, U8> getUidCounterSetMap() {
-                return mUidCounterSetMap;
-            }
+        @Override
+        public BpfInterfaceMapUpdater makeBpfInterfaceMapUpdater(
+                @NonNull Context ctx, @NonNull Handler handler) {
+            return mBpfInterfaceMapUpdater;
+        }
 
-            @Override
-            public IBpfMap<CookieTagMapKey, CookieTagMapValue> getCookieTagMap() {
-                return mCookieTagMap;
-            }
+        @Override
+        public IBpfMap<S32, U8> getUidCounterSetMap() {
+            return mUidCounterSetMap;
+        }
 
-            @Override
-            public IBpfMap<StatsMapKey, StatsMapValue> getStatsMapA() {
-                return mStatsMapA;
-            }
+        @Override
+        public IBpfMap<CookieTagMapKey, CookieTagMapValue> getCookieTagMap() {
+            return mCookieTagMap;
+        }
 
-            @Override
-            public IBpfMap<StatsMapKey, StatsMapValue> getStatsMapB() {
-                return mStatsMapB;
-            }
+        @Override
+        public IBpfMap<StatsMapKey, StatsMapValue> getStatsMapA() {
+            return mStatsMapA;
+        }
 
-            @Override
-            public IBpfMap<UidStatsMapKey, StatsMapValue> getAppUidStatsMap() {
-                return mAppUidStatsMap;
-            }
+        @Override
+        public IBpfMap<StatsMapKey, StatsMapValue> getStatsMapB() {
+            return mStatsMapB;
+        }
 
-            @Override
-            public IBpfMap<S32, StatsMapValue> getIfaceStatsMap() {
-                return mIfaceStatsMap;
-            }
+        @Override
+        public IBpfMap<UidStatsMapKey, StatsMapValue> getAppUidStatsMap() {
+            return mAppUidStatsMap;
+        }
 
-            @Override
-            public boolean isDebuggable() {
-                return mIsDebuggable == Boolean.TRUE;
-            }
+        @Override
+        public IBpfMap<S32, StatsMapValue> getIfaceStatsMap() {
+            return mIfaceStatsMap;
+        }
 
-            @Override
-            public BpfNetMaps makeBpfNetMaps(Context ctx) {
-                return mBpfNetMaps;
-            }
+        @Override
+        public boolean isDebuggable() {
+            return mIsDebuggable == Boolean.TRUE;
+        }
 
-            @Override
-            public SkDestroyListener makeSkDestroyListener(
-                    IBpfMap<CookieTagMapKey, CookieTagMapValue> cookieTagMap, Handler handler) {
-                return mSkDestroyListener;
-            }
+        @Override
+        public BpfNetMaps makeBpfNetMaps(Context ctx) {
+            return mBpfNetMaps;
+        }
 
-            @Override
-            public boolean supportEventLogger(@NonNull Context cts) {
-                return true;
-            }
-        };
+        @Override
+        public SkDestroyListener makeSkDestroyListener(
+                IBpfMap<CookieTagMapKey, CookieTagMapValue> cookieTagMap, Handler handler) {
+            return mSkDestroyListener;
+        }
+
+        @Override
+        public boolean supportEventLogger(@NonNull Context cts) {
+            return true;
+        }
     }
 
     @After
@@ -2230,7 +2230,8 @@
         final DropBoxManager dropBox = mock(DropBoxManager.class);
         return new NetworkStatsRecorder(new FileRotator(
                 directory, prefix, config.rotateAgeMillis, config.deleteAgeMillis),
-                observer, dropBox, prefix, config.bucketDuration, includeTags, wipeOnError);
+                observer, dropBox, prefix, config.bucketDuration, includeTags, wipeOnError,
+                false /* useFastDataInput */);
     }
 
     private NetworkStatsCollection getLegacyCollection(String prefix, boolean includeTags) {
diff --git a/thread/TEST_MAPPING b/thread/TEST_MAPPING
index 30aeca5..ec1cc08 100644
--- a/thread/TEST_MAPPING
+++ b/thread/TEST_MAPPING
@@ -4,7 +4,7 @@
       "name": "CtsThreadNetworkTestCases"
     }
   ],
-  "postsubmit": [
+  "presubmit": [
     {
       "name": "ThreadNetworkUnitTests"
     }