Loading packages/Connectivity/service/src/com/android/server/connectivity/NetworkRanker.java +72 −22 Original line number Diff line number Diff line Loading @@ -108,7 +108,58 @@ public class NetworkRanker { } } @Nullable private <T extends Scoreable> T getBestNetworkByPolicy( private <T extends Scoreable> boolean isBadWiFi(@NonNull final T candidate) { return candidate.getScore().hasPolicy(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD) && candidate.getCapsNoCopy().hasTransport(TRANSPORT_WIFI); } /** * Apply the "yield to bad WiFi" policy. * * This function must run immediately after the validation policy. * * If any of the accepted networks has the "yield to bad WiFi" policy AND there are some * bad WiFis in the rejected list, then move the networks with the policy to the rejected * list. If this leaves no accepted network, then move the bad WiFis back to the accepted list. * * This function returns nothing, but will have updated accepted and rejected in-place. * * @param accepted networks accepted by the validation policy * @param rejected networks rejected by the validation policy */ private <T extends Scoreable> void applyYieldToBadWifiPolicy(@NonNull ArrayList<T> accepted, @NonNull ArrayList<T> rejected) { if (!CollectionUtils.any(accepted, n -> n.getScore().hasPolicy(POLICY_YIELD_TO_BAD_WIFI))) { // No network with the policy : do nothing. return; } if (!CollectionUtils.any(rejected, n -> isBadWiFi(n))) { // No bad WiFi : do nothing. return; } if (CollectionUtils.all(accepted, n -> n.getScore().hasPolicy(POLICY_YIELD_TO_BAD_WIFI))) { // All validated networks yield to bad WiFis : keep bad WiFis alongside with the // yielders. This is important because the yielders need to be compared to the bad // wifis by the following policies (e.g. exiting). final ArrayList<T> acceptedYielders = new ArrayList<>(accepted); final ArrayList<T> rejectedWithBadWiFis = new ArrayList<>(rejected); partitionInto(rejectedWithBadWiFis, n -> isBadWiFi(n), accepted, rejected); accepted.addAll(acceptedYielders); return; } // Only some of the validated networks yield to bad WiFi : keep only the ones who don't. final ArrayList<T> acceptedWithYielders = new ArrayList<>(accepted); partitionInto(acceptedWithYielders, n -> !n.getScore().hasPolicy(POLICY_YIELD_TO_BAD_WIFI), accepted, rejected); } /** * 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 * @return the best network */ @Nullable public <T extends Scoreable> T getBestNetworkByPolicy( @NonNull List<T> candidates, @Nullable final T currentSatisfier) { // Used as working areas. Loading Loading @@ -148,24 +199,15 @@ public class NetworkRanker { if (accepted.size() == 1) return accepted.get(0); if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted); // Yield to bad wifi policy : if any wifi has ever been validated (even if it's now // unvalidated), and unless it's been explicitly avoided when bad in UI, then keep only // networks that don't yield to such a wifi network. final boolean anyWiFiEverValidated = CollectionUtils.any(candidates, nai -> nai.getScore().hasPolicy(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD) && nai.getCapsNoCopy().hasTransport(TRANSPORT_WIFI)); if (anyWiFiEverValidated) { partitionInto(candidates, nai -> !nai.getScore().hasPolicy(POLICY_YIELD_TO_BAD_WIFI), accepted, rejected); if (accepted.size() == 1) return accepted.get(0); if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted); } // If any network is validated (or should be accepted even if it's not validated), then // don't choose one that isn't. partitionInto(candidates, nai -> nai.getScore().hasPolicy(POLICY_IS_VALIDATED) || nai.getScore().hasPolicy(POLICY_ACCEPT_UNVALIDATED), accepted, rejected); // Yield to bad wifi policy : if any network has the "yield to bad WiFi" policy and // there are bad WiFis connected, then accept the bad WiFis and reject the networks with // the policy. applyYieldToBadWifiPolicy(accepted, rejected); if (accepted.size() == 1) return accepted.get(0); if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted); Loading Loading @@ -194,16 +236,24 @@ public class NetworkRanker { // subscription with the same transport. partitionInto(candidates, nai -> nai.getScore().hasPolicy(POLICY_TRANSPORT_PRIMARY), accepted, rejected); for (final Scoreable defaultSubNai : accepted) { // Remove all networks without the DEFAULT_SUBSCRIPTION policy and the same transports // as a network that has it. if (accepted.size() > 0) { // Some networks are primary. For each transport, keep only the primary, but also // keep all networks for which there isn't a primary (which are now in the |rejected| // array). // So for each primary network, remove from |rejected| all networks with the same // transports as one of the primary networks. The remaining networks should be accepted. for (final T defaultSubNai : accepted) { final int[] transports = defaultSubNai.getCapsNoCopy().getTransportTypes(); candidates.removeIf(nai -> !nai.getScore().hasPolicy(POLICY_TRANSPORT_PRIMARY) && Arrays.equals(transports, nai.getCapsNoCopy().getTransportTypes())); rejected.removeIf( nai -> Arrays.equals(transports, nai.getCapsNoCopy().getTransportTypes())); } accepted.addAll(rejected); candidates = new ArrayList<>(accepted); } if (1 == candidates.size()) return candidates.get(0); // It's guaranteed candidates.size() > 0 because there is at least one with the // TRANSPORT_PRIMARY policy and only those without it were removed. // If there were no primary network, then candidates.size() > 0 because it didn't // change from the previous result. If there were, it's guaranteed candidates.size() > 0 // because accepted.size() > 0 above. // If some of the networks have a better transport than others, keep only the ones with // the best transports. Loading packages/Connectivity/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt +128 −46 Original line number Diff line number Diff line /* * Copyright (C) 2020 The Android Open Source Project * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. Loading @@ -17,74 +17,156 @@ package com.android.server.connectivity import android.net.NetworkCapabilities import android.net.NetworkRequest 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.os.Build import androidx.test.filters.SmallTest import androidx.test.runner.AndroidJUnit4 import com.android.server.connectivity.FullScore.POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD import com.android.server.connectivity.FullScore.POLICY_IS_VALIDATED import com.android.testutils.DevSdkIgnoreRule import com.android.testutils.DevSdkIgnoreRunner import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.any import org.mockito.Mockito.doReturn import org.mockito.Mockito.mock import kotlin.test.assertEquals import kotlin.test.assertNull @RunWith(AndroidJUnit4::class) private fun score(vararg policies: Int) = FullScore(0, policies.fold(0L) { acc, e -> acc or (1L shl e) }, KEEP_CONNECTED_NONE) private fun caps(transport: Int) = NetworkCapabilities.Builder().addTransportType(transport).build() @SmallTest @RunWith(DevSdkIgnoreRunner::class) @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R) class NetworkRankerTest { private val ranker = NetworkRanker() private val mRanker = NetworkRanker() private class TestScore(private val sc: FullScore, private val nc: NetworkCapabilities) : NetworkRanker.Scoreable { override fun getScore() = sc override fun getCapsNoCopy(): NetworkCapabilities = nc } @Test fun testYieldToBadWiFiOneCell() { // Only cell, it wins val winner = TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED), caps(TRANSPORT_CELLULAR)) val scores = listOf(winner) assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null)) } private fun makeNai(satisfy: Boolean, legacyScore: Int) = mock(NetworkAgentInfo::class.java).also { doReturn(satisfy).`when`(it).satisfies(any()) val fs = FullScore(legacyScore, 0 /* policies */, KEEP_CONNECTED_NONE) doReturn(fs).`when`(it).getScore() val nc = NetworkCapabilities.Builder().build() doReturn(nc).`when`(it).getCapsNoCopy() @Test fun testYieldToBadWiFiOneCellOneBadWiFi() { // Bad wifi wins against yielding validated cell val winner = TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD), caps(TRANSPORT_WIFI)) val scores = listOf( winner, TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED), caps(TRANSPORT_CELLULAR)) ) assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null)) } @Test fun testYieldToBadWiFiOneCellTwoBadWiFi() { // Bad wifi wins against yielding validated cell. Prefer the one that's primary. val winner = TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD, POLICY_TRANSPORT_PRIMARY), caps(TRANSPORT_WIFI)) val scores = listOf( winner, TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD), caps(TRANSPORT_WIFI)), TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED), caps(TRANSPORT_CELLULAR)) ) assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null)) } @Test fun testGetBestNetwork() { val scores = listOf(20, 50, 90, 60, 23, 68) val nais = scores.map { makeNai(true, it) } val bestNetwork = nais[2] // The one with the top score val someRequest = mock(NetworkRequest::class.java) assertEquals(bestNetwork, ranker.getBestNetwork(someRequest, nais, bestNetwork)) fun testYieldToBadWiFiOneCellTwoBadWiFiOneNotAvoided() { // Bad wifi ever validated wins against bad wifi that never was validated (or was // avoided when bad). val winner = TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD), caps(TRANSPORT_WIFI)) val scores = listOf( winner, TestScore(score(), caps(TRANSPORT_WIFI)), TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED), caps(TRANSPORT_CELLULAR)) ) assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null)) } @Test fun testIgnoreNonSatisfying() { val nais = listOf(makeNai(true, 20), makeNai(true, 50), makeNai(false, 90), makeNai(false, 60), makeNai(true, 23), makeNai(false, 68)) val bestNetwork = nais[1] // Top score that's satisfying val someRequest = mock(NetworkRequest::class.java) assertEquals(bestNetwork, ranker.getBestNetwork(someRequest, nais, nais[1])) fun testYieldToBadWiFiOneCellOneBadWiFiOneGoodWiFi() { // Good wifi wins val winner = TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD, POLICY_IS_VALIDATED), caps(TRANSPORT_WIFI)) val scores = listOf( winner, TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD, POLICY_TRANSPORT_PRIMARY), caps(TRANSPORT_WIFI)), TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED), caps(TRANSPORT_CELLULAR)) ) assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null)) } @Test fun testNoMatch() { val nais = listOf(makeNai(false, 20), makeNai(false, 50), makeNai(false, 90)) val someRequest = mock(NetworkRequest::class.java) assertNull(ranker.getBestNetwork(someRequest, nais, null)) fun testYieldToBadWiFiTwoCellsOneBadWiFi() { // Cell that doesn't yield wins over cell that yields and bad wifi val winner = TestScore(score(POLICY_IS_VALIDATED), caps(TRANSPORT_CELLULAR)) val scores = listOf( winner, TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD, POLICY_TRANSPORT_PRIMARY), caps(TRANSPORT_WIFI)), TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED), caps(TRANSPORT_CELLULAR)) ) assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null)) } @Test fun testEmpty() { val someRequest = mock(NetworkRequest::class.java) assertNull(ranker.getBestNetwork(someRequest, emptyList(), null)) fun testYieldToBadWiFiTwoCellsOneBadWiFiOneGoodWiFi() { // Good wifi wins over cell that doesn't yield and cell that yields val winner = TestScore(score(POLICY_IS_VALIDATED), caps(TRANSPORT_WIFI)) val scores = listOf( winner, TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD, POLICY_TRANSPORT_PRIMARY), caps(TRANSPORT_WIFI)), TestScore(score(POLICY_IS_VALIDATED), caps(TRANSPORT_CELLULAR)), TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED), caps(TRANSPORT_CELLULAR)) ) assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null)) } // Make sure the ranker is "stable" (as in stable sort), that is, it always returns the FIRST // network satisfying the request if multiple of them have the same score. @Test fun testStable() { val nais1 = listOf(makeNai(true, 30), makeNai(true, 30), makeNai(true, 30), makeNai(true, 30), makeNai(true, 30), makeNai(true, 30)) val someRequest = mock(NetworkRequest::class.java) assertEquals(nais1[0], ranker.getBestNetwork(someRequest, nais1, nais1[0])) fun testYieldToBadWiFiOneExitingGoodWiFi() { // Yielding cell wins over good exiting wifi val winner = TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED), caps(TRANSPORT_CELLULAR)) val scores = listOf( winner, TestScore(score(POLICY_IS_VALIDATED, POLICY_EXITING), caps(TRANSPORT_WIFI)) ) assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null)) } val nais2 = listOf(makeNai(true, 30), makeNai(true, 50), makeNai(true, 20), makeNai(true, 50), makeNai(true, 50), makeNai(true, 40)) assertEquals(nais2[1], ranker.getBestNetwork(someRequest, nais2, nais2[1])) @Test fun testYieldToBadWiFiOneExitingBadWiFi() { // Yielding cell wins over bad exiting wifi val winner = TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED), caps(TRANSPORT_CELLULAR)) val scores = listOf( winner, TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD, POLICY_EXITING), caps(TRANSPORT_WIFI)) ) assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null)) } } Loading
packages/Connectivity/service/src/com/android/server/connectivity/NetworkRanker.java +72 −22 Original line number Diff line number Diff line Loading @@ -108,7 +108,58 @@ public class NetworkRanker { } } @Nullable private <T extends Scoreable> T getBestNetworkByPolicy( private <T extends Scoreable> boolean isBadWiFi(@NonNull final T candidate) { return candidate.getScore().hasPolicy(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD) && candidate.getCapsNoCopy().hasTransport(TRANSPORT_WIFI); } /** * Apply the "yield to bad WiFi" policy. * * This function must run immediately after the validation policy. * * If any of the accepted networks has the "yield to bad WiFi" policy AND there are some * bad WiFis in the rejected list, then move the networks with the policy to the rejected * list. If this leaves no accepted network, then move the bad WiFis back to the accepted list. * * This function returns nothing, but will have updated accepted and rejected in-place. * * @param accepted networks accepted by the validation policy * @param rejected networks rejected by the validation policy */ private <T extends Scoreable> void applyYieldToBadWifiPolicy(@NonNull ArrayList<T> accepted, @NonNull ArrayList<T> rejected) { if (!CollectionUtils.any(accepted, n -> n.getScore().hasPolicy(POLICY_YIELD_TO_BAD_WIFI))) { // No network with the policy : do nothing. return; } if (!CollectionUtils.any(rejected, n -> isBadWiFi(n))) { // No bad WiFi : do nothing. return; } if (CollectionUtils.all(accepted, n -> n.getScore().hasPolicy(POLICY_YIELD_TO_BAD_WIFI))) { // All validated networks yield to bad WiFis : keep bad WiFis alongside with the // yielders. This is important because the yielders need to be compared to the bad // wifis by the following policies (e.g. exiting). final ArrayList<T> acceptedYielders = new ArrayList<>(accepted); final ArrayList<T> rejectedWithBadWiFis = new ArrayList<>(rejected); partitionInto(rejectedWithBadWiFis, n -> isBadWiFi(n), accepted, rejected); accepted.addAll(acceptedYielders); return; } // Only some of the validated networks yield to bad WiFi : keep only the ones who don't. final ArrayList<T> acceptedWithYielders = new ArrayList<>(accepted); partitionInto(acceptedWithYielders, n -> !n.getScore().hasPolicy(POLICY_YIELD_TO_BAD_WIFI), accepted, rejected); } /** * 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 * @return the best network */ @Nullable public <T extends Scoreable> T getBestNetworkByPolicy( @NonNull List<T> candidates, @Nullable final T currentSatisfier) { // Used as working areas. Loading Loading @@ -148,24 +199,15 @@ public class NetworkRanker { if (accepted.size() == 1) return accepted.get(0); if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted); // Yield to bad wifi policy : if any wifi has ever been validated (even if it's now // unvalidated), and unless it's been explicitly avoided when bad in UI, then keep only // networks that don't yield to such a wifi network. final boolean anyWiFiEverValidated = CollectionUtils.any(candidates, nai -> nai.getScore().hasPolicy(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD) && nai.getCapsNoCopy().hasTransport(TRANSPORT_WIFI)); if (anyWiFiEverValidated) { partitionInto(candidates, nai -> !nai.getScore().hasPolicy(POLICY_YIELD_TO_BAD_WIFI), accepted, rejected); if (accepted.size() == 1) return accepted.get(0); if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted); } // If any network is validated (or should be accepted even if it's not validated), then // don't choose one that isn't. partitionInto(candidates, nai -> nai.getScore().hasPolicy(POLICY_IS_VALIDATED) || nai.getScore().hasPolicy(POLICY_ACCEPT_UNVALIDATED), accepted, rejected); // Yield to bad wifi policy : if any network has the "yield to bad WiFi" policy and // there are bad WiFis connected, then accept the bad WiFis and reject the networks with // the policy. applyYieldToBadWifiPolicy(accepted, rejected); if (accepted.size() == 1) return accepted.get(0); if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted); Loading Loading @@ -194,16 +236,24 @@ public class NetworkRanker { // subscription with the same transport. partitionInto(candidates, nai -> nai.getScore().hasPolicy(POLICY_TRANSPORT_PRIMARY), accepted, rejected); for (final Scoreable defaultSubNai : accepted) { // Remove all networks without the DEFAULT_SUBSCRIPTION policy and the same transports // as a network that has it. if (accepted.size() > 0) { // Some networks are primary. For each transport, keep only the primary, but also // keep all networks for which there isn't a primary (which are now in the |rejected| // array). // So for each primary network, remove from |rejected| all networks with the same // transports as one of the primary networks. The remaining networks should be accepted. for (final T defaultSubNai : accepted) { final int[] transports = defaultSubNai.getCapsNoCopy().getTransportTypes(); candidates.removeIf(nai -> !nai.getScore().hasPolicy(POLICY_TRANSPORT_PRIMARY) && Arrays.equals(transports, nai.getCapsNoCopy().getTransportTypes())); rejected.removeIf( nai -> Arrays.equals(transports, nai.getCapsNoCopy().getTransportTypes())); } accepted.addAll(rejected); candidates = new ArrayList<>(accepted); } if (1 == candidates.size()) return candidates.get(0); // It's guaranteed candidates.size() > 0 because there is at least one with the // TRANSPORT_PRIMARY policy and only those without it were removed. // If there were no primary network, then candidates.size() > 0 because it didn't // change from the previous result. If there were, it's guaranteed candidates.size() > 0 // because accepted.size() > 0 above. // If some of the networks have a better transport than others, keep only the ones with // the best transports. Loading
packages/Connectivity/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt +128 −46 Original line number Diff line number Diff line /* * Copyright (C) 2020 The Android Open Source Project * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. Loading @@ -17,74 +17,156 @@ package com.android.server.connectivity import android.net.NetworkCapabilities import android.net.NetworkRequest 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.os.Build import androidx.test.filters.SmallTest import androidx.test.runner.AndroidJUnit4 import com.android.server.connectivity.FullScore.POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD import com.android.server.connectivity.FullScore.POLICY_IS_VALIDATED import com.android.testutils.DevSdkIgnoreRule import com.android.testutils.DevSdkIgnoreRunner import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.any import org.mockito.Mockito.doReturn import org.mockito.Mockito.mock import kotlin.test.assertEquals import kotlin.test.assertNull @RunWith(AndroidJUnit4::class) private fun score(vararg policies: Int) = FullScore(0, policies.fold(0L) { acc, e -> acc or (1L shl e) }, KEEP_CONNECTED_NONE) private fun caps(transport: Int) = NetworkCapabilities.Builder().addTransportType(transport).build() @SmallTest @RunWith(DevSdkIgnoreRunner::class) @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R) class NetworkRankerTest { private val ranker = NetworkRanker() private val mRanker = NetworkRanker() private class TestScore(private val sc: FullScore, private val nc: NetworkCapabilities) : NetworkRanker.Scoreable { override fun getScore() = sc override fun getCapsNoCopy(): NetworkCapabilities = nc } @Test fun testYieldToBadWiFiOneCell() { // Only cell, it wins val winner = TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED), caps(TRANSPORT_CELLULAR)) val scores = listOf(winner) assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null)) } private fun makeNai(satisfy: Boolean, legacyScore: Int) = mock(NetworkAgentInfo::class.java).also { doReturn(satisfy).`when`(it).satisfies(any()) val fs = FullScore(legacyScore, 0 /* policies */, KEEP_CONNECTED_NONE) doReturn(fs).`when`(it).getScore() val nc = NetworkCapabilities.Builder().build() doReturn(nc).`when`(it).getCapsNoCopy() @Test fun testYieldToBadWiFiOneCellOneBadWiFi() { // Bad wifi wins against yielding validated cell val winner = TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD), caps(TRANSPORT_WIFI)) val scores = listOf( winner, TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED), caps(TRANSPORT_CELLULAR)) ) assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null)) } @Test fun testYieldToBadWiFiOneCellTwoBadWiFi() { // Bad wifi wins against yielding validated cell. Prefer the one that's primary. val winner = TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD, POLICY_TRANSPORT_PRIMARY), caps(TRANSPORT_WIFI)) val scores = listOf( winner, TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD), caps(TRANSPORT_WIFI)), TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED), caps(TRANSPORT_CELLULAR)) ) assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null)) } @Test fun testGetBestNetwork() { val scores = listOf(20, 50, 90, 60, 23, 68) val nais = scores.map { makeNai(true, it) } val bestNetwork = nais[2] // The one with the top score val someRequest = mock(NetworkRequest::class.java) assertEquals(bestNetwork, ranker.getBestNetwork(someRequest, nais, bestNetwork)) fun testYieldToBadWiFiOneCellTwoBadWiFiOneNotAvoided() { // Bad wifi ever validated wins against bad wifi that never was validated (or was // avoided when bad). val winner = TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD), caps(TRANSPORT_WIFI)) val scores = listOf( winner, TestScore(score(), caps(TRANSPORT_WIFI)), TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED), caps(TRANSPORT_CELLULAR)) ) assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null)) } @Test fun testIgnoreNonSatisfying() { val nais = listOf(makeNai(true, 20), makeNai(true, 50), makeNai(false, 90), makeNai(false, 60), makeNai(true, 23), makeNai(false, 68)) val bestNetwork = nais[1] // Top score that's satisfying val someRequest = mock(NetworkRequest::class.java) assertEquals(bestNetwork, ranker.getBestNetwork(someRequest, nais, nais[1])) fun testYieldToBadWiFiOneCellOneBadWiFiOneGoodWiFi() { // Good wifi wins val winner = TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD, POLICY_IS_VALIDATED), caps(TRANSPORT_WIFI)) val scores = listOf( winner, TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD, POLICY_TRANSPORT_PRIMARY), caps(TRANSPORT_WIFI)), TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED), caps(TRANSPORT_CELLULAR)) ) assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null)) } @Test fun testNoMatch() { val nais = listOf(makeNai(false, 20), makeNai(false, 50), makeNai(false, 90)) val someRequest = mock(NetworkRequest::class.java) assertNull(ranker.getBestNetwork(someRequest, nais, null)) fun testYieldToBadWiFiTwoCellsOneBadWiFi() { // Cell that doesn't yield wins over cell that yields and bad wifi val winner = TestScore(score(POLICY_IS_VALIDATED), caps(TRANSPORT_CELLULAR)) val scores = listOf( winner, TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD, POLICY_TRANSPORT_PRIMARY), caps(TRANSPORT_WIFI)), TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED), caps(TRANSPORT_CELLULAR)) ) assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null)) } @Test fun testEmpty() { val someRequest = mock(NetworkRequest::class.java) assertNull(ranker.getBestNetwork(someRequest, emptyList(), null)) fun testYieldToBadWiFiTwoCellsOneBadWiFiOneGoodWiFi() { // Good wifi wins over cell that doesn't yield and cell that yields val winner = TestScore(score(POLICY_IS_VALIDATED), caps(TRANSPORT_WIFI)) val scores = listOf( winner, TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD, POLICY_TRANSPORT_PRIMARY), caps(TRANSPORT_WIFI)), TestScore(score(POLICY_IS_VALIDATED), caps(TRANSPORT_CELLULAR)), TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED), caps(TRANSPORT_CELLULAR)) ) assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null)) } // Make sure the ranker is "stable" (as in stable sort), that is, it always returns the FIRST // network satisfying the request if multiple of them have the same score. @Test fun testStable() { val nais1 = listOf(makeNai(true, 30), makeNai(true, 30), makeNai(true, 30), makeNai(true, 30), makeNai(true, 30), makeNai(true, 30)) val someRequest = mock(NetworkRequest::class.java) assertEquals(nais1[0], ranker.getBestNetwork(someRequest, nais1, nais1[0])) fun testYieldToBadWiFiOneExitingGoodWiFi() { // Yielding cell wins over good exiting wifi val winner = TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED), caps(TRANSPORT_CELLULAR)) val scores = listOf( winner, TestScore(score(POLICY_IS_VALIDATED, POLICY_EXITING), caps(TRANSPORT_WIFI)) ) assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null)) } val nais2 = listOf(makeNai(true, 30), makeNai(true, 50), makeNai(true, 20), makeNai(true, 50), makeNai(true, 50), makeNai(true, 40)) assertEquals(nais2[1], ranker.getBestNetwork(someRequest, nais2, nais2[1])) @Test fun testYieldToBadWiFiOneExitingBadWiFi() { // Yielding cell wins over bad exiting wifi val winner = TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED), caps(TRANSPORT_CELLULAR)) val scores = listOf( winner, TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD, POLICY_EXITING), caps(TRANSPORT_WIFI)) ) assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null)) } }