Merge "Updates to NetworkFactory for the new NetworkAgent constructor"
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index c3b8668..8f845bc 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -45,6 +45,7 @@
         "//frameworks/opt/telephony",
         "//packages/modules/NetworkStack",
         "//packages/modules/CaptivePortalLogin",
+        "//frameworks/libs/net/common/tests:__subpackages__",
     ]
 }
 
@@ -61,6 +62,7 @@
         "//frameworks/opt/net/ike",
         "//packages/modules/NetworkStack",
         "//packages/modules/CaptivePortalLogin",
+        "//frameworks/libs/net/common/tests:__subpackages__",
     ],
 }
 
diff --git a/staticlibs/src_frameworkcommon/android/net/util/LinkPropertiesUtils.java b/staticlibs/src_frameworkcommon/android/net/util/LinkPropertiesUtils.java
index 59d88ac..c50ca0b 100644
--- a/staticlibs/src_frameworkcommon/android/net/util/LinkPropertiesUtils.java
+++ b/staticlibs/src_frameworkcommon/android/net/util/LinkPropertiesUtils.java
@@ -26,8 +26,10 @@
 import java.net.InetAddress;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Objects;
+import java.util.function.Function;
 
 /**
  * Collection of link properties utilities.
@@ -65,6 +67,60 @@
     }
 
     /**
+     * Generic class to compare two lists of items of type {@code T} whose properties can change.
+     * The items to be compared must provide a way to calculate a corresponding key of type
+     * {@code K} such that if (and only if) an old and a new item have the same key, then the new
+     * item is an update of the old item. Both the old list and the new list may not contain more
+     * than one item with the same key, and may not contain any null items.
+     *
+     * @param <K> A class that represents the key of the items to be compared.
+     * @param <T> The class that represents the object to be compared.
+     */
+    public static class CompareOrUpdateResult<K, T> {
+        public final List<T> added = new ArrayList<>();
+        public final List<T> removed = new ArrayList<>();
+        public final List<T> updated = new ArrayList<>();
+
+        /**
+         * Compares two lists of items.
+         * @param oldItems the old list of items.
+         * @param newItems the new list of items.
+         * @param keyCalculator a {@link Function} that calculates an item's key.
+         */
+        public CompareOrUpdateResult(Collection<T> oldItems, Collection<T> newItems,
+                Function<T, K> keyCalculator) {
+            HashMap<K, T> updateTracker = new HashMap<>();
+
+            for (T oldItem : oldItems) {
+                updateTracker.put(keyCalculator.apply(oldItem), oldItem);
+            }
+
+            for (T newItem : newItems) {
+                T oldItem = updateTracker.remove(keyCalculator.apply(newItem));
+                if (oldItem != null) {
+                    if (!oldItem.equals(newItem)) {
+                        // Update of existing item.
+                        updated.add(newItem);
+                    }
+                } else {
+                    // New item.
+                    added.add(newItem);
+                }
+            }
+
+            removed.addAll(updateTracker.values());
+        }
+
+        @Override
+        public String toString() {
+            return "removed=[" + TextUtils.join(",", removed)
+                    + "] added=[" + TextUtils.join(",", added)
+                    + "] updated=[" + TextUtils.join(",", updated)
+                    + "]";
+        }
+    }
+
+    /**
      * Compares the addresses in {@code left} LinkProperties with {@code right}
      * LinkProperties, examining only addresses on the base link.
      *
diff --git a/staticlibs/tests/unit/Android.bp b/staticlibs/tests/unit/Android.bp
index 4a51a8c..fe363f5 100644
--- a/staticlibs/tests/unit/Android.bp
+++ b/staticlibs/tests/unit/Android.bp
@@ -6,9 +6,12 @@
     name: "NetworkStaticLibTests",
     certificate: "platform",
     srcs: ["src/**/*.java","src/**/*.kt"],
+    jarjar_rules: "jarjar-rules.txt",
     test_suites: ["device-tests"],
     static_libs: [
         "androidx.test.rules",
+        "net-utils-framework-common",
+        "net-utils-services-common",
     ],
     libs: [
         "android.test.runner",
diff --git a/staticlibs/tests/unit/jarjar-rules.txt b/staticlibs/tests/unit/jarjar-rules.txt
new file mode 100644
index 0000000..dbb3974
--- /dev/null
+++ b/staticlibs/tests/unit/jarjar-rules.txt
@@ -0,0 +1,4 @@
+# Ensure that the tests can directly use the version of classes from the library. Otherwise, they
+# will use whatever version is currently in the bootclasspath on the device running the test.
+# These rules must match the jarjar rules used to build the library.
+rule android.net.util.** com.android.net.module.util.@1
diff --git a/staticlibs/tests/unit/src/android/net/util/LinkPropertiesUtilsTest.java b/staticlibs/tests/unit/src/android/net/util/LinkPropertiesUtilsTest.java
index 6e06ee8..e2cde34 100644
--- a/staticlibs/tests/unit/src/android/net/util/LinkPropertiesUtilsTest.java
+++ b/staticlibs/tests/unit/src/android/net/util/LinkPropertiesUtilsTest.java
@@ -26,6 +26,7 @@
 import android.net.LinkProperties;
 import android.net.ProxyInfo;
 import android.net.RouteInfo;
+import android.net.util.LinkPropertiesUtils.CompareOrUpdateResult;
 import android.net.util.LinkPropertiesUtils.CompareResult;
 
 import androidx.test.runner.AndroidJUnit4;
@@ -34,6 +35,11 @@
 import org.junit.runner.RunWith;
 
 import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.function.Function;
 
 @RunWith(AndroidJUnit4.class)
 public final class LinkPropertiesUtilsTest {
@@ -185,4 +191,58 @@
         assertEquals(linkAddr1, results.removed.get(0));
         assertEquals(linkAddr2, results.added.get(0));
     }
-}
+
+    private void assertSameElements(List<String> expected, List<String> actual) {
+        HashSet<String> expectedSet = new HashSet(expected);
+        assertEquals("expected list contains duplicates", expectedSet.size(), expected.size());
+        HashSet<String> actualSet = new HashSet(actual);
+        assertEquals("actual list contains duplicates", actualSet.size(), actual.size());
+        assertEquals(expectedSet, actualSet);
+    }
+
+    private void assertCompareOrUpdateResult(CompareOrUpdateResult result,
+            List<String> expectedAdded, List<String> expectedRemoved,
+            List<String> expectedUpdated) {
+        assertSameElements(expectedAdded, result.added);
+        assertSameElements(expectedRemoved, result.removed);
+        assertSameElements(expectedUpdated, result.updated);
+    }
+
+    private List<String> strArray(String... strs) {
+        return Arrays.asList(strs);
+    }
+
+    @Test
+    public void testCompareOrUpdateResult() {
+        // As the item type, use a simple string. An item is defined to be an update of another item
+        // if the string starts with the same alphabetical characters.
+        // Extracting the key from the object is just a regexp.
+        Function<String, String> extractPrefix = (s) -> s.replaceFirst("^([a-z]+).*", "$1");
+        assertEquals("goodbye", extractPrefix.apply("goodbye1234"));
+
+        List<String> oldItems = strArray("hello123", "goodbye5678", "howareyou669");
+        List<String> newItems = strArray("hello123", "goodbye000", "verywell");
+
+        final List<String> emptyList = new ArrayList<>();
+
+        // Items -> empty: everything removed.
+        CompareOrUpdateResult<String, String> result =
+                new CompareOrUpdateResult<String, String>(oldItems, emptyList, extractPrefix);
+        assertCompareOrUpdateResult(result,
+                emptyList,  strArray("hello123", "howareyou669", "goodbye5678"), emptyList);
+
+        // Empty -> items: everything added.
+        result = new CompareOrUpdateResult<String, String>(emptyList, newItems, extractPrefix);
+        assertCompareOrUpdateResult(result,
+                strArray("hello123", "goodbye000", "verywell"), emptyList,  emptyList);
+
+        // Empty -> empty: no change.
+        result = new CompareOrUpdateResult<String, String>(newItems, newItems, extractPrefix);
+        assertCompareOrUpdateResult(result,  emptyList,  emptyList, emptyList);
+
+        // Added, removed, updated at the same time.
+        result =  new CompareOrUpdateResult<>(oldItems, newItems, extractPrefix);
+        assertCompareOrUpdateResult(result,
+                strArray("verywell"), strArray("howareyou669"), strArray("goodbye000"));
+    }
+}
\ No newline at end of file