Merge "Fix the test flake on ConnectivityServiceTest"
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 6e30fd1..700a085 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -135,6 +135,37 @@
         }
       ]
     },
+    // Test with APK modules only, in cases where APEX is not supported, or the other modules
+    // were simply not updated
+    {
+      "name": "CtsNetTestCasesLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk]",
+      "options": [
+        {
+          "exclude-annotation": "com.android.testutils.SkipPresubmit"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.RequiresDevice"
+        },
+        {
+          "exclude-annotation": "com.android.testutils.ConnectivityModuleTest"
+        }
+      ]
+    },
+    // Test with connectivity/tethering module only, to catch integration issues with older versions
+    // of other modules. "new tethering + old NetworkStack" is not a configuration that should
+    // really exist in the field, but there is no strong guarantee, and it is required by MTS
+    // testing for module qualification, where modules are tested independently.
+    {
+      "name": "CtsNetTestCasesLatestSdk[com.google.android.tethering.apex]",
+      "options": [
+        {
+          "exclude-annotation": "com.android.testutils.SkipPresubmit"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.RequiresDevice"
+        }
+      ]
+    },
     {
       "name": "bpf_existence_test[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
     },
@@ -159,38 +190,6 @@
     {
       "name": "CtsNetTestCasesLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
       "keywords": ["sim"]
-    },
-    // TODO: move to mainline-presubmit when known green.
-    // Test with APK modules only, in cases where APEX is not supported, or the other modules were simply not updated
-    {
-      "name": "CtsNetTestCasesLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk]",
-      "options": [
-        {
-          "exclude-annotation": "com.android.testutils.SkipPresubmit"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.RequiresDevice"
-        },
-        {
-          "exclude-annotation": "com.android.testutils.ConnectivityModuleTest"
-        }
-      ]
-    },
-    // TODO: move to mainline-presubmit when known green.
-    // Test with connectivity/tethering module only, to catch integration issues with older versions of other modules.
-    // "new tethering + old NetworkStack" is not a configuration that should really exist in the field, but
-    // there is no strong guarantee, and it is required by MTS testing for module qualification, where modules
-    // are tested independently.
-    {
-      "name": "CtsNetTestCasesLatestSdk[com.google.android.tethering.apex]",
-      "options": [
-        {
-          "exclude-annotation": "com.android.testutils.SkipPresubmit"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.RequiresDevice"
-        }
-      ]
     }
   ],
   "imports": [
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 142a0b9..74ba209 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -536,6 +536,13 @@
         // TODO: Wrap conntrackMonitor stopping function into mBpfCoordinatorShim.
         if (!isUsingBpf() || !mDeps.isAtLeastS()) return;
 
+        // Ignore stopping monitoring if the monitor has never started for a given IpServer.
+        if (!mMonitoringIpServers.contains(ipServer)) {
+            mLog.e("Ignore stopping monitoring because monitoring has never started for "
+                    + ipServer.interfaceName());
+            return;
+        }
+
         mMonitoringIpServers.remove(ipServer);
 
         if (!mMonitoringIpServers.isEmpty()) return;
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
index 758b533..ac92b43 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
@@ -1413,7 +1413,7 @@
 
         // [1] Don't stop monitoring if it has never started.
         coordinator.stopMonitoring(mIpServer);
-        verify(mConntrackMonitor, never()).start();
+        verify(mConntrackMonitor, never()).stop();
 
         // [2] Start monitoring.
         coordinator.startMonitoring(mIpServer);
diff --git a/service/ServiceConnectivityResources/res/values/config.xml b/service/ServiceConnectivityResources/res/values/config.xml
index 7d777ec..22d9b01 100644
--- a/service/ServiceConnectivityResources/res/values/config.xml
+++ b/service/ServiceConnectivityResources/res/values/config.xml
@@ -97,7 +97,7 @@
 
          On Android 14 and above, the behavior is always like 1, regardless of the value of this
          setting. -->
-    <integer translatable="false" name="config_activelyPreferBadWifi">1</integer>
+    <integer translatable="false" name="config_activelyPreferBadWifi">0</integer>
 
     <!-- Array of ConnectivityManager.TYPE_xxxx constants for networks that may only
          be controlled by systemOrSignature apps.  -->
diff --git a/service/native/TrafficController.cpp b/service/native/TrafficController.cpp
index 49b23c9..245b9d8 100644
--- a/service/native/TrafficController.cpp
+++ b/service/native/TrafficController.cpp
@@ -667,71 +667,6 @@
     }
 
     dw.blankline();
-
-    uint32_t key = UID_RULES_CONFIGURATION_KEY;
-    auto configuration = mConfigurationMap.readValue(key);
-    if (configuration.ok()) {
-        dw.println("current ownerMatch configuration: %d%s", configuration.value(),
-                   uidMatchTypeToString(configuration.value()).c_str());
-    } else {
-        dw.println("mConfigurationMap read ownerMatch configure failed with error: %s",
-                   configuration.error().message().c_str());
-    }
-
-    key = CURRENT_STATS_MAP_CONFIGURATION_KEY;
-    configuration = mConfigurationMap.readValue(key);
-    if (configuration.ok()) {
-        const char* statsMapDescription = "???";
-        switch (configuration.value()) {
-            case SELECT_MAP_A:
-                statsMapDescription = "SELECT_MAP_A";
-                break;
-            case SELECT_MAP_B:
-                statsMapDescription = "SELECT_MAP_B";
-                break;
-                // No default clause, so if we ever add a third map, this code will fail to build.
-        }
-        dw.println("current statsMap configuration: %d %s", configuration.value(),
-                   statsMapDescription);
-    } else {
-        dw.println("mConfigurationMap read stats map configure failed with error: %s",
-                   configuration.error().message().c_str());
-    }
-    dumpBpfMap("mUidOwnerMap", dw, "");
-    const auto printUidMatchInfo = [&dw, this](const uint32_t& key, const UidOwnerValue& value,
-                                               const BpfMap<uint32_t, UidOwnerValue>&) {
-        if (value.rule & IIF_MATCH) {
-            auto ifname = mIfaceIndexNameMap.readValue(value.iif);
-            if (ifname.ok()) {
-                dw.println("%u %s %s", key, uidMatchTypeToString(value.rule).c_str(),
-                           ifname.value().name);
-            } else {
-                dw.println("%u %s %u", key, uidMatchTypeToString(value.rule).c_str(), value.iif);
-            }
-        } else {
-            dw.println("%u %s", key, uidMatchTypeToString(value.rule).c_str());
-        }
-        return base::Result<void>();
-    };
-    res = mUidOwnerMap.iterateWithValue(printUidMatchInfo);
-    if (!res.ok()) {
-        dw.println("mUidOwnerMap print end with error: %s", res.error().message().c_str());
-    }
-    dumpBpfMap("mUidPermissionMap", dw, "");
-    const auto printUidPermissionInfo = [&dw](const uint32_t& key, const int& value,
-                                              const BpfMap<uint32_t, uint8_t>&) {
-        dw.println("%u %s", key, UidPermissionTypeToString(value).c_str());
-        return base::Result<void>();
-    };
-    res = mUidPermissionMap.iterateWithValue(printUidPermissionInfo);
-    if (!res.ok()) {
-        dw.println("mUidPermissionMap print end with error: %s", res.error().message().c_str());
-    }
-
-    dumpBpfMap("mPrivilegedUser", dw, "");
-    for (uid_t uid : mPrivilegedUser) {
-        dw.println("%u ALLOW_UPDATE_DEVICE_STATS", (uint32_t)uid);
-    }
 }
 
 }  // namespace net
diff --git a/service/native/TrafficControllerTest.cpp b/service/native/TrafficControllerTest.cpp
index 8525738..2f5960f 100644
--- a/service/native/TrafficControllerTest.cpp
+++ b/service/native/TrafficControllerTest.cpp
@@ -793,16 +793,6 @@
         "mCookieTagMap:",
         fmt::format("cookie={} tag={:#x} uid={}", TEST_COOKIE, TEST_TAG, TEST_UID)};
 
-    ASSERT_TRUE(isOk(updateUidOwnerMaps({TEST_UID}, HAPPY_BOX_MATCH,
-                                        TrafficController::IptOpInsert)));
-    expectedLines.emplace_back("mUidOwnerMap:");
-    expectedLines.emplace_back(fmt::format("{}  HAPPY_BOX_MATCH", TEST_UID));
-
-    mTc.setPermissionForUids(INetd::PERMISSION_UPDATE_DEVICE_STATS, {TEST_UID2});
-    expectedLines.emplace_back("mUidPermissionMap:");
-    expectedLines.emplace_back(fmt::format("{}  BPF_PERMISSION_UPDATE_DEVICE_STATS", TEST_UID2));
-    expectedLines.emplace_back("mPrivilegedUser:");
-    expectedLines.emplace_back(fmt::format("{} ALLOW_UPDATE_DEVICE_STATS", TEST_UID2));
     EXPECT_TRUE(expectDumpsysContains(expectedLines));
 }
 
@@ -813,59 +803,13 @@
             "Bad file descriptor";
     const std::string kErrReadRulesConfig = "read ownerMatch configure failed with error: "
             "Read value of map -1 failed: Bad file descriptor";
-    const std::string kErrReadStatsMapConfig = "read stats map configure failed with error: "
-            "Read value of map -1 failed: Bad file descriptor";
 
     std::vector<std::string> expectedLines = {
         fmt::format("mCookieTagMap {}", kErrIterate),
-        fmt::format("mIfaceStatsMap {}", kErrIterate),
-        fmt::format("mConfigurationMap {}", kErrReadRulesConfig),
-        fmt::format("mConfigurationMap {}", kErrReadStatsMapConfig),
-        fmt::format("mUidOwnerMap {}", kErrIterate),
-        fmt::format("mUidPermissionMap {}", kErrIterate)};
+        fmt::format("mIfaceStatsMap {}", kErrIterate)};
     EXPECT_TRUE(expectDumpsysContains(expectedLines));
 }
 
-TEST_F(TrafficControllerTest, uidMatchTypeToString) {
-    // NO_MATCH(0) can't be verified because match type flag is added by OR operator.
-    // See TrafficController::addRule()
-    static const struct TestConfig {
-        UidOwnerMatchType uidOwnerMatchType;
-        std::string expected;
-    } testConfigs[] = {
-            // clang-format off
-            {HAPPY_BOX_MATCH, "HAPPY_BOX_MATCH"},
-            {DOZABLE_MATCH, "DOZABLE_MATCH"},
-            {STANDBY_MATCH, "STANDBY_MATCH"},
-            {POWERSAVE_MATCH, "POWERSAVE_MATCH"},
-            {HAPPY_BOX_MATCH, "HAPPY_BOX_MATCH"},
-            {RESTRICTED_MATCH, "RESTRICTED_MATCH"},
-            {LOW_POWER_STANDBY_MATCH, "LOW_POWER_STANDBY_MATCH"},
-            {IIF_MATCH, "IIF_MATCH"},
-            {LOCKDOWN_VPN_MATCH, "LOCKDOWN_VPN_MATCH"},
-            {OEM_DENY_1_MATCH, "OEM_DENY_1_MATCH"},
-            {OEM_DENY_2_MATCH, "OEM_DENY_2_MATCH"},
-            {OEM_DENY_3_MATCH, "OEM_DENY_3_MATCH"},
-            // clang-format on
-    };
-
-    for (const auto& config : testConfigs) {
-        SCOPED_TRACE(fmt::format("testConfig: [{}, {}]", config.uidOwnerMatchType,
-                     config.expected));
-
-        // Test private function uidMatchTypeToString() via dumpsys.
-        ASSERT_TRUE(isOk(updateUidOwnerMaps({TEST_UID}, config.uidOwnerMatchType,
-                                            TrafficController::IptOpInsert)));
-        std::vector<std::string> expectedLines;
-        expectedLines.emplace_back(fmt::format("{}  {}", TEST_UID, config.expected));
-        EXPECT_TRUE(expectDumpsysContains(expectedLines));
-
-        // Clean up the stubs.
-        ASSERT_TRUE(isOk(updateUidOwnerMaps({TEST_UID}, config.uidOwnerMatchType,
-                                            TrafficController::IptOpDelete)));
-    }
-}
-
 TEST_F(TrafficControllerTest, getFirewallType) {
     static const struct TestConfig {
         ChildChain childChain;
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index dbfc383..d560747 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -27,7 +27,9 @@
 import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
 import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
 import static android.net.INetd.PERMISSION_INTERNET;
+import static android.net.INetd.PERMISSION_NONE;
 import static android.net.INetd.PERMISSION_UNINSTALLED;
+import static android.net.INetd.PERMISSION_UPDATE_DEVICE_STATS;
 import static android.system.OsConstants.EINVAL;
 import static android.system.OsConstants.ENODEV;
 import static android.system.OsConstants.ENOENT;
@@ -44,12 +46,15 @@
 import android.system.ErrnoException;
 import android.system.Os;
 import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
 import android.util.Log;
+import android.util.Pair;
 import android.util.StatsEvent;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.modules.utils.BackgroundThread;
 import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.BpfDump;
 import com.android.net.module.util.BpfMap;
 import com.android.net.module.util.DeviceConfigUtils;
 import com.android.net.module.util.IBpfMap;
@@ -62,8 +67,10 @@
 
 import java.io.FileDescriptor;
 import java.io.IOException;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Set;
+import java.util.StringJoiner;
 
 /**
  * BpfNetMaps is responsible for providing traffic controller relevant functionality.
@@ -134,6 +141,25 @@
     @VisibleForTesting public static final long OEM_DENY_3_MATCH = (1 << 11);
     // LINT.ThenChange(packages/modules/Connectivity/bpf_progs/bpf_shared.h)
 
+    private static final List<Pair<Integer, String>> PERMISSION_LIST = Arrays.asList(
+            Pair.create(PERMISSION_INTERNET, "PERMISSION_INTERNET"),
+            Pair.create(PERMISSION_UPDATE_DEVICE_STATS, "PERMISSION_UPDATE_DEVICE_STATS")
+    );
+    private static final List<Pair<Long, String>> MATCH_LIST = Arrays.asList(
+            Pair.create(HAPPY_BOX_MATCH, "HAPPY_BOX_MATCH"),
+            Pair.create(PENALTY_BOX_MATCH, "PENALTY_BOX_MATCH"),
+            Pair.create(DOZABLE_MATCH, "DOZABLE_MATCH"),
+            Pair.create(STANDBY_MATCH, "STANDBY_MATCH"),
+            Pair.create(POWERSAVE_MATCH, "POWERSAVE_MATCH"),
+            Pair.create(RESTRICTED_MATCH, "RESTRICTED_MATCH"),
+            Pair.create(LOW_POWER_STANDBY_MATCH, "LOW_POWER_STANDBY_MATCH"),
+            Pair.create(IIF_MATCH, "IIF_MATCH"),
+            Pair.create(LOCKDOWN_VPN_MATCH, "LOCKDOWN_VPN_MATCH"),
+            Pair.create(OEM_DENY_1_MATCH, "OEM_DENY_1_MATCH"),
+            Pair.create(OEM_DENY_2_MATCH, "OEM_DENY_2_MATCH"),
+            Pair.create(OEM_DENY_3_MATCH, "OEM_DENY_3_MATCH")
+    );
+
     /**
      * Set sEnableJavaBpfMap for test.
      */
@@ -914,14 +940,80 @@
         return StatsManager.PULL_SUCCESS;
     }
 
+    private String permissionToString(int permissionMask) {
+        if (permissionMask == PERMISSION_NONE) {
+            return "PERMISSION_NONE";
+        }
+        if (permissionMask == PERMISSION_UNINSTALLED) {
+            // PERMISSION_UNINSTALLED should never appear in the map
+            return "PERMISSION_UNINSTALLED error!";
+        }
+
+        final StringJoiner sj = new StringJoiner(" ");
+        for (Pair<Integer, String> permission: PERMISSION_LIST) {
+            final int permissionFlag = permission.first;
+            final String permissionName = permission.second;
+            if ((permissionMask & permissionFlag) != 0) {
+                sj.add(permissionName);
+                permissionMask &= ~permissionFlag;
+            }
+        }
+        if (permissionMask != 0) {
+            sj.add("PERMISSION_UNKNOWN(" + permissionMask + ")");
+        }
+        return sj.toString();
+    }
+
+    private String matchToString(long matchMask) {
+        if (matchMask == NO_MATCH) {
+            return "NO_MATCH";
+        }
+
+        final StringJoiner sj = new StringJoiner(" ");
+        for (Pair<Long, String> match: MATCH_LIST) {
+            final long matchFlag = match.first;
+            final String matchName = match.second;
+            if ((matchMask & matchFlag) != 0) {
+                sj.add(matchName);
+                matchMask &= ~matchFlag;
+            }
+        }
+        if (matchMask != 0) {
+            sj.add("UNKNOWN_MATCH(" + matchMask + ")");
+        }
+        return sj.toString();
+    }
+
+    private void dumpOwnerMatchConfig(final IndentingPrintWriter pw) {
+        try {
+            final long match = sConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY).val;
+            pw.println("current ownerMatch configuration: " + match + " " + matchToString(match));
+        } catch (ErrnoException e) {
+            pw.println("Failed to read ownerMatch configuration: " + e);
+        }
+    }
+
+    private void dumpCurrentStatsMapConfig(final IndentingPrintWriter pw) {
+        try {
+            final long config = sConfigurationMap.getValue(CURRENT_STATS_MAP_CONFIGURATION_KEY).val;
+            final String currentStatsMap =
+                    (config == STATS_SELECT_MAP_A) ? "SELECT_MAP_A" : "SELECT_MAP_B";
+            pw.println("current statsMap configuration: " + config + " " + currentStatsMap);
+        } catch (ErrnoException e) {
+            pw.println("Falied to read current statsMap configuration: " + e);
+        }
+    }
+
     /**
      * Dump BPF maps
      *
+     * @param pw print writer
      * @param fd file descriptor to output
+     * @param verbose verbose dump flag, if true dump the BpfMap contents
      * @throws IOException when file descriptor is invalid.
      * @throws ServiceSpecificException when the method is called on an unsupported device.
      */
-    public void dump(final FileDescriptor fd, boolean verbose)
+    public void dump(final IndentingPrintWriter pw, final FileDescriptor fd, boolean verbose)
             throws IOException, ServiceSpecificException {
         if (PRE_T) {
             throw new ServiceSpecificException(
@@ -929,6 +1021,24 @@
                     + " devices, use dumpsys netd trafficcontroller instead.");
         }
         mDeps.nativeDump(fd, verbose);
+
+        if (verbose) {
+            dumpOwnerMatchConfig(pw);
+            dumpCurrentStatsMapConfig(pw);
+            pw.println();
+
+            BpfDump.dumpMap(sUidOwnerMap, pw, "sUidOwnerMap",
+                    (uid, match) -> {
+                        if ((match.rule & IIF_MATCH) != 0) {
+                            // TODO: convert interface index to interface name by IfaceIndexNameMap
+                            return uid.val + " " + matchToString(match.rule) + " " + match.iif;
+                        } else {
+                            return uid.val + " " + matchToString(match.rule);
+                        }
+                    });
+            BpfDump.dumpMap(sUidPermissionMap, pw, "sUidPermissionMap",
+                    (uid, permission) -> uid.val + " " + permissionToString(permission.val));
+        }
     }
 
     private static native void native_init(boolean startSkDestroyListener);
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 1236243..6804baf 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -3457,7 +3457,7 @@
     private void dumpTrafficController(IndentingPrintWriter pw, final FileDescriptor fd,
             boolean verbose) {
         try {
-            mBpfNetMaps.dump(fd, verbose);
+            mBpfNetMaps.dump(pw, fd, verbose);
         } catch (ServiceSpecificException e) {
             pw.println(e.getMessage());
         } catch (IOException e) {
@@ -5112,22 +5112,32 @@
 
         pw.println("Bad Wi-Fi avoidance: " + avoidBadWifi());
         pw.increaseIndent();
-        pw.println("Config restrict:   " + configRestrict);
-        pw.println("Actively prefer:   " + activelyPreferBadWifi());
+        pw.println("Config restrict:               " + configRestrict);
+        pw.println("Actively prefer bad wifi:      " + activelyPreferBadWifi());
 
-        final String value = mMultinetworkPolicyTracker.getAvoidBadWifiSetting();
+        final String settingValue = mMultinetworkPolicyTracker.getAvoidBadWifiSetting();
         String description;
         // Can't use a switch statement because strings are legal case labels, but null is not.
-        if ("0".equals(value)) {
+        if ("0".equals(settingValue)) {
             description = "get stuck";
-        } else if (value == null) {
+        } else if (settingValue == null) {
             description = "prompt";
-        } else if ("1".equals(value)) {
+        } else if ("1".equals(settingValue)) {
             description = "avoid";
         } else {
-            description = value + " (?)";
+            description = settingValue + " (?)";
         }
-        pw.println("User setting:      " + description);
+        pw.println("Avoid bad wifi setting:        " + description);
+        final Boolean configValue = mMultinetworkPolicyTracker.deviceConfigActivelyPreferBadWifi();
+        if (null == configValue) {
+            description = "unset";
+        } else if (configValue) {
+            description = "force true";
+        } else {
+            description = "force false";
+        }
+        pw.println("Actively prefer bad wifi conf: " + description);
+        pw.println();
         pw.println("Network overrides:");
         pw.increaseIndent();
         for (NetworkAgentInfo nai : networksSortedById()) {
diff --git a/service/src/com/android/server/connectivity/MultinetworkPolicyTracker.java b/service/src/com/android/server/connectivity/MultinetworkPolicyTracker.java
index a36bf4b..58196f7 100644
--- a/service/src/com/android/server/connectivity/MultinetworkPolicyTracker.java
+++ b/service/src/com/android/server/connectivity/MultinetworkPolicyTracker.java
@@ -32,6 +32,7 @@
 import android.net.Uri;
 import android.os.Build;
 import android.os.Handler;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyCallback;
@@ -41,6 +42,7 @@
 import com.android.connectivity.resources.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.DeviceConfigUtils;
 
 import java.util.Arrays;
 import java.util.List;
@@ -67,6 +69,9 @@
 public class MultinetworkPolicyTracker {
     private static String TAG = MultinetworkPolicyTracker.class.getSimpleName();
 
+    // See Dependencies#getConfigActivelyPreferBadWifi
+    public static final String CONFIG_ACTIVELY_PREFER_BAD_WIFI = "actively_prefer_bad_wifi";
+
     private final Context mContext;
     private final ConnectivityResources mResources;
     private final Handler mHandler;
@@ -82,6 +87,43 @@
     private volatile long mTestAllowBadWifiUntilMs = 0;
 
     /**
+     * Dependencies for testing
+     */
+    @VisibleForTesting
+    public static class Dependencies {
+        /**
+         * @see DeviceConfigUtils#getDeviceConfigPropertyInt
+         */
+        protected int getConfigActivelyPreferBadWifi() {
+            // CONFIG_ACTIVELY_PREFER_BAD_WIFI is not a feature to be rolled out, but an override
+            // for tests and an emergency kill switch (which could force the behavior on OR off).
+            // As such it uses a -1/null/1 scheme, but features should use
+            // DeviceConfigUtils#isFeatureEnabled instead, to make sure rollbacks disable the
+            // feature before it's ready on R and before.
+            return DeviceConfig.getInt(DeviceConfig.NAMESPACE_CONNECTIVITY,
+                    CONFIG_ACTIVELY_PREFER_BAD_WIFI, 0);
+        }
+
+        /**
+         @see DeviceConfig#addOnPropertiesChangedListener
+         */
+        protected void addOnDevicePropertiesChangedListener(@NonNull final Executor executor,
+                @NonNull final DeviceConfig.OnPropertiesChangedListener listener) {
+            DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_CONNECTIVITY,
+                    executor, listener);
+        }
+
+        @VisibleForTesting
+        @NonNull
+        protected Resources getResourcesForActiveSubId(
+                @NonNull final ConnectivityResources resources, final int activeSubId) {
+            return SubscriptionManager.getResourcesForSubId(
+                    resources.getResourcesContext(), activeSubId);
+        }
+    }
+    private final Dependencies mDeps;
+
+    /**
      * Whether to prefer bad wifi to a network that yields to bad wifis, even if it never validated
      *
      * This setting only makes sense if the system is configured not to avoid bad wifis, i.e.
@@ -129,15 +171,17 @@
         }
     }
 
-    public MultinetworkPolicyTracker(Context ctx, Handler handler) {
-        this(ctx, handler, null);
+    public MultinetworkPolicyTracker(Context ctx, Handler handler, Runnable avoidBadWifiCallback) {
+        this(ctx, handler, avoidBadWifiCallback, new Dependencies());
     }
 
-    public MultinetworkPolicyTracker(Context ctx, Handler handler, Runnable avoidBadWifiCallback) {
+    public MultinetworkPolicyTracker(Context ctx, Handler handler, Runnable avoidBadWifiCallback,
+            Dependencies deps) {
         mContext = ctx;
         mResources = new ConnectivityResources(ctx);
         mHandler = handler;
         mAvoidBadWifiCallback = avoidBadWifiCallback;
+        mDeps = deps;
         mSettingsUris = Arrays.asList(
                 Settings.Global.getUriFor(NETWORK_AVOID_BAD_WIFI),
                 Settings.Global.getUriFor(NETWORK_METERED_MULTIPATH_PREFERENCE));
@@ -166,8 +210,11 @@
         mContext.registerReceiverForAllUsers(mBroadcastReceiver, intentFilter,
                 null /* broadcastPermission */, mHandler);
 
+        final Executor handlerExecutor = new HandlerExecutor(mHandler);
         mContext.getSystemService(TelephonyManager.class).registerTelephonyCallback(
-                new HandlerExecutor(mHandler), new ActiveDataSubscriptionIdListener());
+                handlerExecutor, new ActiveDataSubscriptionIdListener());
+        mDeps.addOnDevicePropertiesChangedListener(handlerExecutor,
+                properties -> reevaluateInternal());
 
         reevaluate();
     }
@@ -201,7 +248,7 @@
         // NETWORK_AVOID_BAD_WIFI setting.
         if (allowBadWifi) return true;
 
-        return getResourcesForActiveSubId()
+        return mDeps.getResourcesForActiveSubId(mResources, mActiveSubId)
                 .getInteger(R.integer.config_networkAvoidBadWifi) == 0;
     }
 
@@ -223,7 +270,7 @@
         // bad wifi (only stay stuck on it if already on there). This implementation treats
         // any non-0 value like 1, on the assumption that anybody setting it non-zero wants
         // the newer behavior.
-        return 0 != getResourcesForActiveSubId()
+        return 0 != mDeps.getResourcesForActiveSubId(mResources, mActiveSubId)
                 .getInteger(R.integer.config_activelyPreferBadWifi);
     }
 
@@ -237,13 +284,6 @@
         reevaluateInternal();
     }
 
-    @VisibleForTesting
-    @NonNull
-    protected Resources getResourcesForActiveSubId() {
-        return SubscriptionManager.getResourcesForSubId(
-                mResources.getResourcesContext(), mActiveSubId);
-    }
-
     /**
      * Whether we should display a notification when wifi becomes unvalidated.
      */
@@ -255,6 +295,29 @@
         return Settings.Global.getString(mResolver, NETWORK_AVOID_BAD_WIFI);
     }
 
+    /**
+     * Returns whether device config says the device should actively prefer bad wifi.
+     *
+     * {@see #configActivelyPrefersBadWifi} for a description of what this does. This device
+     * config overrides that config overlay.
+     *
+     * @return True on Android U and above.
+     *         True if device config says to actively prefer bad wifi.
+     *         False if device config says not to actively prefer bad wifi.
+     *         null if device config doesn't have an opinion (then fall back on the resource).
+     */
+    public Boolean deviceConfigActivelyPreferBadWifi() {
+        if (SdkLevel.isAtLeastU()) return true;
+        switch (mDeps.getConfigActivelyPreferBadWifi()) {
+            case 1:
+                return Boolean.TRUE;
+            case -1:
+                return Boolean.FALSE;
+            default:
+                return null;
+        }
+    }
+
     @VisibleForTesting
     public void reevaluate() {
         mHandler.post(this::reevaluateInternal);
@@ -276,7 +339,12 @@
         mAvoidBadWifi = settingAvoidBadWifi || !configRestrictsAvoidBadWifi();
 
         final boolean prevActive = mActivelyPreferBadWifi;
-        mActivelyPreferBadWifi = configActivelyPrefersBadWifi();
+        final Boolean deviceConfigPreferBadWifi = deviceConfigActivelyPreferBadWifi();
+        if (null == deviceConfigPreferBadWifi) {
+            mActivelyPreferBadWifi = configActivelyPrefersBadWifi();
+        } else {
+            mActivelyPreferBadWifi = deviceConfigPreferBadWifi;
+        }
 
         return mAvoidBadWifi != prevAvoid || mActivelyPreferBadWifi != prevActive;
     }
@@ -285,10 +353,8 @@
      * The default (device and carrier-dependent) value for metered multipath preference.
      */
     public int configMeteredMultipathPreference() {
-        // TODO: use R.integer.config_networkMeteredMultipathPreference directly
-        final int id = mResources.get().getIdentifier("config_networkMeteredMultipathPreference",
-                "integer", mResources.getResourcesContext().getPackageName());
-        return mResources.get().getInteger(id);
+        return mDeps.getResourcesForActiveSubId(mResources, mActiveSubId)
+                .getInteger(R.integer.config_networkMeteredMultipathPreference);
     }
 
     public void updateMeteredMultipathPreference() {
diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
index c732170..2e92d43 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -1208,7 +1208,7 @@
      */
     public void setScore(final NetworkScore score) {
         mScore = FullScore.fromNetworkScore(score, networkCapabilities, networkAgentConfig,
-                everValidated(), 0L == getAvoidUnvalidated(), yieldToBadWiFi(),
+                everValidated(), 0L != getAvoidUnvalidated(), yieldToBadWiFi(),
                 0L != mFirstEvaluationConcludedTime, isDestroyed());
     }
 
diff --git a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
index 74e57c9..3635b7b 100644
--- a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
@@ -41,6 +41,7 @@
 import android.net.MacAddress
 import android.net.Network
 import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED
 import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED
 import android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED
 import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
@@ -69,6 +70,7 @@
 import com.android.testutils.DeviceInfoUtils.isKernelVersionAtLeast
 import com.android.testutils.RecorderCallback.CallbackEntry.Available
 import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
+import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
 import com.android.testutils.RecorderCallback.CallbackEntry.Lost
 import com.android.testutils.RouterAdvertisementResponder
 import com.android.testutils.TapPacketReader
@@ -114,6 +116,10 @@
         .addTransportType(TRANSPORT_ETHERNET)
         .removeCapability(NET_CAPABILITY_TRUSTED)
         .build()
+private val TEST_CAPS = NetworkCapabilities.Builder(ETH_REQUEST.networkCapabilities)
+        .addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)
+        .addCapability(NET_CAPABILITY_NOT_CONGESTED)
+        .build()
 private val STATIC_IP_CONFIGURATION = IpConfiguration.Builder()
         .setStaticIpConfiguration(StaticIpConfiguration.Builder()
                 .setIpAddress(LinkAddress("192.0.2.1/30")).build())
@@ -149,6 +155,7 @@
         private val raResponder: RouterAdvertisementResponder
         private val tnm: TestNetworkManager
         val name get() = tapInterface.interfaceName
+        val onLinkPrefix get() = raResponder.prefix
 
         init {
             tnm = runAsShell(MANAGE_TEST_NETWORKS) {
@@ -536,17 +543,25 @@
             it.networkSpecifier == EthernetNetworkSpecifier(name)
         }
 
-    private fun TestableNetworkCallback.expectCapabilitiesWith(cap: Int) =
-        expectCapabilitiesThat(anyNetwork(), TIMEOUT_MS) {
-            it.hasCapability(cap)
+    private fun TestableNetworkCallback.eventuallyExpectCapabilities(nc: NetworkCapabilities) {
+        // b/233534110: eventuallyExpect<CapabilitiesChanged>() does not advance ReadHead.
+        eventuallyExpect(CapabilitiesChanged::class, TIMEOUT_MS) {
+            // CS may mix in additional capabilities, so NetworkCapabilities#equals cannot be used.
+            // Check if all expected capabilities are present instead.
+            it is CapabilitiesChanged && nc.capabilities.all { c -> it.caps.hasCapability(c) }
         }
+    }
 
-    private fun TestableNetworkCallback.expectLinkPropertiesWithLinkAddress(addr: LinkAddress) =
-        expectLinkPropertiesThat(anyNetwork(), TIMEOUT_MS) {
-            // LinkAddress.equals isn't possible as the system changes the LinkAddress.flags value.
-            // any() must be used since the interface may also have a link-local address.
-            it.linkAddresses.any { x -> x.isSameAddressAs(addr) }
+    private fun TestableNetworkCallback.eventuallyExpectLpForStaticConfig(
+        config: StaticIpConfiguration
+    ) {
+        // b/233534110: eventuallyExpect<LinkPropertiesChanged>() does not advance ReadHead.
+        eventuallyExpect(LinkPropertiesChanged::class, TIMEOUT_MS) {
+            it is LinkPropertiesChanged && it.lp.linkAddresses.any { la ->
+                la.isSameAddressAs(config.ipAddress)
+            }
         }
+    }
 
     @Test
     fun testCallbacks() {
@@ -689,6 +704,8 @@
 
         // install a listener which will later be used to verify the Lost callback
         val listenerCb = registerNetworkListener(ETH_REQUEST)
+        // assert the network is only brought up by a request.
+        listenerCb.assertNeverAvailable()
 
         val cb = requestNetwork(ETH_REQUEST)
         val network = cb.expectAvailable()
@@ -798,7 +815,10 @@
         val iface = createInterface(false /* hasCarrier */)
 
         val cb = requestNetwork(ETH_REQUEST)
-        cb.assertNeverAvailable()
+        // TUNSETCARRIER races with the bring up code, so the network *can* become available despite
+        // it being "created with no carrier".
+        // TODO(b/249611919): re-enable assertion once kernel supports IFF_NO_CARRIER.
+        // cb.assertNeverAvailable()
 
         iface.setCarrierEnabled(true)
         cb.expectAvailable()
@@ -807,6 +827,19 @@
         cb.eventuallyExpectLost()
     }
 
+    // TODO: move to MTS
+    @Test
+    fun testNetworkRequest_linkPropertiesUpdate() {
+        val iface = createInterface()
+        val cb = requestNetwork(ETH_REQUEST)
+        // b/233534110: eventuallyExpect<LinkPropertiesChanged>() does not advance ReadHead
+        cb.eventuallyExpect(LinkPropertiesChanged::class, TIMEOUT_MS) {
+            it is LinkPropertiesChanged && it.lp.addresses.any {
+                address -> iface.onLinkPrefix.contains(address)
+            }
+        }
+    }
+
     @Test
     fun testRemoveInterface_whileInServerMode() {
         assumeNoInterfaceForTetheringAvailable()
@@ -868,22 +901,10 @@
         val iface = createInterface()
         val cb = requestNetwork(ETH_REQUEST.copyWithEthernetSpecifier(iface.name))
         val network = cb.expectAvailable()
-        cb.assertNeverLost()
 
-        val testCapability = NET_CAPABILITY_TEMPORARILY_NOT_METERED
-        val nc = NetworkCapabilities
-                .Builder(ETH_REQUEST.networkCapabilities)
-                .addCapability(testCapability)
-                .build()
-        updateConfiguration(iface, STATIC_IP_CONFIGURATION, nc).expectResult(iface.name)
-
-        // UpdateConfiguration() currently does a restarts on the ethernet interface therefore lost
-        // will be expected first before available, as part of the restart.
-        cb.expectLost(network)
-        cb.expectAvailable()
-        cb.expectCapabilitiesWith(testCapability)
-        cb.expectLinkPropertiesWithLinkAddress(
-                STATIC_IP_CONFIGURATION.staticIpConfiguration.ipAddress!!)
+        updateConfiguration(iface, STATIC_IP_CONFIGURATION, TEST_CAPS).expectResult(iface.name)
+        cb.eventuallyExpectCapabilities(TEST_CAPS)
+        cb.eventuallyExpectLpForStaticConfig(STATIC_IP_CONFIGURATION.staticIpConfiguration)
     }
 
     @Test
@@ -891,17 +912,9 @@
         val iface = createInterface()
         val cb = requestNetwork(ETH_REQUEST.copyWithEthernetSpecifier(iface.name))
         val network = cb.expectAvailable()
-        cb.assertNeverLost()
 
         updateConfiguration(iface, STATIC_IP_CONFIGURATION).expectResult(iface.name)
-
-        // UpdateConfiguration() currently does a restarts on the ethernet interface therefore lost
-        // will be expected first before available, as part of the restart.
-        cb.expectLost(network)
-        cb.expectAvailable()
-        cb.expectCallback<CapabilitiesChanged>()
-        cb.expectLinkPropertiesWithLinkAddress(
-                STATIC_IP_CONFIGURATION.staticIpConfiguration.ipAddress!!)
+        cb.eventuallyExpectLpForStaticConfig(STATIC_IP_CONFIGURATION.staticIpConfiguration)
     }
 
     @Test
@@ -909,20 +922,9 @@
         val iface = createInterface()
         val cb = requestNetwork(ETH_REQUEST.copyWithEthernetSpecifier(iface.name))
         val network = cb.expectAvailable()
-        cb.assertNeverLost()
 
-        val testCapability = NET_CAPABILITY_TEMPORARILY_NOT_METERED
-        val nc = NetworkCapabilities
-                .Builder(ETH_REQUEST.networkCapabilities)
-                .addCapability(testCapability)
-                .build()
-        updateConfiguration(iface, capabilities = nc).expectResult(iface.name)
-
-        // UpdateConfiguration() currently does a restarts on the ethernet interface therefore lost
-        // will be expected first before available, as part of the restart.
-        cb.expectLost(network)
-        cb.expectAvailable()
-        cb.expectCapabilitiesWith(testCapability)
+        updateConfiguration(iface, capabilities = TEST_CAPS).expectResult(iface.name)
+        cb.eventuallyExpectCapabilities(TEST_CAPS)
     }
 
     @Test
@@ -957,4 +959,24 @@
         // With the test process UID allowed, binding to a restricted network should be successful.
         Socket().use { socket -> updatedNetwork.bindSocket(socket) }
     }
+
+    // TODO: consider only having this test in MTS as it makes use of the fact that
+    // setEthernetEnabled(false) untracks all interfaces. This behavior is an implementation detail
+    // and may change in the future.
+    @Test
+    fun testUpdateConfiguration_onUntrackedInterface() {
+        assumeFalse(isAdbOverEthernet())
+        val iface = createInterface()
+        setEthernetEnabled(false)
+
+        // Updating the IpConfiguration and NetworkCapabilities on absent interfaces is a supported
+        // use case.
+        updateConfiguration(iface, STATIC_IP_CONFIGURATION, TEST_CAPS).expectResult(iface.name)
+
+        setEthernetEnabled(true)
+        val cb = requestNetwork(ETH_REQUEST)
+        cb.expectAvailable()
+        cb.eventuallyExpectCapabilities(TEST_CAPS)
+        cb.eventuallyExpectLpForStaticConfig(STATIC_IP_CONFIGURATION.staticIpConfiguration)
+    }
 }
diff --git a/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt b/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt
index 8f17199..eb41d71 100644
--- a/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt
@@ -34,6 +34,7 @@
 import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
 import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.RecorderCallback.CallbackEntry
 import com.android.testutils.TestableNetworkCallback
 import com.android.testutils.TestableNetworkCallback.HasNetwork
 import org.junit.After
@@ -76,7 +77,18 @@
 
     @After
     fun tearDown() {
-        agentsToCleanUp.forEach { it.unregister() }
+        val agentCleanUpCb = TestableNetworkCallback(TIMEOUT_MS).also { cb ->
+            mCm.registerNetworkCallback(
+                NetworkRequest.Builder().clearCapabilities()
+                    .addTransportType(NetworkCapabilities.TRANSPORT_TEST).build(), cb, mHandler
+            )
+        }
+        agentsToCleanUp.forEach {
+            it.unregister()
+            agentCleanUpCb.eventuallyExpect<CallbackEntry.Lost> { cb -> cb.network == it.network }
+        }
+        mCm.unregisterNetworkCallback(agentCleanUpCb)
+
         mHandlerThread.quitSafely()
         callbacksToCleanUp.forEach { mCm.unregisterNetworkCallback(it) }
     }
diff --git a/tests/cts/net/src/android/net/cts/QosCallbackExceptionTest.java b/tests/cts/net/src/android/net/cts/QosCallbackExceptionTest.java
index ffb34e6..04abd72 100644
--- a/tests/cts/net/src/android/net/cts/QosCallbackExceptionTest.java
+++ b/tests/cts/net/src/android/net/cts/QosCallbackExceptionTest.java
@@ -29,6 +29,7 @@
 import android.net.SocketRemoteAddressChangedException;
 import android.os.Build;
 
+import com.android.testutils.ConnectivityModuleTest;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 import com.android.testutils.DevSdkIgnoreRunner;
 
@@ -37,6 +38,7 @@
 
 @RunWith(DevSdkIgnoreRunner.class)
 @IgnoreUpTo(Build.VERSION_CODES.R)
+@ConnectivityModuleTest
 public class QosCallbackExceptionTest {
     private static final String ERROR_MESSAGE = "Test Error Message";
 
diff --git a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
index 5b3f37d..26b058d 100644
--- a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
+++ b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -211,10 +211,15 @@
         doReturn(TestNetIdManager()).`when`(deps).makeNetIdManager()
         doReturn(mock(BpfNetMaps::class.java)).`when`(deps).getBpfNetMaps(any(), any())
         doAnswer { inv ->
-            object : MultinetworkPolicyTracker(inv.getArgument(0), inv.getArgument(1),
-                    inv.getArgument(2)) {
-                override fun getResourcesForActiveSubId() = resources
-            }
+            MultinetworkPolicyTracker(inv.getArgument(0),
+                    inv.getArgument(1),
+                    inv.getArgument(2),
+                    object : MultinetworkPolicyTracker.Dependencies() {
+                        override fun getResourcesForActiveSubId(
+                            connResources: ConnectivityResources,
+                            activeSubId: Int
+                        ) = resources
+                    })
         }.`when`(deps).makeMultinetworkPolicyTracker(any(), any(), any())
         return deps
     }
diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java
index 4966aed..0c00bc0 100644
--- a/tests/unit/java/com/android/server/BpfNetMapsTest.java
+++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java
@@ -37,10 +37,15 @@
 import static com.android.server.BpfNetMaps.HAPPY_BOX_MATCH;
 import static com.android.server.BpfNetMaps.IIF_MATCH;
 import static com.android.server.BpfNetMaps.LOCKDOWN_VPN_MATCH;
+import static com.android.server.BpfNetMaps.LOW_POWER_STANDBY_MATCH;
 import static com.android.server.BpfNetMaps.NO_MATCH;
+import static com.android.server.BpfNetMaps.OEM_DENY_1_MATCH;
+import static com.android.server.BpfNetMaps.OEM_DENY_2_MATCH;
+import static com.android.server.BpfNetMaps.OEM_DENY_3_MATCH;
 import static com.android.server.BpfNetMaps.PENALTY_BOX_MATCH;
 import static com.android.server.BpfNetMaps.POWERSAVE_MATCH;
 import static com.android.server.BpfNetMaps.RESTRICTED_MATCH;
+import static com.android.server.BpfNetMaps.STANDBY_MATCH;
 import static com.android.server.ConnectivityStatsLog.NETWORK_BPF_MAP_INFO;
 
 import static org.junit.Assert.assertEquals;
@@ -61,6 +66,7 @@
 import android.os.Build;
 import android.os.ServiceSpecificException;
 import android.system.ErrnoException;
+import android.util.IndentingPrintWriter;
 
 import androidx.test.filters.SmallTest;
 
@@ -84,6 +90,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.io.FileDescriptor;
+import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -138,6 +146,9 @@
         doReturn(0).when(mDeps).synchronizeKernelRCU();
         BpfNetMaps.setEnableJavaBpfMapForTest(true /* enable */);
         BpfNetMaps.setConfigurationMapForTest(mConfigurationMap);
+        mConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(0));
+        mConfigurationMap.updateEntry(
+                CURRENT_STATS_MAP_CONFIGURATION_KEY, new U32(STATS_SELECT_MAP_A));
         BpfNetMaps.setUidOwnerMapForTest(mUidOwnerMap);
         BpfNetMaps.setUidPermissionMapForTest(mUidPermissionMap);
         BpfNetMaps.setCookieTagMapForTest(mCookieTagMap);
@@ -927,4 +938,136 @@
         final int ret = mBpfNetMaps.pullBpfMapInfoAtom(-1 /* atomTag */, new ArrayList<>());
         assertEquals(StatsManager.PULL_SKIP, ret);
     }
+
+    private void assertDumpContains(final String dump, final String message) {
+        assertTrue(String.format("dump(%s) does not contain '%s'", dump, message),
+                dump.contains(message));
+    }
+
+    private String getDump() throws Exception {
+        final StringWriter sw = new StringWriter();
+        mBpfNetMaps.dump(new IndentingPrintWriter(sw), new FileDescriptor(), true /* verbose */);
+        return sw.toString();
+    }
+
+    private void doTestDumpUidPermissionMap(final int permission, final String permissionString)
+            throws Exception {
+        mUidPermissionMap.updateEntry(new S32(TEST_UID), new U8((short) permission));
+        assertDumpContains(getDump(), TEST_UID + " " + permissionString);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testDumpUidPermissionMap() throws Exception {
+        doTestDumpUidPermissionMap(PERMISSION_NONE, "PERMISSION_NONE");
+        doTestDumpUidPermissionMap(PERMISSION_INTERNET | PERMISSION_UPDATE_DEVICE_STATS,
+                "PERMISSION_INTERNET PERMISSION_UPDATE_DEVICE_STATS");
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testDumpUidPermissionMapInvalidPermission() throws Exception {
+        doTestDumpUidPermissionMap(PERMISSION_UNINSTALLED, "PERMISSION_UNINSTALLED error!");
+        doTestDumpUidPermissionMap(PERMISSION_INTERNET | 1 << 6,
+                "PERMISSION_INTERNET PERMISSION_UNKNOWN(64)");
+    }
+
+    void doTestDumpUidOwnerMap(final int iif, final long match, final String matchString)
+            throws Exception {
+        mUidOwnerMap.updateEntry(new S32(TEST_UID), new UidOwnerValue(iif, match));
+        assertDumpContains(getDump(), TEST_UID + " " + matchString);
+    }
+
+    void doTestDumpUidOwnerMap(final long match, final String matchString) throws Exception {
+        doTestDumpUidOwnerMap(0 /* iif */, match, matchString);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testDumpUidOwnerMap() throws Exception {
+        doTestDumpUidOwnerMap(HAPPY_BOX_MATCH, "HAPPY_BOX_MATCH");
+        doTestDumpUidOwnerMap(PENALTY_BOX_MATCH, "PENALTY_BOX_MATCH");
+        doTestDumpUidOwnerMap(DOZABLE_MATCH, "DOZABLE_MATCH");
+        doTestDumpUidOwnerMap(STANDBY_MATCH, "STANDBY_MATCH");
+        doTestDumpUidOwnerMap(POWERSAVE_MATCH, "POWERSAVE_MATCH");
+        doTestDumpUidOwnerMap(RESTRICTED_MATCH, "RESTRICTED_MATCH");
+        doTestDumpUidOwnerMap(LOW_POWER_STANDBY_MATCH, "LOW_POWER_STANDBY_MATCH");
+        doTestDumpUidOwnerMap(LOCKDOWN_VPN_MATCH, "LOCKDOWN_VPN_MATCH");
+        doTestDumpUidOwnerMap(OEM_DENY_1_MATCH, "OEM_DENY_1_MATCH");
+        doTestDumpUidOwnerMap(OEM_DENY_2_MATCH, "OEM_DENY_2_MATCH");
+        doTestDumpUidOwnerMap(OEM_DENY_3_MATCH, "OEM_DENY_3_MATCH");
+
+        doTestDumpUidOwnerMap(HAPPY_BOX_MATCH | POWERSAVE_MATCH,
+                "HAPPY_BOX_MATCH POWERSAVE_MATCH");
+        doTestDumpUidOwnerMap(DOZABLE_MATCH | LOCKDOWN_VPN_MATCH | OEM_DENY_1_MATCH,
+                "DOZABLE_MATCH LOCKDOWN_VPN_MATCH OEM_DENY_1_MATCH");
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testDumpUidOwnerMapWithIifMatch() throws Exception {
+        doTestDumpUidOwnerMap(TEST_IF_INDEX, IIF_MATCH, "IIF_MATCH " + TEST_IF_INDEX);
+        doTestDumpUidOwnerMap(TEST_IF_INDEX,
+                IIF_MATCH | DOZABLE_MATCH | LOCKDOWN_VPN_MATCH | OEM_DENY_1_MATCH,
+                "DOZABLE_MATCH IIF_MATCH LOCKDOWN_VPN_MATCH OEM_DENY_1_MATCH " + TEST_IF_INDEX);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testDumpUidOwnerMapWithInvalidMatch() throws Exception {
+        final long invalid_match = 1L << 31;
+        doTestDumpUidOwnerMap(invalid_match, "UNKNOWN_MATCH(" + invalid_match + ")");
+        doTestDumpUidOwnerMap(DOZABLE_MATCH | invalid_match,
+                "DOZABLE_MATCH UNKNOWN_MATCH(" + invalid_match + ")");
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testDumpCurrentStatsMapConfig() throws Exception {
+        mConfigurationMap.updateEntry(
+                CURRENT_STATS_MAP_CONFIGURATION_KEY, new U32(STATS_SELECT_MAP_A));
+        assertDumpContains(getDump(), "current statsMap configuration: 0 SELECT_MAP_A");
+
+        mConfigurationMap.updateEntry(
+                CURRENT_STATS_MAP_CONFIGURATION_KEY, new U32(STATS_SELECT_MAP_B));
+        assertDumpContains(getDump(), "current statsMap configuration: 1 SELECT_MAP_B");
+    }
+
+    private void doTestDumpOwnerMatchConfig(final long match, final String matchString)
+            throws Exception {
+        mConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(match));
+        assertDumpContains(getDump(),
+                "current ownerMatch configuration: " + match + " " + matchString);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testDumpUidOwnerMapConfig() throws Exception {
+        doTestDumpOwnerMatchConfig(HAPPY_BOX_MATCH, "HAPPY_BOX_MATCH");
+        doTestDumpOwnerMatchConfig(PENALTY_BOX_MATCH, "PENALTY_BOX_MATCH");
+        doTestDumpOwnerMatchConfig(DOZABLE_MATCH, "DOZABLE_MATCH");
+        doTestDumpOwnerMatchConfig(STANDBY_MATCH, "STANDBY_MATCH");
+        doTestDumpOwnerMatchConfig(POWERSAVE_MATCH, "POWERSAVE_MATCH");
+        doTestDumpOwnerMatchConfig(RESTRICTED_MATCH, "RESTRICTED_MATCH");
+        doTestDumpOwnerMatchConfig(LOW_POWER_STANDBY_MATCH, "LOW_POWER_STANDBY_MATCH");
+        doTestDumpOwnerMatchConfig(IIF_MATCH, "IIF_MATCH");
+        doTestDumpOwnerMatchConfig(LOCKDOWN_VPN_MATCH, "LOCKDOWN_VPN_MATCH");
+        doTestDumpOwnerMatchConfig(OEM_DENY_1_MATCH, "OEM_DENY_1_MATCH");
+        doTestDumpOwnerMatchConfig(OEM_DENY_2_MATCH, "OEM_DENY_2_MATCH");
+        doTestDumpOwnerMatchConfig(OEM_DENY_3_MATCH, "OEM_DENY_3_MATCH");
+
+        doTestDumpOwnerMatchConfig(HAPPY_BOX_MATCH | POWERSAVE_MATCH,
+                "HAPPY_BOX_MATCH POWERSAVE_MATCH");
+        doTestDumpOwnerMatchConfig(DOZABLE_MATCH | LOCKDOWN_VPN_MATCH | OEM_DENY_1_MATCH,
+                "DOZABLE_MATCH LOCKDOWN_VPN_MATCH OEM_DENY_1_MATCH");
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testDumpUidOwnerMapConfigWithInvalidMatch() throws Exception {
+        final long invalid_match = 1L << 31;
+        doTestDumpOwnerMatchConfig(invalid_match, "UNKNOWN_MATCH(" + invalid_match + ")");
+        doTestDumpOwnerMatchConfig(DOZABLE_MATCH | invalid_match,
+                "DOZABLE_MATCH UNKNOWN_MATCH(" + invalid_match + ")");
+    }
 }
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index be1c0af..f9f63ed 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -29,7 +29,6 @@
 import static android.Manifest.permission.NETWORK_SETTINGS;
 import static android.Manifest.permission.NETWORK_STACK;
 import static android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD;
-import static android.Manifest.permission.READ_DEVICE_CONFIG;
 import static android.app.PendingIntent.FLAG_IMMUTABLE;
 import static android.content.Intent.ACTION_PACKAGE_ADDED;
 import static android.content.Intent.ACTION_PACKAGE_REMOVED;
@@ -366,6 +365,7 @@
 import com.android.server.connectivity.ClatCoordinator;
 import com.android.server.connectivity.ConnectivityFlags;
 import com.android.server.connectivity.MultinetworkPolicyTracker;
+import com.android.server.connectivity.MultinetworkPolicyTrackerTestDependencies;
 import com.android.server.connectivity.Nat464Xlat;
 import com.android.server.connectivity.NetworkAgentInfo;
 import com.android.server.connectivity.NetworkNotificationManager;
@@ -1637,12 +1637,7 @@
         volatile int mConfigMeteredMultipathPreference;
 
         WrappedMultinetworkPolicyTracker(Context c, Handler h, Runnable r) {
-            super(c, h, r);
-        }
-
-        @Override
-        protected Resources getResourcesForActiveSubId() {
-            return mResources;
+            super(c, h, r, new MultinetworkPolicyTrackerTestDependencies(mResources));
         }
 
         @Override
@@ -5813,16 +5808,12 @@
     public void testPreferBadWifi_doNotPrefer() throws Exception {
         // Starting with U this mode is no longer supported and can't actually be tested
         assumeFalse(SdkLevel.isAtLeastU());
-        runAsShell(READ_DEVICE_CONFIG, () -> {
-            doTestPreferBadWifi(false /* preferBadWifi */);
-        });
+        doTestPreferBadWifi(false /* preferBadWifi */);
     }
 
     @Test
     public void testPreferBadWifi_doPrefer() throws Exception {
-        runAsShell(READ_DEVICE_CONFIG, () -> {
-            doTestPreferBadWifi(true /* preferBadWifi */);
-        });
+        doTestPreferBadWifi(true /* preferBadWifi */);
     }
 
     @Test
diff --git a/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTest.kt b/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTest.kt
index 8348675..b52e8a8 100644
--- a/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTest.kt
@@ -27,6 +27,7 @@
 import com.android.server.connectivity.MultinetworkPolicyTracker.ActiveDataSubscriptionIdListener
 import android.os.Build
 import android.os.Handler
+import android.os.test.TestLooper
 import android.provider.Settings
 import android.telephony.SubscriptionInfo
 import android.telephony.SubscriptionManager
@@ -47,7 +48,6 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.ArgumentMatchers.argThat
 import org.mockito.Mockito.any
 import org.mockito.Mockito.doCallRealMethod
 import org.mockito.Mockito.doReturn
@@ -55,6 +55,8 @@
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 
+const val HANDLER_TIMEOUT_MS = 400
+
 /**
  * Tests for [MultinetworkPolicyTracker].
  *
@@ -91,8 +93,11 @@
         Settings.Global.putString(resolver, NETWORK_AVOID_BAD_WIFI, "1")
         ConnectivityResources.setResourcesContextForTest(it)
     }
-    private val handler = mock(Handler::class.java)
-    private val tracker = MultinetworkPolicyTracker(context, handler)
+    private val csLooper = TestLooper()
+    private val handler = Handler(csLooper.looper)
+    private val trackerDependencies = MultinetworkPolicyTrackerTestDependencies(resources)
+    private val tracker = MultinetworkPolicyTracker(context, handler,
+            null /* avoidBadWifiCallback */, trackerDependencies)
 
     private fun assertMultipathPreference(preference: Int) {
         Settings.Global.putString(resolver, NETWORK_METERED_MULTIPATH_PREFERENCE,
@@ -146,6 +151,18 @@
         }
         // In all cases, now the system actively prefers bad wifi
         assertTrue(tracker.activelyPreferBadWifi)
+
+        // Remaining tests are only useful on T-, which support both the old and new mode.
+        if (SdkLevel.isAtLeastU()) return
+
+        doReturn(0).`when`(resources).getInteger(R.integer.config_activelyPreferBadWifi)
+        assertTrue(tracker.updateAvoidBadWifi())
+        assertFalse(tracker.activelyPreferBadWifi)
+
+        // Simulate update of device config
+        trackerDependencies.putConfigActivelyPreferBadWifi(1)
+        csLooper.dispatchAll()
+        assertTrue(tracker.activelyPreferBadWifi)
     }
 
     @Test
@@ -164,6 +181,8 @@
         Settings.Global.putString(resolver, NETWORK_METERED_MULTIPATH_PREFERENCE,
                 MULTIPATH_PREFERENCE_PERFORMANCE.toString())
 
+        assertTrue(tracker.avoidBadWifi)
+
         val listenerCaptor = ArgumentCaptor.forClass(
                 ActiveDataSubscriptionIdListener::class.java)
         verify(telephonyManager, times(1))
@@ -171,10 +190,6 @@
         val listener = listenerCaptor.value
         listener.onActiveDataSubscriptionIdChanged(testSubId)
 
-        // Check it get resource value with test sub id.
-        verify(subscriptionManager, times(1)).getActiveSubscriptionInfo(testSubId)
-        verify(context).createConfigurationContext(argThat { it.mcc == 310 && it.mnc == 210 })
-
         // Check if avoidBadWifi and meteredMultipathPreference values have been updated.
         assertFalse(tracker.avoidBadWifi)
         assertEquals(MULTIPATH_PREFERENCE_PERFORMANCE, tracker.meteredMultipathPreference)
diff --git a/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTestDependencies.kt b/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTestDependencies.kt
new file mode 100644
index 0000000..744c020
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTestDependencies.kt
@@ -0,0 +1,47 @@
+package com.android.server.connectivity
+
+import android.content.res.Resources
+import android.net.ConnectivityResources
+import android.provider.DeviceConfig
+import android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY
+import android.provider.DeviceConfig.OnPropertiesChangedListener
+import com.android.internal.annotations.GuardedBy
+import com.android.server.connectivity.MultinetworkPolicyTracker.CONFIG_ACTIVELY_PREFER_BAD_WIFI
+import java.util.concurrent.Executor
+
+class MultinetworkPolicyTrackerTestDependencies(private val resources: Resources) :
+        MultinetworkPolicyTracker.Dependencies() {
+    @GuardedBy("listeners")
+    private var configActivelyPreferBadWifi = 0
+    // TODO : move this to an actual fake device config object
+    @GuardedBy("listeners")
+    private val listeners = mutableListOf<Pair<Executor, OnPropertiesChangedListener>>()
+
+    fun putConfigActivelyPreferBadWifi(value: Int) {
+        synchronized(listeners) {
+            if (value == configActivelyPreferBadWifi) return
+            configActivelyPreferBadWifi = value
+            val p = DeviceConfig.Properties(NAMESPACE_CONNECTIVITY,
+                    mapOf(CONFIG_ACTIVELY_PREFER_BAD_WIFI to value.toString()))
+            listeners.forEach { (executor, listener) ->
+                executor.execute { listener.onPropertiesChanged(p) }
+            }
+        }
+    }
+
+    override fun getConfigActivelyPreferBadWifi(): Int {
+        return synchronized(listeners) { configActivelyPreferBadWifi }
+    }
+
+    override fun addOnDevicePropertiesChangedListener(
+        e: Executor,
+        listener: OnPropertiesChangedListener
+    ) {
+        synchronized(listeners) {
+            listeners.add(e to listener)
+        }
+    }
+
+    override fun getResourcesForActiveSubId(res: ConnectivityResources, id: Int): Resources =
+            resources
+}
diff --git a/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt b/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt
index ae8b438..1e3f389 100644
--- a/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt
@@ -17,20 +17,20 @@
 package com.android.server.connectivity
 
 import android.net.NetworkCapabilities
-import android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL
+import android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL as NET_CAP_PORTAL
 import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
 import android.net.NetworkCapabilities.TRANSPORT_WIFI
 import android.net.NetworkScore.KEEP_CONNECTED_NONE
-import android.net.NetworkScore.POLICY_EXITING
-import android.net.NetworkScore.POLICY_TRANSPORT_PRIMARY
-import android.net.NetworkScore.POLICY_YIELD_TO_BAD_WIFI
+import android.net.NetworkScore.POLICY_EXITING as EXITING
+import android.net.NetworkScore.POLICY_TRANSPORT_PRIMARY as PRIMARY
+import android.net.NetworkScore.POLICY_YIELD_TO_BAD_WIFI as YIELD_TO_BAD_WIFI
 import android.os.Build
 import androidx.test.filters.SmallTest
 import com.android.connectivity.resources.R
-import com.android.server.connectivity.FullScore.POLICY_AVOIDED_WHEN_UNVALIDATED
-import com.android.server.connectivity.FullScore.POLICY_EVER_EVALUATED
-import com.android.server.connectivity.FullScore.POLICY_EVER_VALIDATED
-import com.android.server.connectivity.FullScore.POLICY_IS_VALIDATED
+import com.android.server.connectivity.FullScore.POLICY_AVOIDED_WHEN_UNVALIDATED as AVOIDED_UNVALID
+import com.android.server.connectivity.FullScore.POLICY_EVER_EVALUATED as EVER_EVALUATED
+import com.android.server.connectivity.FullScore.POLICY_EVER_VALIDATED as EVER_VALIDATED
+import com.android.server.connectivity.FullScore.POLICY_IS_VALIDATED as IS_VALIDATED
 import com.android.testutils.DevSdkIgnoreRule
 import org.junit.Rule
 import org.junit.Test
@@ -65,221 +65,135 @@
         fun ranker() = listOf(true, false)
     }
 
+    // Helpers to shorten syntax
+    private fun rank(vararg scores: TestScore) =
+            mRanker.getBestNetworkByPolicy(scores.toList(), null /* currentSatisfier */)
+    val CAPS_CELL = caps(TRANSPORT_CELLULAR)
+    val CAPS_WIFI = caps(TRANSPORT_WIFI)
+    val CAPS_WIFI_PORTAL = caps(TRANSPORT_WIFI, NET_CAP_PORTAL)
+
     @Test
-    fun testYieldToBadWiFiOneCell() {
+    fun testYieldToBadWiFi_oneCell() {
         // Only cell, it wins
-        val winner = TestScore(
-                score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED, POLICY_EVER_EVALUATED),
-                caps(TRANSPORT_CELLULAR))
-        val scores = listOf(winner)
-        assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
+        val cell = TestScore(score(EVER_EVALUATED, YIELD_TO_BAD_WIFI, IS_VALIDATED), CAPS_CELL)
+        assertEquals(cell, rank(cell))
     }
 
     @Test
-    fun testPreferBadWifiOneCellOneEvaluatingWifi() {
-        // TODO : refactor the tests to name each network like this test
+    fun testPreferBadWifi_oneCellOneEvaluatingWifi() {
         val wifi = TestScore(score(), caps(TRANSPORT_WIFI))
-        val cell = TestScore(
-                score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED, POLICY_EVER_EVALUATED),
-                caps(TRANSPORT_CELLULAR))
-        assertEquals(cell, mRanker.getBestNetworkByPolicy(listOf(wifi, cell), null))
+        val cell = TestScore(score(YIELD_TO_BAD_WIFI, IS_VALIDATED, EVER_EVALUATED), CAPS_CELL)
+        assertEquals(cell, rank(wifi, cell))
     }
 
     @Test
-    fun testYieldToBadWiFiOneCellOneBadWiFi() {
+    fun testYieldToBadWiFi_oneCellOneBadWiFi() {
         // Bad wifi wins against yielding validated cell
-        val winner = TestScore(score(POLICY_EVER_VALIDATED, POLICY_EVER_EVALUATED),
-                caps(TRANSPORT_WIFI))
-        val scores = listOf(
-                winner,
-                TestScore(
-                        score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED, POLICY_EVER_EVALUATED),
-                        caps(TRANSPORT_CELLULAR))
-        )
-        assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
+        val badWifi = TestScore(score(EVER_EVALUATED, EVER_VALIDATED), CAPS_WIFI)
+        val cell = TestScore(score(EVER_EVALUATED, YIELD_TO_BAD_WIFI, IS_VALIDATED), CAPS_CELL)
+        assertEquals(badWifi, rank(badWifi, cell))
     }
 
     @Test
-    fun testPreferBadWifiOneCellOneBadWifi() {
-        val wifi = TestScore(score(POLICY_EVER_EVALUATED), caps(TRANSPORT_WIFI))
-        val cell = TestScore(
-                score(POLICY_EVER_EVALUATED, POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
-                caps(TRANSPORT_CELLULAR))
-        val scores = listOf(wifi, cell)
-        val winner = if (activelyPreferBadWifi) wifi else cell
-        assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
+    fun testPreferBadWifi_oneCellOneBadWifi() {
+        val badWifi = TestScore(score(EVER_EVALUATED), CAPS_WIFI)
+        val cell = TestScore(score(EVER_EVALUATED, YIELD_TO_BAD_WIFI, IS_VALIDATED), CAPS_CELL)
+        val winner = if (activelyPreferBadWifi) badWifi else cell
+        assertEquals(winner, rank(badWifi, cell))
     }
 
     @Test
-    fun testPreferBadWifiOneCellOneCaptivePortalWifi() {
-        val wifi = TestScore(score(POLICY_EVER_EVALUATED),
-                caps(TRANSPORT_WIFI, NET_CAPABILITY_CAPTIVE_PORTAL))
-        val cell = TestScore(
-                score(POLICY_EVER_EVALUATED, POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
-                caps(TRANSPORT_CELLULAR))
-        assertEquals(cell, mRanker.getBestNetworkByPolicy(listOf(wifi, cell), null))
+    fun testPreferBadWifi_oneCellOneCaptivePortalWifi() {
+        val portalWifi = TestScore(score(EVER_EVALUATED), CAPS_WIFI_PORTAL)
+        val cell = TestScore(score(EVER_EVALUATED, YIELD_TO_BAD_WIFI, IS_VALIDATED), CAPS_CELL)
+        assertEquals(cell, rank(portalWifi, cell))
     }
 
     @Test
-    fun testYieldToBadWifiOneCellOneCaptivePortalWifiThatClosed() {
-        val wifi = TestScore(score(POLICY_EVER_EVALUATED, POLICY_EVER_VALIDATED),
-                caps(TRANSPORT_WIFI, NET_CAPABILITY_CAPTIVE_PORTAL))
-        val cell = TestScore(
-                score(POLICY_EVER_EVALUATED, POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
-                caps(TRANSPORT_CELLULAR))
-        assertEquals(wifi, mRanker.getBestNetworkByPolicy(listOf(wifi, cell), null))
+    fun testYieldToBadWifi_oneCellOneCaptivePortalWifiThatClosed() {
+        val portalWifiClosed = TestScore(score(EVER_EVALUATED, EVER_VALIDATED), CAPS_WIFI_PORTAL)
+        val cell = TestScore(score(EVER_EVALUATED, YIELD_TO_BAD_WIFI, IS_VALIDATED), CAPS_CELL)
+        assertEquals(portalWifiClosed, rank(portalWifiClosed, cell))
     }
 
     @Test
-    fun testYieldToBadWifiAvoidUnvalidated() {
+    fun testYieldToBadWifi_avoidUnvalidated() {
         // Bad wifi avoided when unvalidated loses against yielding validated cell
-        val winner = TestScore(
-                score(POLICY_EVER_EVALUATED, POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
-                caps(TRANSPORT_CELLULAR))
-        val scores = listOf(
-                winner,
-                TestScore(
-                        score(POLICY_EVER_EVALUATED, POLICY_EVER_VALIDATED,
-                                POLICY_AVOIDED_WHEN_UNVALIDATED),
-                        caps(TRANSPORT_WIFI))
-        )
-        assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
+        val cell = TestScore(score(EVER_EVALUATED, YIELD_TO_BAD_WIFI, IS_VALIDATED), CAPS_CELL)
+        val avoidedWifi = TestScore(score(EVER_EVALUATED, EVER_VALIDATED, AVOIDED_UNVALID),
+                CAPS_WIFI)
+        assertEquals(cell, rank(cell, avoidedWifi))
     }
 
     @Test
-    fun testYieldToBadWiFiOneCellTwoBadWiFi() {
+    fun testYieldToBadWiFi_oneCellTwoBadWiFi() {
         // Bad wifi wins against yielding validated cell. Prefer the one that's primary.
-        val winner = TestScore(
-                score(POLICY_EVER_EVALUATED, POLICY_EVER_VALIDATED, POLICY_TRANSPORT_PRIMARY),
-                caps(TRANSPORT_WIFI))
-        val scores = listOf(
-                winner,
-                TestScore(
-                        score(POLICY_EVER_EVALUATED, POLICY_EVER_VALIDATED),
-                        caps(TRANSPORT_WIFI)),
-                TestScore(
-                        score(POLICY_EVER_EVALUATED, POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
-                        caps(TRANSPORT_CELLULAR))
-        )
-        assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
+        val primaryBadWifi = TestScore(score(EVER_EVALUATED, EVER_VALIDATED, PRIMARY), CAPS_WIFI)
+        val secondaryBadWifi = TestScore(score(EVER_EVALUATED, EVER_VALIDATED), CAPS_WIFI)
+        val cell = TestScore(score(EVER_EVALUATED, YIELD_TO_BAD_WIFI, IS_VALIDATED), CAPS_CELL)
+        assertEquals(primaryBadWifi, rank(primaryBadWifi, secondaryBadWifi, cell))
     }
 
     @Test
-    fun testYieldToBadWiFiOneCellTwoBadWiFiOneNotAvoided() {
+    fun testYieldToBadWiFi_oneCellTwoBadWiFiOneNotAvoided() {
         // Bad wifi ever validated wins against bad wifi that never was validated (or was
         // avoided when bad).
-        val winner = TestScore(score(POLICY_EVER_EVALUATED, POLICY_EVER_VALIDATED),
-                caps(TRANSPORT_WIFI))
-        val scores = listOf(
-                winner,
-                TestScore(score(POLICY_EVER_EVALUATED), caps(TRANSPORT_WIFI)),
-                TestScore(
-                        score(POLICY_EVER_EVALUATED, POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
-                        caps(TRANSPORT_CELLULAR))
-        )
-        assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
+        val badWifi = TestScore(score(EVER_EVALUATED, EVER_VALIDATED), CAPS_WIFI)
+        val neverValidatedWifi = TestScore(score(), CAPS_WIFI)
+        val cell = TestScore(score(EVER_EVALUATED, YIELD_TO_BAD_WIFI, IS_VALIDATED), CAPS_CELL)
+        assertEquals(badWifi, rank(badWifi, neverValidatedWifi, cell))
     }
 
     @Test
-    fun testYieldToBadWiFiOneCellOneBadWiFiOneGoodWiFi() {
+    fun testYieldToBadWiFi_oneCellOneBadWiFiOneGoodWiFi() {
         // Good wifi wins
-        val winner = TestScore(
-                score(POLICY_EVER_EVALUATED, POLICY_EVER_VALIDATED, POLICY_IS_VALIDATED),
-                caps(TRANSPORT_WIFI))
-        val scores = listOf(
-                winner,
-                TestScore(
-                        score(POLICY_EVER_EVALUATED, POLICY_EVER_VALIDATED,
-                                POLICY_TRANSPORT_PRIMARY),
-                        caps(TRANSPORT_WIFI)),
-                TestScore(
-                        score(POLICY_EVER_EVALUATED, POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
-                        caps(TRANSPORT_CELLULAR))
-        )
-        assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
+        val goodWifi = TestScore(score(EVER_EVALUATED, EVER_VALIDATED, IS_VALIDATED), CAPS_WIFI)
+        val badWifi = TestScore(score(EVER_EVALUATED, EVER_VALIDATED, PRIMARY), CAPS_WIFI)
+        val cell = TestScore(score(EVER_EVALUATED, YIELD_TO_BAD_WIFI, IS_VALIDATED), CAPS_CELL)
+        assertEquals(goodWifi, rank(goodWifi, badWifi, cell))
     }
 
     @Test
-    fun testPreferBadWifiOneCellOneBadWifiOneEvaluatingWifi() {
-        val cell = TestScore(
-                score(POLICY_EVER_EVALUATED, POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
-                caps(TRANSPORT_CELLULAR))
-        val badWifi = TestScore(score(POLICY_EVER_EVALUATED), caps(TRANSPORT_WIFI))
-        val evaluatingWifi = TestScore(score(), caps(TRANSPORT_WIFI))
+    fun testPreferBadWifi_oneCellOneBadWifiOneEvaluatingWifi() {
+        val cell = TestScore(score(EVER_EVALUATED, YIELD_TO_BAD_WIFI, IS_VALIDATED), CAPS_CELL)
+        val badWifi = TestScore(score(EVER_EVALUATED), CAPS_WIFI)
+        val evaluatingWifi = TestScore(score(), CAPS_WIFI)
         val winner = if (activelyPreferBadWifi) badWifi else cell
-        assertEquals(winner,
-                mRanker.getBestNetworkByPolicy(listOf(cell, badWifi, evaluatingWifi), null))
+        assertEquals(winner, rank(cell, badWifi, evaluatingWifi))
     }
 
     @Test
-    fun testYieldToBadWiFiTwoCellsOneBadWiFi() {
+    fun testYieldToBadWiFi_twoCellsOneBadWiFi() {
         // Cell that doesn't yield wins over cell that yields and bad wifi
-        val winner = TestScore(
-                score(POLICY_EVER_EVALUATED, POLICY_IS_VALIDATED),
-                caps(TRANSPORT_CELLULAR))
-        val scores = listOf(
-                winner,
-                TestScore(
-                        score(POLICY_EVER_EVALUATED, POLICY_EVER_VALIDATED,
-                                POLICY_TRANSPORT_PRIMARY),
-                        caps(TRANSPORT_WIFI)),
-                TestScore(
-                        score(POLICY_EVER_EVALUATED, POLICY_YIELD_TO_BAD_WIFI,
-                                POLICY_EVER_VALIDATED, POLICY_IS_VALIDATED),
-                        caps(TRANSPORT_CELLULAR))
-        )
-        assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
+        val cellNotYield = TestScore(score(EVER_EVALUATED, IS_VALIDATED), CAPS_CELL)
+        val badWifi = TestScore(score(EVER_EVALUATED, EVER_VALIDATED, PRIMARY), CAPS_WIFI)
+        val cellYield = TestScore(score(EVER_EVALUATED, YIELD_TO_BAD_WIFI, IS_VALIDATED), CAPS_CELL)
+        assertEquals(cellNotYield, rank(cellNotYield, badWifi, cellYield))
     }
 
     @Test
-    fun testYieldToBadWiFiTwoCellsOneBadWiFiOneGoodWiFi() {
+    fun testYieldToBadWiFi_twoCellsOneBadWiFiOneGoodWiFi() {
         // Good wifi wins over cell that doesn't yield and cell that yields
-        val winner = TestScore(
-                score(POLICY_EVER_EVALUATED, POLICY_IS_VALIDATED),
-                caps(TRANSPORT_WIFI))
-        val scores = listOf(
-                winner,
-                TestScore(
-                        score(POLICY_EVER_EVALUATED, POLICY_EVER_VALIDATED,
-                                POLICY_TRANSPORT_PRIMARY),
-                        caps(TRANSPORT_WIFI)),
-                TestScore(
-                        score(POLICY_EVER_EVALUATED, POLICY_IS_VALIDATED),
-                        caps(TRANSPORT_CELLULAR)),
-                TestScore(
-                        score(POLICY_EVER_EVALUATED, POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
-                        caps(TRANSPORT_CELLULAR))
-        )
-        assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
+        val goodWifi = TestScore(score(EVER_EVALUATED, IS_VALIDATED), CAPS_WIFI)
+        val badWifi = TestScore(score(EVER_EVALUATED, EVER_VALIDATED, PRIMARY), CAPS_WIFI)
+        val cellNotYield = TestScore(score(EVER_EVALUATED, IS_VALIDATED), CAPS_CELL)
+        val cellYield = TestScore(score(EVER_EVALUATED, YIELD_TO_BAD_WIFI, IS_VALIDATED), CAPS_CELL)
+        assertEquals(goodWifi, rank(goodWifi, badWifi, cellNotYield, cellYield))
     }
 
     @Test
-    fun testYieldToBadWiFiOneExitingGoodWiFi() {
+    fun testYieldToBadWiFi_oneExitingGoodWiFi() {
         // Yielding cell wins over good exiting wifi
-        val winner = TestScore(
-                score(POLICY_EVER_EVALUATED, POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
-                caps(TRANSPORT_CELLULAR))
-        val scores = listOf(
-                winner,
-                TestScore(
-                        score(POLICY_EVER_EVALUATED, POLICY_IS_VALIDATED, POLICY_EXITING),
-                        caps(TRANSPORT_WIFI))
-        )
-        assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
+        val cell = TestScore(score(EVER_EVALUATED, YIELD_TO_BAD_WIFI, IS_VALIDATED), CAPS_CELL)
+        val exitingWifi = TestScore(score(EVER_EVALUATED, IS_VALIDATED, EXITING), CAPS_WIFI)
+        assertEquals(cell, rank(cell, exitingWifi))
     }
 
     @Test
-    fun testYieldToBadWiFiOneExitingBadWiFi() {
+    fun testYieldToBadWiFi_oneExitingBadWiFi() {
         // Yielding cell wins over bad exiting wifi
-        val winner = TestScore(
-                score(POLICY_EVER_EVALUATED, POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
-                caps(TRANSPORT_CELLULAR))
-        val scores = listOf(
-                winner,
-                TestScore(
-                        score(POLICY_EVER_EVALUATED, POLICY_EVER_VALIDATED, POLICY_EXITING),
-                        caps(TRANSPORT_WIFI))
-        )
-        assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
+        val cell = TestScore(score(EVER_EVALUATED, YIELD_TO_BAD_WIFI, IS_VALIDATED), CAPS_CELL)
+        val badExitingWifi = TestScore(score(EVER_EVALUATED, EVER_VALIDATED, EXITING), CAPS_WIFI)
+        assertEquals(cell, rank(cell, badExitingWifi))
     }
 }
diff --git a/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java b/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
index aad80d5..949e0c2 100644
--- a/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
+++ b/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
@@ -25,7 +25,6 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.same;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.never;
@@ -306,35 +305,6 @@
     }
 
     @Test
-    public void testUpdateInterfaceLinkStateForProvisionedInterface() throws Exception {
-        initEthernetNetworkFactory();
-        createAndVerifyProvisionedInterface(TEST_IFACE);
-
-        final boolean retDown = mNetFactory.updateInterfaceLinkState(TEST_IFACE, false /* up */);
-
-        assertTrue(retDown);
-        verifyStop();
-
-        final boolean retUp = mNetFactory.updateInterfaceLinkState(TEST_IFACE, true /* up */);
-
-        assertTrue(retUp);
-    }
-
-    @Test
-    public void testUpdateInterfaceLinkStateForUnprovisionedInterface() throws Exception {
-        initEthernetNetworkFactory();
-        createUnprovisionedInterface(TEST_IFACE);
-
-        final boolean ret = mNetFactory.updateInterfaceLinkState(TEST_IFACE, false /* up */);
-
-        assertTrue(ret);
-        // There should not be an active IPClient or NetworkAgent.
-        verify(mDeps, never()).makeIpClient(any(), any(), any());
-        verify(mDeps, never())
-                .makeEthernetNetworkAgent(any(), any(), any(), any(), any(), any(), any());
-    }
-
-    @Test
     public void testUpdateInterfaceLinkStateForNonExistingInterface() throws Exception {
         initEthernetNetworkFactory();
 
@@ -346,17 +316,6 @@
     }
 
     @Test
-    public void testUpdateInterfaceLinkStateWithNoChanges() throws Exception {
-        initEthernetNetworkFactory();
-        createAndVerifyProvisionedInterface(TEST_IFACE);
-
-        final boolean ret = mNetFactory.updateInterfaceLinkState(TEST_IFACE, true /* up */);
-
-        assertFalse(ret);
-        verifyNoStopOrStart();
-    }
-
-    @Test
     public void testProvisioningLoss() throws Exception {
         initEthernetNetworkFactory();
         when(mDeps.getNetworkInterfaceByName(TEST_IFACE)).thenReturn(mInterfaceParams);
@@ -390,17 +349,6 @@
     }
 
     @Test
-    public void testLinkPropertiesChanged() throws Exception {
-        initEthernetNetworkFactory();
-        createAndVerifyProvisionedInterface(TEST_IFACE);
-
-        LinkProperties lp = new LinkProperties();
-        mIpClientCallbacks.onLinkPropertiesChange(lp);
-        mLooper.dispatchAll();
-        verify(mNetworkAgent).sendLinkPropertiesImpl(same(lp));
-    }
-
-    @Test
     public void testNetworkUnwanted() throws Exception {
         initEthernetNetworkFactory();
         createAndVerifyProvisionedInterface(TEST_IFACE);