Use new NetworkStats#addEntries API

Currently, NetworkStats#addEntry creates a new instance
containing the requested result instead of mutating the
original object. This creates a heavy memory footprint
when the caller invokes it in a loop because a new NetworkStats
instance is created whenever an addEntry is called.

This is a no-op change except it utilizes the newly
created API to add a list of entries at once.
This approach only clones the instance once,
thereby reducing the required memory footprint.

Test: atest CtsStatsdAtomHostTestCases:android.cts.statsdatom.net.BytesTransferredTest
Test: adb shell cmd stats pull-source 10000
Test: atest FrameworksServicesTests:com.android.server.stats.pull.netstats.NetworkStatsUtilsTest
Flag: com.android.server.stats.netstats_use_add_entries
Bug: 335680025
Change-Id: I1b8195d1cacc53e6dc77f154ac1a8108a0220c5b
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 35003af..5324709 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -66,6 +66,7 @@
 import static com.android.server.stats.pull.ProcfsMemoryUtil.readCmdlineFromProcfs;
 import static com.android.server.stats.pull.ProcfsMemoryUtil.readMemorySnapshotFromProcfs;
 import static com.android.server.stats.pull.netstats.NetworkStatsUtils.fromPublicNetworkStats;
+import static com.android.server.stats.pull.netstats.NetworkStatsUtils.isAddEntriesSupported;
 
 import static libcore.io.IoUtils.closeQuietly;
 
@@ -1336,14 +1337,22 @@
 
     @NonNull
     private static NetworkStats removeEmptyEntries(NetworkStats stats) {
-        NetworkStats ret = new NetworkStats(0, 1);
+        final ArrayList<NetworkStats.Entry> entries = new ArrayList<>();
         for (NetworkStats.Entry e : stats) {
             if (e.getRxBytes() != 0 || e.getRxPackets() != 0 || e.getTxBytes() != 0
                     || e.getTxPackets() != 0 || e.getOperations() != 0) {
-                ret = ret.addEntry(e);
+                entries.add(e);
             }
         }
-        return ret;
+        if (isAddEntriesSupported()) {
+            return new NetworkStats(0, entries.size()).addEntries(entries);
+        } else {
+            NetworkStats outputStats = new NetworkStats(0L, 1);
+            for (NetworkStats.Entry e : entries) {
+                outputStats = outputStats.addEntry(e);
+            }
+            return outputStats;
+        }
     }
 
     private void addNetworkStats(int atomTag, @NonNull List<StatsEvent> ret,
@@ -1656,11 +1665,19 @@
     @NonNull
     private NetworkStats sliceNetworkStats(@NonNull NetworkStats stats,
             @NonNull Function<NetworkStats.Entry, NetworkStats.Entry> slicer) {
-        NetworkStats ret = new NetworkStats(0, 1);
+        final ArrayList<NetworkStats.Entry> entries = new ArrayList();
         for (NetworkStats.Entry e : stats) {
-            ret = ret.addEntry(slicer.apply(e));
+            entries.add(slicer.apply(e));
         }
-        return ret;
+        if (isAddEntriesSupported()) {
+            return new NetworkStats(0, entries.size()).addEntries(entries);
+        } else {
+            NetworkStats outputStats = new NetworkStats(0L, 1);
+            for (NetworkStats.Entry e : entries) {
+                outputStats = outputStats.addEntry(e);
+            }
+            return outputStats;
+        }
     }
 
     private void registerWifiBytesTransferBackground() {
diff --git a/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsUtils.java b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsUtils.java
index de58852..0318bdd 100644
--- a/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsUtils.java
+++ b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsUtils.java
@@ -24,6 +24,9 @@
 import android.app.usage.NetworkStats;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.stats.Flags;
+
+import java.util.ArrayList;
 
 /**
  * Utility methods for accessing {@link android.net.NetworkStats}.
@@ -35,12 +38,21 @@
      */
     public static android.net.NetworkStats fromPublicNetworkStats(
             NetworkStats publiceNetworkStats) {
-        android.net.NetworkStats stats = new android.net.NetworkStats(0L, 0);
+        final ArrayList<android.net.NetworkStats.Entry> entries = new ArrayList<>();
         while (publiceNetworkStats.hasNextBucket()) {
             NetworkStats.Bucket bucket = new NetworkStats.Bucket();
             publiceNetworkStats.getNextBucket(bucket);
-            final android.net.NetworkStats.Entry entry = fromBucket(bucket);
-            stats = stats.addEntry(entry);
+            entries.add(fromBucket(bucket));
+        }
+        android.net.NetworkStats stats = new android.net.NetworkStats(0L, 1);
+        // The new API is only supported on devices running the mainline version of `NetworkStats`.
+        // It should always be used when available for memory efficiency.
+        if (isAddEntriesSupported()) {
+            stats = stats.addEntries(entries);
+        } else {
+            for (android.net.NetworkStats.Entry entry : entries) {
+                stats = stats.addEntry(entry);
+            }
         }
         return stats;
     }
@@ -106,4 +118,8 @@
         }
         return 0;
     }
+
+    public static boolean isAddEntriesSupported() {
+        return Flags.netstatsUseAddEntries();
+    }
 }
diff --git a/services/core/java/com/android/server/stats/stats_flags.aconfig b/services/core/java/com/android/server/stats/stats_flags.aconfig
index 101b98e..7ef6fee 100644
--- a/services/core/java/com/android/server/stats/stats_flags.aconfig
+++ b/services/core/java/com/android/server/stats/stats_flags.aconfig
@@ -1,6 +1,20 @@
 package: "com.android.server.stats"
 container: "system"
 
+# Note: To ensure compatibility across all release configurations, initiate the ramp-up process
+# only after the 'com.android.net.flags.netstats_add_entries' flag has been fully deployed.
+# This flag provides the necessary API from the Connectivity module.
+# The flag was added because the flag 'com.android.net.flags.netstats_add_entries' for API
+# is already being rolled out, and modifying behavior during an active rollout might
+# lead to unwanted issues.
+flag {
+    name: "netstats_use_add_entries"
+    namespace: "statsd"
+    description: "Use NetworkStats#addEntries to reduce memory footprint"
+    bug: "335680025"
+    is_fixed_read_only: true
+}
+
 flag {
     name: "add_mobile_bytes_transfer_by_proc_state_puller"
     namespace: "statsd"