Add a field and score flag for first evaluation

A wifi network that is still evaluating, i.e. that doesn't know
yet if it's behind a captive portal, should not be preferred
to a network that yields to bad wifi because the stack doesn't
know yet if it's bad.

To rank the networks correctly, the ranker will therefore need
to know whether a network is still being evaluated. This patch
adds the time when this happened first to the NAI (as a
timestamp for debuggability) and the corresponding flag in
FullScore.

This doesn't have new tests because it doesn't yet expose
new behavior. Tests will come with the behavior.

Test: FrameworksNetTests
Change-Id: I737f314760356926fc07e6eef52f3c8abba2248b
This commit is contained in:
Chalard Jean
2022-08-30 21:25:36 +09:00
parent 4c46308265
commit f9f4a8ddfe
3 changed files with 57 additions and 11 deletions

View File

@@ -85,13 +85,23 @@ public class FullScore {
/** @hide */ /** @hide */
public static final int POLICY_IS_INVINCIBLE = 56; public static final int POLICY_IS_INVINCIBLE = 56;
// This network has undergone initial validation.
//
// The stack considers that any result finding some working connectivity (valid, partial,
// captive portal) is an initial validation. Negative result (not valid), however, is not
// considered initial validation until {@link ConnectivityService#PROMPT_UNVALIDATED_DELAY_MS}
// have elapsed. This is because some networks may spuriously fail for a short time immediately
// after associating. If no positive result is found after the timeout has elapsed, then
// the network has been evaluated once.
public static final int POLICY_EVER_EVALUATED = 55;
// The network agent has communicated that this network no longer functions, and the underlying // The network agent has communicated that this network no longer functions, and the underlying
// native network has been destroyed. The network will still be reported to clients as connected // native network has been destroyed. The network will still be reported to clients as connected
// until a timeout expires, the agent disconnects, or the network no longer satisfies requests. // until a timeout expires, the agent disconnects, or the network no longer satisfies requests.
// This network should lose to an identical network that has not been destroyed, but should // This network should lose to an identical network that has not been destroyed, but should
// otherwise be scored exactly the same. // otherwise be scored exactly the same.
/** @hide */ /** @hide */
public static final int POLICY_IS_DESTROYED = 55; public static final int POLICY_IS_DESTROYED = 54;
// To help iterate when printing // To help iterate when printing
@VisibleForTesting @VisibleForTesting
@@ -144,6 +154,7 @@ public class FullScore {
* @param everValidated whether this network has ever validated * @param everValidated whether this network has ever validated
* @param avoidUnvalidated whether the user said in UI to avoid this network when unvalidated * @param avoidUnvalidated whether the user said in UI to avoid this network when unvalidated
* @param yieldToBadWiFi whether this network yields to a previously validated wifi gone bad * @param yieldToBadWiFi whether this network yields to a previously validated wifi gone bad
* @param everEvaluated whether this network ever evaluated at least once
* @param destroyed whether this network has been destroyed pending a replacement connecting * @param destroyed whether this network has been destroyed pending a replacement connecting
* @return a FullScore that is appropriate to use for ranking. * @return a FullScore that is appropriate to use for ranking.
*/ */
@@ -153,7 +164,7 @@ public class FullScore {
public static FullScore fromNetworkScore(@NonNull final NetworkScore score, public static FullScore fromNetworkScore(@NonNull final NetworkScore score,
@NonNull final NetworkCapabilities caps, @NonNull final NetworkAgentConfig config, @NonNull final NetworkCapabilities caps, @NonNull final NetworkAgentConfig config,
final boolean everValidated, final boolean avoidUnvalidated, final boolean everValidated, final boolean avoidUnvalidated,
final boolean yieldToBadWiFi, final boolean destroyed) { final boolean yieldToBadWiFi, final boolean everEvaluated, final boolean destroyed) {
return withPolicies(score.getPolicies(), return withPolicies(score.getPolicies(),
score.getKeepConnectedReason(), score.getKeepConnectedReason(),
caps.hasCapability(NET_CAPABILITY_VALIDATED), caps.hasCapability(NET_CAPABILITY_VALIDATED),
@@ -164,6 +175,7 @@ public class FullScore {
caps.hasCapability(NET_CAPABILITY_NOT_METERED), caps.hasCapability(NET_CAPABILITY_NOT_METERED),
yieldToBadWiFi, yieldToBadWiFi,
false /* invincible */, // only prospective scores can be invincible false /* invincible */, // only prospective scores can be invincible
everEvaluated,
destroyed); destroyed);
} }
@@ -198,15 +210,17 @@ public class FullScore {
// Prospective scores are always unmetered, because unmetered networks are stronger // Prospective scores are always unmetered, because unmetered networks are stronger
// than metered networks, and it's not known in advance whether the network is metered. // than metered networks, and it's not known in advance whether the network is metered.
final boolean unmetered = true; final boolean unmetered = true;
// A network can only be destroyed once it has connected.
final boolean destroyed = false;
// A prospective score is invincible if the legacy int in the filter is over the maximum // A prospective score is invincible if the legacy int in the filter is over the maximum
// score. // score.
final boolean invincible = score.getLegacyInt() > NetworkRanker.LEGACY_INT_MAX; final boolean invincible = score.getLegacyInt() > NetworkRanker.LEGACY_INT_MAX;
// A prospective network will eventually be evaluated.
final boolean everEvaluated = true;
// A network can only be destroyed once it has connected.
final boolean destroyed = false;
return withPolicies(score.getPolicies(), KEEP_CONNECTED_NONE, return withPolicies(score.getPolicies(), KEEP_CONNECTED_NONE,
mayValidate, everValidated, vpn, everUserSelected, acceptUnvalidated, mayValidate, everValidated, vpn, everUserSelected,
avoidUnvalidated, unmetered, acceptUnvalidated, avoidUnvalidated, unmetered, yieldToBadWiFi,
yieldToBadWiFi, invincible, destroyed); invincible, everEvaluated, destroyed);
} }
/** /**
@@ -224,6 +238,7 @@ public class FullScore {
final boolean everValidated, final boolean everValidated,
final boolean avoidUnvalidated, final boolean avoidUnvalidated,
final boolean yieldToBadWifi, final boolean yieldToBadWifi,
final boolean everEvaluated,
final boolean destroyed) { final boolean destroyed) {
return withPolicies(mPolicies, mKeepConnectedReason, return withPolicies(mPolicies, mKeepConnectedReason,
caps.hasCapability(NET_CAPABILITY_VALIDATED), caps.hasCapability(NET_CAPABILITY_VALIDATED),
@@ -234,6 +249,7 @@ public class FullScore {
caps.hasCapability(NET_CAPABILITY_NOT_METERED), caps.hasCapability(NET_CAPABILITY_NOT_METERED),
yieldToBadWifi, yieldToBadWifi,
false /* invincible */, // only prospective scores can be invincible false /* invincible */, // only prospective scores can be invincible
everEvaluated,
destroyed); destroyed);
} }
@@ -251,6 +267,7 @@ public class FullScore {
final boolean isUnmetered, final boolean isUnmetered,
final boolean yieldToBadWiFi, final boolean yieldToBadWiFi,
final boolean invincible, final boolean invincible,
final boolean everEvaluated,
final boolean destroyed) { final boolean destroyed) {
return new FullScore((externalPolicies & EXTERNAL_POLICIES_MASK) return new FullScore((externalPolicies & EXTERNAL_POLICIES_MASK)
| (isValidated ? 1L << POLICY_IS_VALIDATED : 0) | (isValidated ? 1L << POLICY_IS_VALIDATED : 0)
@@ -262,6 +279,7 @@ public class FullScore {
| (isUnmetered ? 1L << POLICY_IS_UNMETERED : 0) | (isUnmetered ? 1L << POLICY_IS_UNMETERED : 0)
| (yieldToBadWiFi ? 1L << POLICY_YIELD_TO_BAD_WIFI : 0) | (yieldToBadWiFi ? 1L << POLICY_YIELD_TO_BAD_WIFI : 0)
| (invincible ? 1L << POLICY_IS_INVINCIBLE : 0) | (invincible ? 1L << POLICY_IS_INVINCIBLE : 0)
| (everEvaluated ? 1L << POLICY_EVER_EVALUATED : 0)
| (destroyed ? 1L << POLICY_IS_DESTROYED : 0), | (destroyed ? 1L << POLICY_IS_DESTROYED : 0),
keepConnectedReason); keepConnectedReason);
} }

View File

@@ -377,6 +377,28 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable {
return 0L != mPartialConnectivityTime; return 0L != mPartialConnectivityTime;
} }
// Timestamp (SystemClock.elapsedRealTime()) at which the first validation attempt concluded,
// or timed out after {@link ConnectivityService#PROMPT_UNVALIDATED_DELAY_MS}. 0 if not yet.
private long mFirstEvaluationConcludedTime;
/**
* Notify this NAI that this network has been evaluated.
*
* The stack considers that any result finding some working connectivity (valid, partial,
* captive portal) is an initial validation. Negative result (not valid), however, is not
* considered initial validation until {@link ConnectivityService#PROMPT_UNVALIDATED_DELAY_MS}
* have elapsed. This is because some networks may spuriously fail for a short time immediately
* after associating. If no positive result is found after the timeout has elapsed, then
* the network has been evaluated once.
*
* @return true the first time this is called on this object, then always returns false.
*/
public boolean setEvaluated() {
if (0L != mFirstEvaluationConcludedTime) return false;
mFirstEvaluationConcludedTime = SystemClock.elapsedRealtime();
return true;
}
// Delay between when the network is disconnected and when the native network is destroyed. // Delay between when the network is disconnected and when the native network is destroyed.
public int teardownDelayMs; public int teardownDelayMs;
@@ -975,7 +997,8 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable {
final NetworkCapabilities oldNc = networkCapabilities; final NetworkCapabilities oldNc = networkCapabilities;
networkCapabilities = nc; networkCapabilities = nc;
mScore = mScore.mixInScore(networkCapabilities, networkAgentConfig, everValidated(), mScore = mScore.mixInScore(networkCapabilities, networkAgentConfig, everValidated(),
0L != getAvoidUnvalidated(), yieldToBadWiFi(), isDestroyed()); 0L != getAvoidUnvalidated(), yieldToBadWiFi(),
0L != mFirstEvaluationConcludedTime, isDestroyed());
final NetworkMonitorManager nm = mNetworkMonitor; final NetworkMonitorManager nm = mNetworkMonitor;
if (nm != null) { if (nm != null) {
nm.notifyNetworkCapabilitiesChanged(nc); nm.notifyNetworkCapabilitiesChanged(nc);
@@ -1178,7 +1201,8 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable {
*/ */
public void setScore(final NetworkScore score) { public void setScore(final NetworkScore score) {
mScore = FullScore.fromNetworkScore(score, networkCapabilities, networkAgentConfig, mScore = FullScore.fromNetworkScore(score, networkCapabilities, networkAgentConfig,
everValidated(), 0L == getAvoidUnvalidated(), yieldToBadWiFi(), isDestroyed()); everValidated(), 0L == getAvoidUnvalidated(), yieldToBadWiFi(),
0L != mFirstEvaluationConcludedTime, isDestroyed());
} }
/** /**
@@ -1188,7 +1212,8 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable {
*/ */
public void updateScoreForNetworkAgentUpdate() { public void updateScoreForNetworkAgentUpdate() {
mScore = mScore.mixInScore(networkCapabilities, networkAgentConfig, mScore = mScore.mixInScore(networkCapabilities, networkAgentConfig,
everValidated(), 0L != getAvoidUnvalidated(), yieldToBadWiFi(), isDestroyed()); everValidated(), 0L != getAvoidUnvalidated(), yieldToBadWiFi(),
0L != mFirstEvaluationConcludedTime, isDestroyed());
} }
/** /**

View File

@@ -29,6 +29,7 @@ import androidx.test.filters.SmallTest
import com.android.server.connectivity.FullScore.MAX_CS_MANAGED_POLICY import com.android.server.connectivity.FullScore.MAX_CS_MANAGED_POLICY
import com.android.server.connectivity.FullScore.MIN_CS_MANAGED_POLICY import com.android.server.connectivity.FullScore.MIN_CS_MANAGED_POLICY
import com.android.server.connectivity.FullScore.POLICY_ACCEPT_UNVALIDATED import com.android.server.connectivity.FullScore.POLICY_ACCEPT_UNVALIDATED
import com.android.server.connectivity.FullScore.POLICY_EVER_EVALUATED
import com.android.server.connectivity.FullScore.POLICY_EVER_USER_SELECTED import com.android.server.connectivity.FullScore.POLICY_EVER_USER_SELECTED
import com.android.server.connectivity.FullScore.POLICY_IS_DESTROYED import com.android.server.connectivity.FullScore.POLICY_IS_DESTROYED
import com.android.server.connectivity.FullScore.POLICY_IS_UNMETERED import com.android.server.connectivity.FullScore.POLICY_IS_UNMETERED
@@ -56,6 +57,7 @@ class FullScoreTest {
vpn: Boolean = false, vpn: Boolean = false,
onceChosen: Boolean = false, onceChosen: Boolean = false,
acceptUnvalidated: Boolean = false, acceptUnvalidated: Boolean = false,
everEvaluated: Boolean = true,
destroyed: Boolean = false destroyed: Boolean = false
): FullScore { ): FullScore {
val nac = NetworkAgentConfig.Builder().apply { val nac = NetworkAgentConfig.Builder().apply {
@@ -67,7 +69,7 @@ class FullScoreTest {
if (validated) addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) if (validated) addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
}.build() }.build()
return mixInScore(nc, nac, validated, false /* avoidUnvalidated */, return mixInScore(nc, nac, validated, false /* avoidUnvalidated */,
false /* yieldToBadWifi */, destroyed) false /* yieldToBadWifi */, everEvaluated, destroyed)
} }
private val TAG = this::class.simpleName private val TAG = this::class.simpleName
@@ -123,6 +125,7 @@ class FullScoreTest {
assertTrue(ns.withPolicies(onceChosen = true).hasPolicy(POLICY_EVER_USER_SELECTED)) assertTrue(ns.withPolicies(onceChosen = true).hasPolicy(POLICY_EVER_USER_SELECTED))
assertTrue(ns.withPolicies(acceptUnvalidated = true).hasPolicy(POLICY_ACCEPT_UNVALIDATED)) assertTrue(ns.withPolicies(acceptUnvalidated = true).hasPolicy(POLICY_ACCEPT_UNVALIDATED))
assertTrue(ns.withPolicies(destroyed = true).hasPolicy(POLICY_IS_DESTROYED)) assertTrue(ns.withPolicies(destroyed = true).hasPolicy(POLICY_IS_DESTROYED))
assertTrue(ns.withPolicies(everEvaluated = true).hasPolicy(POLICY_EVER_EVALUATED))
} }
@Test @Test