Prioritize non-slices over slices

For general internet access, a specialized slice is generally
not preferable to a non-specialized network.

Test: new test in this patch
Change-Id: I052ce923300566807999b2f20f5911181fb761dd
diff --git a/service/src/com/android/server/connectivity/NetworkRanker.java b/service/src/com/android/server/connectivity/NetworkRanker.java
index d94c8dc..c473444 100644
--- a/service/src/com/android/server/connectivity/NetworkRanker.java
+++ b/service/src/com/android/server/connectivity/NetworkRanker.java
@@ -17,6 +17,8 @@
 package com.android.server.connectivity;
 
 import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY;
 import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
@@ -221,6 +223,19 @@
     }
 
     /**
+     * Returns whether the scorable has any of the PRIORITIZE_* capabilities.
+     *
+     * These capabilities code for customer slices, and a network that has one is a customer slice.
+     */
+    private boolean hasPrioritizedCapability(@NonNull final Scoreable nai) {
+        final NetworkCapabilities caps = nai.getCapsNoCopy();
+        final long anyPrioritizeCapability =
+                (1L << NET_CAPABILITY_PRIORITIZE_LATENCY)
+                | (1L << NET_CAPABILITY_PRIORITIZE_BANDWIDTH);
+        return 0 != (caps.getCapabilitiesInternal() & anyPrioritizeCapability);
+    }
+
+    /**
      * Get the best network among a list of candidates according to policy.
      * @param candidates the candidates
      * @param currentSatisfier the current satisfier, or null if none
@@ -324,6 +339,12 @@
         // change from the previous result. If there were, it's guaranteed candidates.size() > 0
         // because accepted.size() > 0 above.
 
+        // If any network is not a slice with prioritized bandwidth or latency, don't choose one
+        // that is.
+        partitionInto(candidates, nai -> !hasPrioritizedCapability(nai), accepted, rejected);
+        if (accepted.size() == 1) return accepted.get(0);
+        if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted);
+
         // If some of the networks have a better transport than others, keep only the ones with
         // the best transports.
         for (final int transport : PREFERRED_TRANSPORTS_ORDER) {
diff --git a/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt b/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt
index 1e3f389..87f7369 100644
--- a/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt
@@ -18,9 +18,12 @@
 
 import android.net.NetworkCapabilities
 import android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL as NET_CAP_PORTAL
+import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET as NET_CAP_INTERNET
+import android.net.NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH as NET_CAP_PRIO_BW
 import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
 import android.net.NetworkCapabilities.TRANSPORT_WIFI
 import android.net.NetworkScore.KEEP_CONNECTED_NONE
+import android.net.NetworkScore.POLICY_TRANSPORT_PRIMARY
 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
@@ -50,8 +53,8 @@
 class NetworkRankerTest(private val activelyPreferBadWifi: Boolean) {
     private val mRanker = NetworkRanker(NetworkRanker.Configuration(activelyPreferBadWifi))
 
-    private class TestScore(private val sc: FullScore, private val nc: NetworkCapabilities)
-            : NetworkRanker.Scoreable {
+    private class TestScore(private val sc: FullScore, private val nc: NetworkCapabilities) :
+            NetworkRanker.Scoreable {
         override fun getScore() = sc
         override fun getCapsNoCopy(): NetworkCapabilities = nc
     }
@@ -196,4 +199,41 @@
         val badExitingWifi = TestScore(score(EVER_EVALUATED, EVER_VALIDATED, EXITING), CAPS_WIFI)
         assertEquals(cell, rank(cell, badExitingWifi))
     }
+
+    @Test
+    fun testValidatedPolicyStrongerThanSlice() {
+        val unvalidatedNonslice = TestScore(score(EVER_EVALUATED),
+                caps(TRANSPORT_CELLULAR, NET_CAP_INTERNET))
+        val slice = TestScore(score(EVER_EVALUATED, IS_VALIDATED),
+                caps(TRANSPORT_CELLULAR, NET_CAP_INTERNET, NET_CAP_PRIO_BW))
+        assertEquals(slice, rank(slice, unvalidatedNonslice))
+    }
+
+    @Test
+    fun testPrimaryPolicyStrongerThanSlice() {
+        val nonslice = TestScore(score(EVER_EVALUATED),
+                caps(TRANSPORT_CELLULAR, NET_CAP_INTERNET))
+        val primarySlice = TestScore(score(EVER_EVALUATED, POLICY_TRANSPORT_PRIMARY),
+                caps(TRANSPORT_CELLULAR, NET_CAP_INTERNET, NET_CAP_PRIO_BW))
+        assertEquals(primarySlice, rank(nonslice, primarySlice))
+    }
+
+    @Test
+    fun testPreferNonSlices() {
+        // Slices lose to non-slices for general ranking
+        val nonslice = TestScore(score(EVER_EVALUATED, IS_VALIDATED),
+                caps(TRANSPORT_CELLULAR, NET_CAP_INTERNET))
+        val slice = TestScore(score(EVER_EVALUATED, IS_VALIDATED),
+                caps(TRANSPORT_CELLULAR, NET_CAP_INTERNET, NET_CAP_PRIO_BW))
+        assertEquals(nonslice, rank(slice, nonslice))
+    }
+
+    @Test
+    fun testSlicePolicyStrongerThanTransport() {
+        val nonSliceCell = TestScore(score(EVER_EVALUATED, IS_VALIDATED),
+                caps(TRANSPORT_CELLULAR, NET_CAP_INTERNET))
+        val sliceWifi = TestScore(score(EVER_EVALUATED, IS_VALIDATED),
+                caps(TRANSPORT_WIFI, NET_CAP_INTERNET, NET_CAP_PRIO_BW))
+        assertEquals(nonSliceCell, rank(nonSliceCell, sliceWifi))
+    }
 }