Refactor grouped by methods

This is a no-op refactoring, which re-write the group by methods
to use a common method that can slice NetworkStats by a given
lambda. This is useful for subsequent changes to slice NetworkStats
with different conditions.

Test: atest FrameworksNetTests:android.net.connectivity.android.net.NetworkStatsTest
Bug: 290728278
Change-Id: Ia28571021fba46fc59380798053ccdc91ddc6ae4
diff --git a/framework-t/src/android/net/NetworkStats.java b/framework-t/src/android/net/NetworkStats.java
index 8719960..0b5bb85 100644
--- a/framework-t/src/android/net/NetworkStats.java
+++ b/framework-t/src/android/net/NetworkStats.java
@@ -46,6 +46,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.function.Function;
 import java.util.function.Predicate;
 
 /**
@@ -455,6 +456,41 @@
             return operations;
         }
 
+        /**
+         * Set Key fields for this entry.
+         *
+         * @return this object.
+         * @hide
+         */
+        private Entry setKeys(@Nullable String iface, int uid, @State int set,
+                int tag, @Meteredness int metered, @Roaming int roaming,
+                @DefaultNetwork int defaultNetwork) {
+            this.iface = iface;
+            this.uid = uid;
+            this.set = set;
+            this.tag = tag;
+            this.metered = metered;
+            this.roaming = roaming;
+            this.defaultNetwork = defaultNetwork;
+            return this;
+        }
+
+        /**
+         * Set Value fields for this entry.
+         *
+         * @return this object.
+         * @hide
+         */
+        private Entry setValues(long rxBytes, long rxPackets, long txBytes, long txPackets,
+                long operations) {
+            this.rxBytes = rxBytes;
+            this.rxPackets = rxPackets;
+            this.txBytes = txBytes;
+            this.txPackets = txPackets;
+            this.operations = operations;
+            return this;
+        }
+
         @Override
         public String toString() {
             final StringBuilder builder = new StringBuilder();
@@ -1210,30 +1246,21 @@
      * @hide
      */
     public NetworkStats groupedByIface() {
-        final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
+        // Keep backward compatibility where the method filtered out tagged stats and keep the
+        // operation counts as 0. The method used to deal with uid snapshot where tagged and
+        // non-tagged stats were mixed. And this method was also in Android O API list,
+        // so it is possible OEM can access it.
+        final NetworkStats copiedStats = this.clone();
+        copiedStats.filter(e -> e.getTag() == TAG_NONE);
 
-        final Entry entry = new Entry();
-        entry.uid = UID_ALL;
-        entry.set = SET_ALL;
-        entry.tag = TAG_NONE;
-        entry.metered = METERED_ALL;
-        entry.roaming = ROAMING_ALL;
-        entry.defaultNetwork = DEFAULT_NETWORK_ALL;
-        entry.operations = 0L;
+        final Entry temp = new Entry();
+        final NetworkStats mappedStats = copiedStats.map(entry -> temp.setKeys(entry.getIface(),
+                UID_ALL, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL));
 
-        for (int i = 0; i < size; i++) {
-            // skip specific tags, since already counted in TAG_NONE
-            if (tag[i] != TAG_NONE) continue;
-
-            entry.iface = iface[i];
-            entry.rxBytes = rxBytes[i];
-            entry.rxPackets = rxPackets[i];
-            entry.txBytes = txBytes[i];
-            entry.txPackets = txPackets[i];
-            stats.combineValues(entry);
+        for (int i = 0; i < mappedStats.size; i++) {
+            mappedStats.operations[i] = 0L;
         }
-
-        return stats;
+        return mappedStats;
     }
 
     /**
@@ -1242,30 +1269,15 @@
      * @hide
      */
     public NetworkStats groupedByUid() {
-        final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
+        // Keep backward compatibility where the method filtered out tagged stats. The method used
+        // to deal with uid snapshot where tagged and non-tagged stats were mixed. And
+        // this method is also in Android O API list, so it is possible OEM can access it.
+        final NetworkStats copiedStats = this.clone();
+        copiedStats.filter(e -> e.getTag() == TAG_NONE);
 
-        final Entry entry = new Entry();
-        entry.iface = IFACE_ALL;
-        entry.set = SET_ALL;
-        entry.tag = TAG_NONE;
-        entry.metered = METERED_ALL;
-        entry.roaming = ROAMING_ALL;
-        entry.defaultNetwork = DEFAULT_NETWORK_ALL;
-
-        for (int i = 0; i < size; i++) {
-            // skip specific tags, since already counted in TAG_NONE
-            if (tag[i] != TAG_NONE) continue;
-
-            entry.uid = uid[i];
-            entry.rxBytes = rxBytes[i];
-            entry.rxPackets = rxPackets[i];
-            entry.txBytes = txBytes[i];
-            entry.txPackets = txPackets[i];
-            entry.operations = operations[i];
-            stats.combineValues(entry);
-        }
-
-        return stats;
+        final Entry temp = new Entry();
+        return copiedStats.map(entry -> temp.setKeys(IFACE_ALL,
+                entry.getUid(), SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL));
     }
 
     /**
@@ -1302,6 +1314,29 @@
     }
 
     /**
+     * Returns a new NetworkStats object where entries are transformed.
+     *
+     * Note that because NetworkStats is more akin to a map than to a list,
+     * the entries will be grouped after they are mapped by the key fields,
+     * e.g. uid, set, tag, defaultNetwork.
+     * Value fields with the same keys will be added together.
+     */
+    @NonNull
+    private NetworkStats map(@NonNull Function<Entry, Entry> f) {
+        final NetworkStats ret = new NetworkStats(0, 1);
+        for (Entry e : this) {
+            final NetworkStats.Entry transformed = f.apply(e);
+            if (transformed == e) {
+                throw new IllegalStateException("A new entry must be created.");
+            }
+            transformed.setValues(e.getRxBytes(), e.getRxPackets(), e.getTxBytes(),
+                    e.getTxPackets(), e.getOperations());
+            ret.combineValues(transformed);
+        }
+        return ret;
+    }
+
+    /**
      * Only keep entries that match all specified filters.
      *
      * <p>This mutates the original structure in place. After this method is called,