1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.connectivity; 18 19 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; 20 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; 21 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; 22 import static android.net.NetworkCapabilities.TRANSPORT_VPN; 23 import static android.net.NetworkScore.KEEP_CONNECTED_NONE; 24 import static android.net.NetworkScore.POLICY_YIELD_TO_BAD_WIFI; 25 26 import static com.android.net.module.util.BitUtils.describeDifferences; 27 28 import android.annotation.NonNull; 29 import android.annotation.Nullable; 30 import android.net.NetworkAgentConfig; 31 import android.net.NetworkCapabilities; 32 import android.net.NetworkScore; 33 import android.net.NetworkScore.KeepConnectedReason; 34 import android.util.Log; 35 import android.util.SparseArray; 36 37 import com.android.internal.annotations.VisibleForTesting; 38 import com.android.internal.util.MessageUtils; 39 40 import java.util.StringJoiner; 41 42 /** 43 * This class represents how desirable a network is. 44 * 45 * FullScore is very similar to NetworkScore, but it contains the bits that are managed 46 * by ConnectivityService. This provides static guarantee that all users must know whether 47 * they are handling a score that had the CS-managed bits set. 48 */ 49 public class FullScore { 50 private static final String TAG = FullScore.class.getSimpleName(); 51 52 // Agent-managed policies are in NetworkScore. They start from 1. 53 // CS-managed policies, counting from 63 downward 54 // This network is validated. CS-managed because the source of truth is in NetworkCapabilities. 55 /** @hide */ 56 public static final int POLICY_IS_VALIDATED = 63; 57 58 // This network has been validated at least once since it was connected. 59 /** @hide */ 60 public static final int POLICY_EVER_VALIDATED = 62; 61 62 // This is a VPN and behaves as one for scoring purposes. 63 /** @hide */ 64 public static final int POLICY_IS_VPN = 61; 65 66 // This network has been selected by the user manually from settings or a 3rd party app 67 // at least once. @see NetworkAgentConfig#explicitlySelected. 68 /** @hide */ 69 public static final int POLICY_EVER_USER_SELECTED = 60; 70 71 // The user has indicated in UI that this network should be used even if it doesn't 72 // validate. @see NetworkAgentConfig#acceptUnvalidated. 73 /** @hide */ 74 public static final int POLICY_ACCEPT_UNVALIDATED = 59; 75 76 // The user explicitly said in UI to avoid this network when unvalidated. 77 // TODO : remove setAvoidUnvalidated and instead disconnect the network when the user 78 // chooses to move away from this network, and remove this flag. 79 /** @hide */ 80 public static final int POLICY_AVOIDED_WHEN_UNVALIDATED = 58; 81 82 // This network is unmetered. @see NetworkCapabilities.NET_CAPABILITY_NOT_METERED. 83 /** @hide */ 84 public static final int POLICY_IS_UNMETERED = 57; 85 86 // This network is invincible. This is useful for offers until there is an API to listen 87 // to requests. 88 /** @hide */ 89 public static final int POLICY_IS_INVINCIBLE = 56; 90 91 // This network has undergone initial validation. 92 // 93 // The stack considers that any result finding some working connectivity (valid, partial, 94 // captive portal) is an initial validation. Negative result (not valid), however, is not 95 // considered initial validation until {@link ConnectivityService#PROMPT_UNVALIDATED_DELAY_MS} 96 // have elapsed. This is because some networks may spuriously fail for a short time immediately 97 // after associating. If no positive result is found after the timeout has elapsed, then 98 // the network has been evaluated once. 99 public static final int POLICY_EVER_EVALUATED = 55; 100 101 // The network agent has communicated that this network no longer functions, and the underlying 102 // native network has been destroyed. The network will still be reported to clients as connected 103 // until a timeout expires, the agent disconnects, or the network no longer satisfies requests. 104 // This network should lose to an identical network that has not been destroyed, but should 105 // otherwise be scored exactly the same. 106 /** @hide */ 107 public static final int POLICY_IS_DESTROYED = 54; 108 109 // To help iterate when printing 110 @VisibleForTesting 111 static final int MIN_CS_MANAGED_POLICY = POLICY_IS_DESTROYED; 112 @VisibleForTesting 113 static final int MAX_CS_MANAGED_POLICY = POLICY_IS_VALIDATED; 114 115 // Mask for policies in NetworkScore. This should have all bits managed by NetworkScore set 116 // and all bits managed by FullScore unset. As bits are handled from 0 up in NetworkScore and 117 // from 63 down in FullScore, cut at the 32nd bit for simplicity, but change this if some day 118 // there are more than 32 bits handled on either side. 119 // YIELD_TO_BAD_WIFI is temporarily handled by ConnectivityService. 120 private static final long EXTERNAL_POLICIES_MASK = 121 0x00000000FFFFFFFFL & ~(1L << POLICY_YIELD_TO_BAD_WIFI); 122 123 private static SparseArray<String> sMessageNames = MessageUtils.findMessageNames( 124 new Class[]{FullScore.class, NetworkScore.class}, new String[]{"POLICY_"}); 125 126 @VisibleForTesting policyNameOf(final int policy)127 public static @NonNull String policyNameOf(final int policy) { 128 final String name = sMessageNames.get(policy); 129 if (name == null) { 130 // Don't throw here because name might be null due to proguard stripping out the 131 // POLICY_* constants, potentially causing a crash only on user builds because proguard 132 // does not run on userdebug builds. 133 // TODO: make MessageUtils safer by not returning the array and instead storing it 134 // internally and providing a getter (that does not throw) for individual values. 135 Log.wtf(TAG, "Unknown policy: " + policy); 136 return Integer.toString(policy); 137 } 138 return name.substring("POLICY_".length()); 139 } 140 141 // Bitmask of all the policies applied to this score. 142 private final long mPolicies; 143 144 private final int mKeepConnectedReason; 145 FullScore(final long policies, @KeepConnectedReason final int keepConnectedReason)146 FullScore(final long policies, @KeepConnectedReason final int keepConnectedReason) { 147 mPolicies = policies; 148 mKeepConnectedReason = keepConnectedReason; 149 } 150 151 /** 152 * Given a score supplied by the NetworkAgent and CS-managed objects, produce a full score. 153 * 154 * @param score the score supplied by the agent 155 * @param caps the NetworkCapabilities of the network 156 * @param config the NetworkAgentConfig of the network 157 * @param everValidated whether this network has ever validated 158 * @param avoidUnvalidated whether the user said in UI to avoid this network when unvalidated 159 * @param yieldToBadWiFi whether this network yields to a previously validated wifi gone bad 160 * @param everEvaluated whether this network ever evaluated at least once 161 * @param destroyed whether this network has been destroyed pending a replacement connecting 162 * @return a FullScore that is appropriate to use for ranking. 163 */ 164 // TODO : this shouldn't manage bad wifi avoidance – instead this should be done by the 165 // telephony factory, so that it depends on the carrier. For now this is handled by 166 // connectivity for backward compatibility. fromNetworkScore(@onNull final NetworkScore score, @NonNull final NetworkCapabilities caps, @NonNull final NetworkAgentConfig config, final boolean everValidated, final boolean avoidUnvalidated, final boolean yieldToBadWiFi, final boolean everEvaluated, final boolean destroyed)167 public static FullScore fromNetworkScore(@NonNull final NetworkScore score, 168 @NonNull final NetworkCapabilities caps, @NonNull final NetworkAgentConfig config, 169 final boolean everValidated, final boolean avoidUnvalidated, 170 final boolean yieldToBadWiFi, final boolean everEvaluated, final boolean destroyed) { 171 return withPolicies(score.getPolicies(), 172 score.getKeepConnectedReason(), 173 caps.hasCapability(NET_CAPABILITY_VALIDATED), 174 everValidated, caps.hasTransport(TRANSPORT_VPN), 175 config.explicitlySelected, 176 config.acceptUnvalidated, 177 avoidUnvalidated, 178 caps.hasCapability(NET_CAPABILITY_NOT_METERED), 179 yieldToBadWiFi, 180 false /* invincible */, // only prospective scores can be invincible 181 everEvaluated, 182 destroyed); 183 } 184 185 /** 186 * Given a score supplied by a NetworkProvider, produce a prospective score for an offer. 187 * 188 * NetworkOffers have score filters that are compared to the scores of actual networks 189 * to see if they could possibly beat the current satisfier. Some things the agent can't 190 * know in advance; a good example is the validation bit – some networks will validate, 191 * others won't. For comparison purposes, assume the best, so all possibly beneficial 192 * networks will be brought up. 193 * 194 * @param score the score supplied by the agent for this offer 195 * @param caps the capabilities supplied by the agent for this offer 196 * @return a FullScore appropriate for comparing to actual network's scores. 197 */ makeProspectiveScore(@onNull final NetworkScore score, @NonNull final NetworkCapabilities caps, final boolean yieldToBadWiFi)198 public static FullScore makeProspectiveScore(@NonNull final NetworkScore score, 199 @NonNull final NetworkCapabilities caps, final boolean yieldToBadWiFi) { 200 // If the network offers Internet access, it may validate. 201 final boolean mayValidate = caps.hasCapability(NET_CAPABILITY_INTERNET); 202 // If the offer may validate, then it should be considered to have validated at some point 203 final boolean everValidated = mayValidate; 204 // VPN transports are known in advance. 205 final boolean vpn = caps.hasTransport(TRANSPORT_VPN); 206 // The network hasn't been chosen by the user (yet, at least). 207 final boolean everUserSelected = false; 208 // Don't assume the user will accept unvalidated connectivity. 209 final boolean acceptUnvalidated = false; 210 // A prospective network is never avoided when unvalidated, because the user has never 211 // had the opportunity to say so in UI. 212 final boolean avoidUnvalidated = false; 213 // Prospective scores are always unmetered, because unmetered networks are stronger 214 // than metered networks, and it's not known in advance whether the network is metered. 215 final boolean unmetered = true; 216 // A prospective score is invincible if the legacy int in the filter is over the maximum 217 // score. 218 final boolean invincible = score.getLegacyInt() > NetworkRanker.LEGACY_INT_MAX; 219 // A prospective network will eventually be evaluated. 220 final boolean everEvaluated = true; 221 // A network can only be destroyed once it has connected. 222 final boolean destroyed = false; 223 return withPolicies(score.getPolicies(), KEEP_CONNECTED_NONE, 224 mayValidate, everValidated, vpn, everUserSelected, 225 acceptUnvalidated, avoidUnvalidated, unmetered, yieldToBadWiFi, 226 invincible, everEvaluated, destroyed); 227 } 228 229 /** 230 * Return a new score given updated caps and config. 231 * 232 * @param caps the NetworkCapabilities of the network 233 * @param config the NetworkAgentConfig of the network 234 * @return a score with the policies from the arguments reset 235 */ 236 // TODO : this shouldn't manage bad wifi avoidance – instead this should be done by the 237 // telephony factory, so that it depends on the carrier. For now this is handled by 238 // connectivity for backward compatibility. mixInScore(@onNull final NetworkCapabilities caps, @NonNull final NetworkAgentConfig config, final boolean everValidated, final boolean avoidUnvalidated, final boolean yieldToBadWifi, final boolean everEvaluated, final boolean destroyed)239 public FullScore mixInScore(@NonNull final NetworkCapabilities caps, 240 @NonNull final NetworkAgentConfig config, 241 final boolean everValidated, 242 final boolean avoidUnvalidated, 243 final boolean yieldToBadWifi, 244 final boolean everEvaluated, 245 final boolean destroyed) { 246 return withPolicies(mPolicies, mKeepConnectedReason, 247 caps.hasCapability(NET_CAPABILITY_VALIDATED), 248 everValidated, caps.hasTransport(TRANSPORT_VPN), 249 config.explicitlySelected, 250 config.acceptUnvalidated, 251 avoidUnvalidated, 252 caps.hasCapability(NET_CAPABILITY_NOT_METERED), 253 yieldToBadWifi, 254 false /* invincible */, // only prospective scores can be invincible 255 everEvaluated, 256 destroyed); 257 } 258 259 // TODO : this shouldn't manage bad wifi avoidance – instead this should be done by the 260 // telephony factory, so that it depends on the carrier. For now this is handled by 261 // connectivity for backward compatibility. withPolicies(final long externalPolicies, @KeepConnectedReason final int keepConnectedReason, final boolean isValidated, final boolean everValidated, final boolean isVpn, final boolean everUserSelected, final boolean acceptUnvalidated, final boolean avoidUnvalidated, final boolean isUnmetered, final boolean yieldToBadWiFi, final boolean invincible, final boolean everEvaluated, final boolean destroyed)262 private static FullScore withPolicies(final long externalPolicies, 263 @KeepConnectedReason final int keepConnectedReason, 264 final boolean isValidated, 265 final boolean everValidated, 266 final boolean isVpn, 267 final boolean everUserSelected, 268 final boolean acceptUnvalidated, 269 final boolean avoidUnvalidated, 270 final boolean isUnmetered, 271 final boolean yieldToBadWiFi, 272 final boolean invincible, 273 final boolean everEvaluated, 274 final boolean destroyed) { 275 return new FullScore((externalPolicies & EXTERNAL_POLICIES_MASK) 276 | (isValidated ? 1L << POLICY_IS_VALIDATED : 0) 277 | (everValidated ? 1L << POLICY_EVER_VALIDATED : 0) 278 | (isVpn ? 1L << POLICY_IS_VPN : 0) 279 | (everUserSelected ? 1L << POLICY_EVER_USER_SELECTED : 0) 280 | (acceptUnvalidated ? 1L << POLICY_ACCEPT_UNVALIDATED : 0) 281 | (avoidUnvalidated ? 1L << POLICY_AVOIDED_WHEN_UNVALIDATED : 0) 282 | (isUnmetered ? 1L << POLICY_IS_UNMETERED : 0) 283 | (yieldToBadWiFi ? 1L << POLICY_YIELD_TO_BAD_WIFI : 0) 284 | (invincible ? 1L << POLICY_IS_INVINCIBLE : 0) 285 | (everEvaluated ? 1L << POLICY_EVER_EVALUATED : 0) 286 | (destroyed ? 1L << POLICY_IS_DESTROYED : 0), 287 keepConnectedReason); 288 } 289 290 /** 291 * Returns this score but with the specified yield to bad wifi policy. 292 */ withYieldToBadWiFi(final boolean newYield)293 public FullScore withYieldToBadWiFi(final boolean newYield) { 294 return new FullScore(newYield ? mPolicies | (1L << POLICY_YIELD_TO_BAD_WIFI) 295 : mPolicies & ~(1L << POLICY_YIELD_TO_BAD_WIFI), 296 mKeepConnectedReason); 297 } 298 299 /** 300 * Returns this score but validated. 301 */ asValidated()302 public FullScore asValidated() { 303 return new FullScore(mPolicies | (1L << POLICY_IS_VALIDATED), mKeepConnectedReason); 304 } 305 306 /** 307 * Gets the policies as an long. Internal callers only. 308 * 309 * DO NOT USE if not immediately collapsing back into a scalar. Instead, use 310 * {@link #hasPolicy}. 311 * @return the internal, version-dependent int representing the policies. 312 * @hide 313 */ getPoliciesInternal()314 public long getPoliciesInternal() { 315 return mPolicies; 316 } 317 318 /** 319 * @return whether this score has a particular policy. 320 */ 321 @VisibleForTesting hasPolicy(final int policy)322 public boolean hasPolicy(final int policy) { 323 return 0 != (mPolicies & (1L << policy)); 324 } 325 326 /** 327 * Returns the keep-connected reason, or KEEP_CONNECTED_NONE. 328 */ getKeepConnectedReason()329 public int getKeepConnectedReason() { 330 return mKeepConnectedReason; 331 } 332 333 @Override equals(final Object o)334 public boolean equals(final Object o) { 335 if (this == o) return true; 336 if (o == null || getClass() != o.getClass()) return false; 337 338 final FullScore fullScore = (FullScore) o; 339 340 if (mPolicies != fullScore.mPolicies) return false; 341 return mKeepConnectedReason == fullScore.mKeepConnectedReason; 342 } 343 344 @Override hashCode()345 public int hashCode() { 346 return 2 * ((int) mPolicies) 347 + 3 * (int) (mPolicies >>> 32) 348 + 5 * mKeepConnectedReason; 349 } 350 351 /** 352 * Returns a short but human-readable string of updates from an older score. 353 * @param old the old score to diff from 354 * @return a string fit for logging differences, or null if no differences. 355 * this method cannot return the empty string. See BitUtils#describeDifferences. 356 */ 357 @Nullable describeDifferencesFrom(@ullable final FullScore old)358 public String describeDifferencesFrom(@Nullable final FullScore old) { 359 final long oldPolicies = null == old ? 0 : old.mPolicies; 360 return describeDifferences(oldPolicies, mPolicies, FullScore::policyNameOf); 361 } 362 363 // Example output : 364 // Score(Policies : EVER_USER_SELECTED&IS_VALIDATED ; KeepConnected : ) 365 @Override toString()366 public String toString() { 367 final StringJoiner sj = new StringJoiner( 368 "&", // delimiter 369 "Score(Policies : ", // prefix 370 " ; KeepConnected : " + mKeepConnectedReason + ")"); // suffix 371 for (int i = NetworkScore.MIN_AGENT_MANAGED_POLICY; 372 i <= NetworkScore.MAX_AGENT_MANAGED_POLICY; ++i) { 373 if (hasPolicy(i)) sj.add(policyNameOf(i)); 374 } 375 for (int i = MIN_CS_MANAGED_POLICY; i <= MAX_CS_MANAGED_POLICY; ++i) { 376 if (hasPolicy(i)) sj.add(policyNameOf(i)); 377 } 378 return sj.toString(); 379 } 380 } 381