From 6eab1d0af365f6720b20dc12196f7feac4164df1 Mon Sep 17 00:00:00 2001 From: Yan Yan Date: Sat, 7 Jun 2025 01:01:13 +0000 Subject: [PATCH] Allow VCN network to beat a physical cell in network ranking This patch adds a new POLICY to identify a VCN network offer, and updates NetworkRanker to allow a VCN network to win over a physical cell network. This is to fix the issue where VCN cannot exit safe mode when there is already a phsical cell Bug: 391977557 Test: NetworkRankerTest(new test) Flag: EXEMPT bugfix Change-Id: I00ebeb3eeb30ca8b46d25bbde1b6fd3dfee53993 --- framework/src/android/net/NetworkScore.java | 34 ++++++++++++++++- .../server/connectivity/NetworkRanker.java | 11 ++++++ .../server/connectivity/NetworkRankerTest.kt | 38 +++++++++++++++++++ 3 files changed, 82 insertions(+), 1 deletion(-) diff --git a/framework/src/android/net/NetworkScore.java b/framework/src/android/net/NetworkScore.java index 935dea1348..4e446064ca 100644 --- a/framework/src/android/net/NetworkScore.java +++ b/framework/src/android/net/NetworkScore.java @@ -89,11 +89,15 @@ public final class NetworkScore implements Parcelable { // This network is exiting : it will likely disconnect in a few seconds. /** @hide */ public static final int POLICY_EXITING = 3; + // This is a VCN network + // TODO: b/424266491 Remove this policy after adding a new NET_CAP to identify VCN + /** @hide */ + public static final int POLICY_VCN = 4; /** @hide */ public static final int MIN_AGENT_MANAGED_POLICY = POLICY_YIELD_TO_BAD_WIFI; /** @hide */ - public static final int MAX_AGENT_MANAGED_POLICY = POLICY_EXITING; + public static final int MAX_AGENT_MANAGED_POLICY = POLICY_VCN; // Bitmask of all the policies applied to this score. private final long mPolicies; @@ -193,6 +197,17 @@ public final class NetworkScore implements Parcelable { return hasPolicy(POLICY_EXITING); } + /** + * Whether this network is a VCN cell network. + * + *

When multiple cell networks are active, the device prefers the VCN cell network over a + * physical cell network. + */ + /** @hide */ + public boolean isVcn() { + return hasPolicy(POLICY_VCN); + } + @Override public String toString() { return "Score(Policies : " + mPolicies + ")"; @@ -319,6 +334,23 @@ public final class NetworkScore implements Parcelable { return this; } + /** + * Set for a VCN network. + * + *

When multiple cell networks are active, the device prefers the VCN cell network over a + * physical cell network. + */ + /** @hide */ + @NonNull + public Builder setVcn(final boolean val) { + if (val) { + mPolicies |= (1L << POLICY_VCN); + } else { + mPolicies &= ~(1L << POLICY_VCN); + } + return this; + } + /** * Set the keep-connected reason. * diff --git a/service/src/com/android/server/connectivity/NetworkRanker.java b/service/src/com/android/server/connectivity/NetworkRanker.java index d94c8dceeb..371daa6b38 100644 --- a/service/src/com/android/server/connectivity/NetworkRanker.java +++ b/service/src/com/android/server/connectivity/NetworkRanker.java @@ -23,6 +23,7 @@ import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.NetworkScore.POLICY_EXITING; import static android.net.NetworkScore.POLICY_TRANSPORT_PRIMARY; +import static android.net.NetworkScore.POLICY_VCN; import static android.net.NetworkScore.POLICY_YIELD_TO_BAD_WIFI; import static com.android.net.module.util.CollectionUtils.filter; @@ -336,6 +337,16 @@ public class NetworkRanker { } } + // If two networks are equivalent, and one is a VCN network, keep the VCN network. This + // ensures VCN can beat a physical cell network + // Note: this check must happen before POLICY_IS_DESTROYED check, so that a VCN being + // destroyed can be preferred over a physical cell and then be handed over to another VCN + partitionInto(candidates, nai -> nai.getScore().hasPolicy(POLICY_VCN), accepted, rejected); + if (accepted.size() == 1) return accepted.get(0); + if (accepted.size() > 0 && rejected.size() > 0) { + candidates = new ArrayList<>(accepted); + } + // If two networks are equivalent, and one has been destroyed pending replacement, keep the // other one. This ensures that when the replacement connects, it's preferred. partitionInto(candidates, nai -> !nai.getScore().hasPolicy(POLICY_IS_DESTROYED), diff --git a/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt b/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt index 1e3f38970a..2e29eecd51 100644 --- a/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt +++ b/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt @@ -23,6 +23,7 @@ import android.net.NetworkCapabilities.TRANSPORT_WIFI import android.net.NetworkScore.KEEP_CONNECTED_NONE import android.net.NetworkScore.POLICY_EXITING as EXITING import android.net.NetworkScore.POLICY_TRANSPORT_PRIMARY as PRIMARY +import android.net.NetworkScore.POLICY_VCN as VCN import android.net.NetworkScore.POLICY_YIELD_TO_BAD_WIFI as YIELD_TO_BAD_WIFI import android.os.Build import androidx.test.filters.SmallTest @@ -30,6 +31,7 @@ import com.android.connectivity.resources.R 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_DESTROYED import com.android.server.connectivity.FullScore.POLICY_IS_VALIDATED as IS_VALIDATED import com.android.testutils.DevSdkIgnoreRule import org.junit.Rule @@ -196,4 +198,40 @@ class NetworkRankerTest(private val activelyPreferBadWifi: Boolean) { val badExitingWifi = TestScore(score(EVER_EVALUATED, EVER_VALIDATED, EXITING), CAPS_WIFI) assertEquals(cell, rank(cell, badExitingWifi)) } + + @Test + fun testPreferVcn_onePhysicalCell() { + // A VCN network wins over a physical cell + val physicalCell = TestScore(score(PRIMARY), CAPS_CELL) + val vcn = TestScore(score(PRIMARY, VCN), CAPS_CELL) + + assertEquals(vcn, rank(physicalCell, vcn)) + } + + @Test + fun testPreferDestroyedVcn_onePhysicalCell() { + // A destroyed VCN network wins over a physical cell + val physicalCell = TestScore(score(PRIMARY), CAPS_CELL) + val destroyedVcn = TestScore(score(PRIMARY, VCN, POLICY_IS_DESTROYED), CAPS_CELL) + + assertEquals(destroyedVcn, rank(physicalCell, destroyedVcn)) + } + + @Test + fun testPhysicalCell_oneExitingVcn() { + // A physical cell wins over an exiting VCN network + val physicalCell = TestScore(score(PRIMARY), CAPS_CELL) + val exitingVcn = TestScore(score(PRIMARY, VCN, EXITING), CAPS_CELL) + + assertEquals(physicalCell, rank(physicalCell, exitingVcn)) + } + + @Test + fun testPreferWifi_oneVcn() { + // A Wifi wins over a VCN network + val wifi = TestScore(score(), CAPS_WIFI) + val vcn = TestScore(score(PRIMARY, VCN), CAPS_CELL) + + assertEquals(wifi, rank(wifi, vcn)) + } }