Merge "BpfCoordinator: ignore stopping event monitoring if never started"
diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt
index f1298ce..8b35197 100644
--- a/framework/api/system-current.txt
+++ b/framework/api/system-current.txt
@@ -433,6 +433,7 @@
   public abstract class QosFilter {
     method @NonNull public abstract android.net.Network getNetwork();
     method public abstract boolean matchesLocalAddress(@NonNull java.net.InetAddress, int, int);
+    method public boolean matchesProtocol(int);
     method public abstract boolean matchesRemoteAddress(@NonNull java.net.InetAddress, int, int);
   }
 
@@ -453,6 +454,7 @@
 
   public final class QosSocketInfo implements android.os.Parcelable {
     ctor public QosSocketInfo(@NonNull android.net.Network, @NonNull java.net.Socket) throws java.io.IOException;
+    ctor public QosSocketInfo(@NonNull android.net.Network, @NonNull java.net.DatagramSocket) throws java.io.IOException;
     method public int describeContents();
     method @NonNull public java.net.InetSocketAddress getLocalSocketAddress();
     method @NonNull public android.net.Network getNetwork();
@@ -480,6 +482,14 @@
     ctor public SocketNotBoundException();
   }
 
+  public class SocketNotConnectedException extends java.lang.Exception {
+    ctor public SocketNotConnectedException();
+  }
+
+  public class SocketRemoteAddressChangedException extends java.lang.Exception {
+    ctor public SocketRemoteAddressChangedException();
+  }
+
   public final class StaticIpConfiguration implements android.os.Parcelable {
     ctor public StaticIpConfiguration();
     ctor public StaticIpConfiguration(@Nullable android.net.StaticIpConfiguration);
diff --git a/framework/src/android/net/QosFilter.java b/framework/src/android/net/QosFilter.java
index 01dc4bb..a731b23 100644
--- a/framework/src/android/net/QosFilter.java
+++ b/framework/src/android/net/QosFilter.java
@@ -97,10 +97,15 @@
      * Determines whether or not the parameter will be matched with this filter.
      *
      * @param protocol the protocol such as TCP or UDP included in IP packet filter set of a QoS
-     *                 flow assigned on {@link Network}.
+     *                 flow assigned on {@link Network}. Only {@code IPPROTO_TCP} and {@code
+     *                 IPPROTO_UDP} currently supported.
      * @return whether the parameters match the socket type of the filter
-     * @hide
      */
-    public abstract boolean matchesProtocol(int protocol);
+    // Since this method is added in U, it's required to be default method for binary compatibility
+    // with existing @SystemApi.
+    // IPPROTO_* are not compile-time constants, so they are not annotated with @IntDef.
+    public boolean matchesProtocol(int protocol) {
+        return false;
+    }
 }
 
diff --git a/framework/src/android/net/QosSocketInfo.java b/framework/src/android/net/QosSocketInfo.java
index da9b356..1c3db23 100644
--- a/framework/src/android/net/QosSocketInfo.java
+++ b/framework/src/android/net/QosSocketInfo.java
@@ -144,7 +144,6 @@
      *
      * @param network the network
      * @param socket the bound {@link DatagramSocket}
-     * @hide
      */
     public QosSocketInfo(@NonNull final Network network, @NonNull final DatagramSocket socket)
             throws IOException {
diff --git a/framework/src/android/net/SocketNotConnectedException.java b/framework/src/android/net/SocketNotConnectedException.java
index fa2a615..a6357c7 100644
--- a/framework/src/android/net/SocketNotConnectedException.java
+++ b/framework/src/android/net/SocketNotConnectedException.java
@@ -16,13 +16,18 @@
 
 package android.net;
 
+import android.annotation.SystemApi;
+
+import com.android.internal.annotations.VisibleForTesting;
+
 /**
  * Thrown when a previously bound socket becomes unbound.
  *
  * @hide
  */
+@SystemApi
 public class SocketNotConnectedException extends Exception {
-    /** @hide */
+    @VisibleForTesting
     public SocketNotConnectedException() {
         super("The socket is not connected");
     }
diff --git a/framework/src/android/net/SocketRemoteAddressChangedException.java b/framework/src/android/net/SocketRemoteAddressChangedException.java
index ecaeebc..e13d5ed 100644
--- a/framework/src/android/net/SocketRemoteAddressChangedException.java
+++ b/framework/src/android/net/SocketRemoteAddressChangedException.java
@@ -16,13 +16,18 @@
 
 package android.net;
 
+import android.annotation.SystemApi;
+
+import com.android.internal.annotations.VisibleForTesting;
+
 /**
  * Thrown when the local address of the socket has changed.
  *
  * @hide
  */
+@SystemApi
 public class SocketRemoteAddressChangedException extends Exception {
-    /** @hide */
+    @VisibleForTesting
     public SocketRemoteAddressChangedException() {
         super("The remote address of the socket changed");
     }
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/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 1236243..84cf561 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -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 f1cb9f1..58196f7 100644
--- a/service/src/com/android/server/connectivity/MultinetworkPolicyTracker.java
+++ b/service/src/com/android/server/connectivity/MultinetworkPolicyTracker.java
@@ -32,14 +32,17 @@
 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;
 import android.telephony.TelephonyManager;
 import android.util.Log;
 
+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;
@@ -66,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;
@@ -81,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.
@@ -128,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));
@@ -165,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();
     }
@@ -200,10 +248,8 @@
         // NETWORK_AVOID_BAD_WIFI setting.
         if (allowBadWifi) return true;
 
-        // TODO: use R.integer.config_networkAvoidBadWifi directly
-        final int id = mResources.get().getIdentifier("config_networkAvoidBadWifi",
-                "integer", mResources.getResourcesContext().getPackageName());
-        return (getResourcesForActiveSubId().getInteger(id) == 0);
+        return mDeps.getResourcesForActiveSubId(mResources, mActiveSubId)
+                .getInteger(R.integer.config_networkAvoidBadWifi) == 0;
     }
 
     /**
@@ -219,14 +265,13 @@
         // See the definition of config_activelyPreferBadWifi for a description of its meaning.
         // On U and above, the config is ignored, and bad wifi is always actively preferred.
         if (SdkLevel.isAtLeastU()) return true;
-        // TODO: use R.integer.config_activelyPreferBadWifi directly
-        final int id = mResources.get().getIdentifier("config_activelyPreferBadWifi",
-                "integer", mResources.getResourcesContext().getPackageName());
+
         // On T and below, 1 means to actively prefer bad wifi, 0 means not to prefer
         // 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().getInteger(id);
+        return 0 != mDeps.getResourcesForActiveSubId(mResources, mActiveSubId)
+                .getInteger(R.integer.config_activelyPreferBadWifi);
     }
 
     /**
@@ -239,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.
      */
@@ -257,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);
@@ -278,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;
     }
@@ -287,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 b959c73..74e57c9 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_RESTRICTED
 import android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED
 import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
 import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
@@ -56,6 +57,7 @@
 import android.os.Looper
 import android.os.OutcomeReceiver
 import android.os.SystemProperties
+import android.os.Process
 import android.platform.test.annotations.AppModeFull
 import android.util.ArraySet
 import androidx.test.platform.app.InstrumentationRegistry
@@ -72,6 +74,7 @@
 import com.android.testutils.TapPacketReader
 import com.android.testutils.TestableNetworkCallback
 import com.android.testutils.anyNetwork
+import com.android.testutils.assertThrows
 import com.android.testutils.runAsShell
 import com.android.testutils.waitForIdle
 import org.junit.After
@@ -80,8 +83,10 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import java.io.IOException
 import java.net.Inet6Address
 import java.util.Random
+import java.net.Socket
 import java.util.concurrent.CompletableFuture
 import java.util.concurrent.ExecutionException
 import java.util.concurrent.TimeUnit
@@ -502,7 +507,7 @@
 
     // NetworkRequest.Builder does not create a copy of the passed NetworkRequest, so in order to
     // keep ETH_REQUEST as it is, a defensive copy is created here.
-    private fun NetworkRequest.createCopyWithEthernetSpecifier(ifaceName: String) =
+    private fun NetworkRequest.copyWithEthernetSpecifier(ifaceName: String) =
         NetworkRequest.Builder(NetworkRequest(ETH_REQUEST))
             .setNetworkSpecifier(EthernetNetworkSpecifier(ifaceName)).build()
 
@@ -716,7 +721,7 @@
         val iface1 = createInterface()
         val iface2 = createInterface()
 
-        val cb = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface2.name))
+        val cb = requestNetwork(ETH_REQUEST.copyWithEthernetSpecifier(iface2.name))
 
         val network = cb.expectAvailable()
         cb.expectCapabilitiesWithInterfaceName(iface2.name)
@@ -749,8 +754,8 @@
         val iface1 = createInterface()
         val iface2 = createInterface()
 
-        val cb1 = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface1.name))
-        val cb2 = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface2.name))
+        val cb1 = requestNetwork(ETH_REQUEST.copyWithEthernetSpecifier(iface1.name))
+        val cb2 = requestNetwork(ETH_REQUEST.copyWithEthernetSpecifier(iface2.name))
         val cb3 = requestNetwork(ETH_REQUEST)
 
         cb1.expectAvailable()
@@ -861,7 +866,7 @@
     @Test
     fun testUpdateConfiguration_forBothIpConfigAndCapabilities() {
         val iface = createInterface()
-        val cb = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface.name))
+        val cb = requestNetwork(ETH_REQUEST.copyWithEthernetSpecifier(iface.name))
         val network = cb.expectAvailable()
         cb.assertNeverLost()
 
@@ -884,7 +889,7 @@
     @Test
     fun testUpdateConfiguration_forOnlyIpConfig() {
         val iface = createInterface()
-        val cb = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface.name))
+        val cb = requestNetwork(ETH_REQUEST.copyWithEthernetSpecifier(iface.name))
         val network = cb.expectAvailable()
         cb.assertNeverLost()
 
@@ -902,7 +907,7 @@
     @Test
     fun testUpdateConfiguration_forOnlyCapabilities() {
         val iface = createInterface()
-        val cb = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface.name))
+        val cb = requestNetwork(ETH_REQUEST.copyWithEthernetSpecifier(iface.name))
         val network = cb.expectAvailable()
         cb.assertNeverLost()
 
@@ -919,4 +924,37 @@
         cb.expectAvailable()
         cb.expectCapabilitiesWith(testCapability)
     }
+
+    @Test
+    fun testUpdateConfiguration_forAllowedUids() {
+        // Configure a restricted network.
+        val iface = createInterface()
+        val request = NetworkRequest.Builder(ETH_REQUEST.copyWithEthernetSpecifier(iface.name))
+                .removeCapability(NET_CAPABILITY_NOT_RESTRICTED).build()
+        updateConfiguration(iface, capabilities = request.networkCapabilities)
+                .expectResult(iface.name)
+
+        // Request the restricted network as the shell with CONNECTIVITY_USE_RESTRICTED_NETWORKS.
+        val cb = runAsShell(CONNECTIVITY_USE_RESTRICTED_NETWORKS) { requestNetwork(request) }
+        val network = cb.expectAvailable()
+        cb.assertNeverLost(network)
+
+        // The network is restricted therefore binding to it when available will fail.
+        Socket().use { socket ->
+            assertThrows(IOException::class.java, { network.bindSocket(socket) })
+        }
+
+        // Add the test process UID to the allowed UIDs for the network and ultimately bind again.
+        val allowedUids = setOf(Process.myUid())
+        val nc = NetworkCapabilities.Builder(request.networkCapabilities)
+                .setAllowedUids(allowedUids).build()
+        updateConfiguration(iface, capabilities = nc).expectResult(iface.name)
+
+        // UpdateConfiguration() currently does a restart on the ethernet interface therefore lost
+        // will be expected first before available, as part of the restart.
+        cb.expectLost(network)
+        val updatedNetwork = cb.expectAvailable()
+        // With the test process UID allowed, binding to a restricted network should be successful.
+        Socket().use { socket -> updatedNetwork.bindSocket(socket) }
+    }
 }
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index d4f3d57..d2cd7aa 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -73,6 +73,8 @@
 import android.os.Message
 import android.os.SystemClock
 import android.platform.test.annotations.AppModeFull
+import android.system.OsConstants.IPPROTO_TCP
+import android.system.OsConstants.IPPROTO_UDP
 import android.telephony.TelephonyManager
 import android.telephony.data.EpsBearerQosSessionAttributes
 import android.util.DebugUtils.valueToString
@@ -117,6 +119,7 @@
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.timeout
 import org.mockito.Mockito.verify
+import java.io.Closeable
 import java.io.IOException
 import java.net.DatagramSocket
 import java.net.InetAddress
@@ -174,7 +177,7 @@
     private val mFakeConnectivityService = FakeConnectivityService()
     private val agentsToCleanUp = mutableListOf<NetworkAgent>()
     private val callbacksToCleanUp = mutableListOf<TestableNetworkCallback>()
-    private var qosTestSocket: Socket? = null
+    private var qosTestSocket: Closeable? = null // either Socket or DatagramSocket
 
     @Before
     fun setUp() {
@@ -930,34 +933,49 @@
         }
     }
 
-    private fun setupForQosCallbackTesting(): Pair<TestableNetworkAgent, Socket> {
-        val request = NetworkRequest.Builder()
-                .clearCapabilities()
-                .addTransportType(TRANSPORT_TEST)
-                .build()
+    private fun <T : Closeable> setupForQosCallbackTest(creator: (TestableNetworkAgent) -> T) =
+            createConnectedNetworkAgent().first.let { Pair(it, creator(it)) }
 
-        val callback = TestableNetworkCallback(timeoutMs = DEFAULT_TIMEOUT_MS)
-        requestNetwork(request, callback)
-        val (agent, _) = createConnectedNetworkAgent()
+    private fun setupForQosSocket() = setupForQosCallbackTest {
+        agent: TestableNetworkAgent -> Socket()
+            .also { assertNotNull(agent.network?.bindSocket(it))
+                it.bind(InetSocketAddress(InetAddress.getLoopbackAddress(), 0)) }
+    }
 
-        qosTestSocket = assertNotNull(agent.network?.socketFactory?.createSocket()).also {
-            it.bind(InetSocketAddress(InetAddress.getLoopbackAddress(), 0))
-        }
-        return Pair(agent, qosTestSocket!!)
+    private fun setupForQosDatagram() = setupForQosCallbackTest {
+        agent: TestableNetworkAgent -> DatagramSocket(
+            InetSocketAddress(InetAddress.getLoopbackAddress(), 0))
+            .also { assertNotNull(agent.network?.bindSocket(it)) }
     }
 
     @AppModeFull(reason = "Instant apps don't have permission to bind sockets.")
     @Test
-    fun testQosCallbackRegisterWithUnregister() {
-        val (agent, socket) = setupForQosCallbackTesting()
+    fun testQosCallbackRegisterAndUnregister() {
+        validateQosCallbackRegisterAndUnregister(IPPROTO_TCP)
+    }
 
+    @AppModeFull(reason = "Instant apps don't have permission to bind sockets.")
+    @Test
+    fun testQosCallbackRegisterAndUnregisterWithDatagramSocket() {
+        validateQosCallbackRegisterAndUnregister(IPPROTO_UDP)
+    }
+
+    private fun validateQosCallbackRegisterAndUnregister(proto: Int) {
+        val (agent, qosTestSocket) = when (proto) {
+            IPPROTO_TCP -> setupForQosSocket()
+            IPPROTO_UDP -> setupForQosDatagram()
+            else -> fail("unsupported protocol")
+        }
         val qosCallback = TestableQosCallback()
         var callbackId = -1
         Executors.newSingleThreadExecutor().let { executor ->
             try {
-                val info = QosSocketInfo(agent.network!!, socket)
+                val info = QosSocketInfo(agent, qosTestSocket)
                 mCM.registerQosCallback(info, executor, qosCallback)
-                callbackId = agent.expectCallback<OnRegisterQosCallback>().callbackId
+                agent.expectCallback<OnRegisterQosCallback>().let {
+                    callbackId = it.callbackId
+                    assertTrue(it.filter.matchesProtocol(proto))
+                }
 
                 assertFailsWith<QosCallbackRegistrationException>(
                         "The same callback cannot be " +
@@ -965,7 +983,7 @@
                     mCM.registerQosCallback(info, executor, qosCallback)
                 }
             } finally {
-                socket.close()
+                qosTestSocket.close()
                 mCM.unregisterQosCallback(qosCallback)
                 agent.expectCallback<OnUnregisterQosCallback> { it.callbackId == callbackId }
                 executor.shutdown()
@@ -976,11 +994,31 @@
     @AppModeFull(reason = "Instant apps don't have permission to bind sockets.")
     @Test
     fun testQosCallbackOnQosSession() {
-        val (agent, socket) = setupForQosCallbackTesting()
+        validateQosCallbackOnQosSession(IPPROTO_TCP)
+    }
+
+    @AppModeFull(reason = "Instant apps don't have permission to bind sockets.")
+    @Test
+    fun testQosCallbackOnQosSessionWithDatagramSocket() {
+        validateQosCallbackOnQosSession(IPPROTO_UDP)
+    }
+
+    fun QosSocketInfo(agent: NetworkAgent, socket: Closeable) = when (socket) {
+        is Socket -> QosSocketInfo(agent.network, socket)
+        is DatagramSocket -> QosSocketInfo(agent.network, socket)
+        else -> fail("unexpected socket type")
+    }
+
+    private fun validateQosCallbackOnQosSession(proto: Int) {
+        val (agent, qosTestSocket) = when (proto) {
+            IPPROTO_TCP -> setupForQosSocket()
+            IPPROTO_UDP -> setupForQosDatagram()
+            else -> fail("unsupported protocol")
+        }
         val qosCallback = TestableQosCallback()
         Executors.newSingleThreadExecutor().let { executor ->
             try {
-                val info = QosSocketInfo(agent.network!!, socket)
+                val info = QosSocketInfo(agent, qosTestSocket)
                 assertEquals(agent.network, info.getNetwork())
                 mCM.registerQosCallback(info, executor, qosCallback)
                 val callbackId = agent.expectCallback<OnRegisterQosCallback>().callbackId
@@ -1009,8 +1047,7 @@
                 agent.sendQosSessionLost(callbackId, sessId, QosSession.TYPE_EPS_BEARER)
                 qosCallback.assertNoCallback()
             } finally {
-                socket.close()
-
+                qosTestSocket.close()
                 // safety precaution
                 mCM.unregisterQosCallback(qosCallback)
 
@@ -1022,11 +1059,11 @@
     @AppModeFull(reason = "Instant apps don't have permission to bind sockets.")
     @Test
     fun testQosCallbackOnError() {
-        val (agent, socket) = setupForQosCallbackTesting()
+        val (agent, qosTestSocket) = setupForQosSocket()
         val qosCallback = TestableQosCallback()
         Executors.newSingleThreadExecutor().let { executor ->
             try {
-                val info = QosSocketInfo(agent.network!!, socket)
+                val info = QosSocketInfo(agent.network!!, qosTestSocket)
                 mCM.registerQosCallback(info, executor, qosCallback)
                 val callbackId = agent.expectCallback<OnRegisterQosCallback>().callbackId
 
@@ -1048,7 +1085,7 @@
                 agent.sendQosSessionLost(callbackId, sessId, QosSession.TYPE_EPS_BEARER)
                 qosCallback.assertNoCallback()
             } finally {
-                socket.close()
+                qosTestSocket.close()
 
                 // Make sure that the callback is fully unregistered
                 mCM.unregisterQosCallback(qosCallback)
@@ -1061,12 +1098,12 @@
     @AppModeFull(reason = "Instant apps don't have permission to bind sockets.")
     @Test
     fun testQosCallbackIdsAreMappedCorrectly() {
-        val (agent, socket) = setupForQosCallbackTesting()
+        val (agent, qosTestSocket) = setupForQosSocket()
         val qosCallback1 = TestableQosCallback()
         val qosCallback2 = TestableQosCallback()
         Executors.newSingleThreadExecutor().let { executor ->
             try {
-                val info = QosSocketInfo(agent.network!!, socket)
+                val info = QosSocketInfo(agent.network!!, qosTestSocket)
                 mCM.registerQosCallback(info, executor, qosCallback1)
                 val callbackId1 = agent.expectCallback<OnRegisterQosCallback>().callbackId
 
@@ -1088,7 +1125,7 @@
                 qosCallback1.assertNoCallback()
                 qosCallback2.expectCallback<OnQosSessionAvailable> { sessId2 == it.sess.sessionId }
             } finally {
-                socket.close()
+                qosTestSocket.close()
 
                 // Make sure that the callback is fully unregistered
                 mCM.unregisterQosCallback(qosCallback1)
@@ -1102,13 +1139,13 @@
     @AppModeFull(reason = "Instant apps don't have permission to bind sockets.")
     @Test
     fun testQosCallbackWhenNetworkReleased() {
-        val (agent, socket) = setupForQosCallbackTesting()
+        val (agent, qosTestSocket) = setupForQosSocket()
         Executors.newSingleThreadExecutor().let { executor ->
             try {
                 val qosCallback1 = TestableQosCallback()
                 val qosCallback2 = TestableQosCallback()
                 try {
-                    val info = QosSocketInfo(agent.network!!, socket)
+                    val info = QosSocketInfo(agent.network!!, qosTestSocket)
                     mCM.registerQosCallback(info, executor, qosCallback1)
                     mCM.registerQosCallback(info, executor, qosCallback2)
                     agent.unregister()
@@ -1121,12 +1158,12 @@
                         it.ex.cause is NetworkReleasedException
                     }
                 } finally {
-                    socket.close()
+                    qosTestSocket.close()
                     mCM.unregisterQosCallback(qosCallback1)
                     mCM.unregisterQosCallback(qosCallback2)
                 }
             } finally {
-                socket.close()
+                qosTestSocket.close()
                 executor.shutdown()
             }
         }
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 cd43a34..ffb34e6 100644
--- a/tests/cts/net/src/android/net/cts/QosCallbackExceptionTest.java
+++ b/tests/cts/net/src/android/net/cts/QosCallbackExceptionTest.java
@@ -17,6 +17,7 @@
 package android.net.cts;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -24,6 +25,8 @@
 import android.net.QosCallbackException;
 import android.net.SocketLocalAddressChangedException;
 import android.net.SocketNotBoundException;
+import android.net.SocketNotConnectedException;
+import android.net.SocketRemoteAddressChangedException;
 import android.os.Build;
 
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
@@ -36,12 +39,6 @@
 @IgnoreUpTo(Build.VERSION_CODES.R)
 public class QosCallbackExceptionTest {
     private static final String ERROR_MESSAGE = "Test Error Message";
-    private static final String ERROR_MSG_SOCK_NOT_BOUND = "The socket is unbound";
-    private static final String ERROR_MSG_NET_RELEASED =
-            "The network was released and is no longer available";
-    private static final String ERROR_MSG_SOCK_ADDR_CHANGED =
-            "The local address of the socket changed";
-
 
     @Test
     public void testQosCallbackException() throws Exception {
@@ -57,33 +54,65 @@
     public void testNetworkReleasedExceptions() throws Exception {
         final Throwable netReleasedException = new NetworkReleasedException();
         final QosCallbackException exception = new QosCallbackException(netReleasedException);
-
-        assertTrue(exception.getCause() instanceof NetworkReleasedException);
-        assertEquals(netReleasedException, exception.getCause());
-        assertTrue(exception.getMessage().contains(ERROR_MSG_NET_RELEASED));
-        assertThrowableMessageContains(exception, ERROR_MSG_NET_RELEASED);
+        validateQosCallbackException(
+                exception, netReleasedException, NetworkReleasedException.class);
     }
 
     @Test
     public void testSocketNotBoundExceptions() throws Exception {
         final Throwable sockNotBoundException = new SocketNotBoundException();
         final QosCallbackException exception = new QosCallbackException(sockNotBoundException);
-
-        assertTrue(exception.getCause() instanceof SocketNotBoundException);
-        assertEquals(sockNotBoundException, exception.getCause());
-        assertTrue(exception.getMessage().contains(ERROR_MSG_SOCK_NOT_BOUND));
-        assertThrowableMessageContains(exception, ERROR_MSG_SOCK_NOT_BOUND);
+        validateQosCallbackException(
+                exception, sockNotBoundException, SocketNotBoundException.class);
     }
 
     @Test
     public void testSocketLocalAddressChangedExceptions() throws  Exception {
-        final Throwable localAddrChangedException = new SocketLocalAddressChangedException();
-        final QosCallbackException exception = new QosCallbackException(localAddrChangedException);
+        final Throwable localAddressChangedException = new SocketLocalAddressChangedException();
+        final QosCallbackException exception =
+                new QosCallbackException(localAddressChangedException);
+        validateQosCallbackException(
+                exception, localAddressChangedException, SocketLocalAddressChangedException.class);
+    }
 
-        assertTrue(exception.getCause() instanceof SocketLocalAddressChangedException);
-        assertEquals(localAddrChangedException, exception.getCause());
-        assertTrue(exception.getMessage().contains(ERROR_MSG_SOCK_ADDR_CHANGED));
-        assertThrowableMessageContains(exception, ERROR_MSG_SOCK_ADDR_CHANGED);
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S)
+    public void testSocketNotConnectedExceptions() throws Exception {
+        final Throwable sockNotConnectedException = new SocketNotConnectedException();
+        final QosCallbackException exception = new QosCallbackException(sockNotConnectedException);
+        validateQosCallbackException(
+                exception, sockNotConnectedException, SocketNotConnectedException.class);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S)
+    public void testSocketRemoteAddressChangedExceptions() throws  Exception {
+        final Throwable remoteAddressChangedException = new SocketRemoteAddressChangedException();
+        final QosCallbackException exception =
+                new QosCallbackException(remoteAddressChangedException);
+        validateQosCallbackException(
+                exception, remoteAddressChangedException,
+                SocketRemoteAddressChangedException.class);
+    }
+
+    private void validateQosCallbackException(
+            QosCallbackException e, Throwable cause, Class c) throws Exception {
+        if (c == SocketNotConnectedException.class) {
+            assertTrue(e.getCause() instanceof SocketNotConnectedException);
+        } else if (c == SocketRemoteAddressChangedException.class) {
+            assertTrue(e.getCause() instanceof SocketRemoteAddressChangedException);
+        } else if (c == SocketLocalAddressChangedException.class) {
+            assertTrue(e.getCause() instanceof SocketLocalAddressChangedException);
+        } else if (c == SocketNotBoundException.class) {
+            assertTrue(e.getCause() instanceof SocketNotBoundException);
+        } else if (c == NetworkReleasedException.class) {
+            assertTrue(e.getCause() instanceof NetworkReleasedException);
+        } else {
+            fail("unexpected error msg.");
+        }
+        assertEquals(cause, e.getCause());
+        assertFalse(e.getMessage().isEmpty());
+        assertThrowableMessageContains(e, e.getMessage());
     }
 
     private void assertThrowableMessageContains(QosCallbackException exception, String errorMsg)
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/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 31b0908..7993a5c 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;
@@ -1634,12 +1634,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
@@ -1838,10 +1833,6 @@
                 .getIdentifier(eq("config_networkSupportedKeepaliveCount"), eq("array"), any());
         doReturn(R.array.network_switch_type_name).when(mResources)
                 .getIdentifier(eq("network_switch_type_name"), eq("array"), any());
-        doReturn(R.integer.config_networkAvoidBadWifi).when(mResources)
-                .getIdentifier(eq("config_networkAvoidBadWifi"), eq("integer"), any());
-        doReturn(R.integer.config_activelyPreferBadWifi).when(mResources)
-                .getIdentifier(eq("config_activelyPreferBadWifi"), eq("integer"), any());
         doReturn(1).when(mResources).getInteger(R.integer.config_networkAvoidBadWifi);
         doReturn(0).when(mResources).getInteger(R.integer.config_activelyPreferBadWifi);
         doReturn(true).when(mResources)
@@ -5814,16 +5805,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 c4ce041..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,8 +48,6 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.ArgumentMatchers.argThat
-import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mockito.any
 import org.mockito.Mockito.doCallRealMethod
 import org.mockito.Mockito.doReturn
@@ -56,6 +55,8 @@
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 
+const val HANDLER_TIMEOUT_MS = 400
+
 /**
  * Tests for [MultinetworkPolicyTracker].
  *
@@ -67,10 +68,6 @@
 @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 class MultinetworkPolicyTrackerTest {
     private val resources = mock(Resources::class.java).also {
-        doReturn(R.integer.config_networkAvoidBadWifi).`when`(it).getIdentifier(
-                eq("config_networkAvoidBadWifi"), eq("integer"), any())
-        doReturn(R.integer.config_activelyPreferBadWifi).`when`(it).getIdentifier(
-                eq("config_activelyPreferBadWifi"), eq("integer"), any())
         doReturn(0).`when`(it).getInteger(R.integer.config_networkAvoidBadWifi)
         doReturn(0).`when`(it).getInteger(R.integer.config_activelyPreferBadWifi)
     }
@@ -96,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,
@@ -151,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
@@ -169,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))
@@ -176,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))
     }
 }