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 android.annotation.IntDef; 27 import android.annotation.NonNull; 28 import android.net.NetworkAgentConfig; 29 import android.net.NetworkCapabilities; 30 import android.net.NetworkScore; 31 import android.net.NetworkScore.KeepConnectedReason; 32 import android.util.Log; 33 import android.util.SparseArray; 34 35 import com.android.internal.annotations.VisibleForTesting; 36 import com.android.internal.util.MessageUtils; 37 38 import java.lang.annotation.Retention; 39 import java.lang.annotation.RetentionPolicy; 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 // This will be removed soon. Do *NOT* depend on it for any new code that is not part of 53 // a migration. 54 private final int mLegacyInt; 55 56 /** @hide */ 57 @Retention(RetentionPolicy.SOURCE) 58 @IntDef(prefix = {"POLICY_"}, value = { 59 POLICY_IS_VALIDATED, 60 POLICY_IS_VPN, 61 POLICY_EVER_USER_SELECTED, 62 POLICY_ACCEPT_UNVALIDATED, 63 POLICY_IS_UNMETERED 64 }) 65 public @interface Policy { 66 } 67 68 // Agent-managed policies are in NetworkScore. They start from 1. 69 // CS-managed policies, counting from 63 downward 70 // This network is validated. CS-managed because the source of truth is in NetworkCapabilities. 71 /** @hide */ 72 public static final int POLICY_IS_VALIDATED = 63; 73 74 // This is a VPN and behaves as one for scoring purposes. 75 /** @hide */ 76 public static final int POLICY_IS_VPN = 62; 77 78 // This network has been selected by the user manually from settings or a 3rd party app 79 // at least once. {@see NetworkAgentConfig#explicitlySelected}. 80 /** @hide */ 81 public static final int POLICY_EVER_USER_SELECTED = 61; 82 83 // The user has indicated in UI that this network should be used even if it doesn't 84 // validate. {@see NetworkAgentConfig#acceptUnvalidated}. 85 /** @hide */ 86 public static final int POLICY_ACCEPT_UNVALIDATED = 60; 87 88 // This network is unmetered. {@see NetworkCapabilities.NET_CAPABILITY_NOT_METERED}. 89 /** @hide */ 90 public static final int POLICY_IS_UNMETERED = 59; 91 92 // This network is invincible. This is useful for offers until there is an API to listen 93 // to requests. 94 /** @hide */ 95 public static final int POLICY_IS_INVINCIBLE = 58; 96 97 // This network has been validated at least once since it was connected, but not explicitly 98 // avoided in UI. 99 // TODO : remove setAvoidUnvalidated and instead disconnect the network when the user 100 // chooses to move away from this network, and remove this flag. 101 /** @hide */ 102 public static final int POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD = 57; 103 104 // The network agent has communicated that this network no longer functions, and the underlying 105 // native network has been destroyed. The network will still be reported to clients as connected 106 // until a timeout expires, the agent disconnects, or the network no longer satisfies requests. 107 // This network should lose to an identical network that has not been destroyed, but should 108 // otherwise be scored exactly the same. 109 /** @hide */ 110 public static final int POLICY_IS_DESTROYED = 56; 111 112 // To help iterate when printing 113 @VisibleForTesting 114 static final int MIN_CS_MANAGED_POLICY = POLICY_IS_DESTROYED; 115 @VisibleForTesting 116 static final int MAX_CS_MANAGED_POLICY = POLICY_IS_VALIDATED; 117 118 // Mask for policies in NetworkScore. This should have all bits managed by NetworkScore set 119 // and all bits managed by FullScore unset. As bits are handled from 0 up in NetworkScore and 120 // from 63 down in FullScore, cut at the 32nd bit for simplicity, but change this if some day 121 // there are more than 32 bits handled on either side. 122 // YIELD_TO_BAD_WIFI is temporarily handled by ConnectivityService. 123 private static final long EXTERNAL_POLICIES_MASK = 124 0x00000000FFFFFFFFL & ~(1L << POLICY_YIELD_TO_BAD_WIFI); 125 126 private static SparseArray<String> sMessageNames = MessageUtils.findMessageNames( 127 new Class[]{FullScore.class, NetworkScore.class}, new String[]{"POLICY_"}); 128 129 @VisibleForTesting policyNameOf(final int policy)130 static @NonNull String policyNameOf(final int policy) { 131 final String name = sMessageNames.get(policy); 132 if (name == null) { 133 // Don't throw here because name might be null due to proguard stripping out the 134 // POLICY_* constants, potentially causing a crash only on user builds because proguard 135 // does not run on userdebug builds. 136 // TODO: make MessageUtils safer by not returning the array and instead storing it 137 // internally and providing a getter (that does not throw) for individual values. 138 Log.wtf(TAG, "Unknown policy: " + policy); 139 return Integer.toString(policy); 140 } 141 return name.substring("POLICY_".length()); 142 } 143 144 // Bitmask of all the policies applied to this score. 145 private final long mPolicies; 146 147 private final int mKeepConnectedReason; 148 FullScore(final int legacyInt, final long policies, @KeepConnectedReason final int keepConnectedReason)149 FullScore(final int legacyInt, final long policies, 150 @KeepConnectedReason final int keepConnectedReason) { 151 mLegacyInt = legacyInt; 152 mPolicies = policies; 153 mKeepConnectedReason = keepConnectedReason; 154 } 155 156 /** 157 * Given a score supplied by the NetworkAgent and CS-managed objects, produce a full score. 158 * 159 * @param score the score supplied by the agent 160 * @param caps the NetworkCapabilities of the network 161 * @param config the NetworkAgentConfig of the network 162 * @param everValidated whether this network has ever validated 163 * @param yieldToBadWiFi whether this network yields to a previously validated wifi gone bad 164 * @param destroyed whether this network has been destroyed pending a replacement connecting 165 * @return a FullScore that is appropriate to use for ranking. 166 */ 167 // TODO : this shouldn't manage bad wifi avoidance – instead this should be done by the 168 // telephony factory, so that it depends on the carrier. For now this is handled by 169 // connectivity for backward compatibility. fromNetworkScore(@onNull final NetworkScore score, @NonNull final NetworkCapabilities caps, @NonNull final NetworkAgentConfig config, final boolean everValidated, final boolean yieldToBadWiFi, final boolean destroyed)170 public static FullScore fromNetworkScore(@NonNull final NetworkScore score, 171 @NonNull final NetworkCapabilities caps, @NonNull final NetworkAgentConfig config, 172 final boolean everValidated, final boolean yieldToBadWiFi, final boolean destroyed) { 173 return withPolicies(score.getLegacyInt(), score.getPolicies(), 174 score.getKeepConnectedReason(), 175 caps.hasCapability(NET_CAPABILITY_VALIDATED), 176 caps.hasTransport(TRANSPORT_VPN), 177 caps.hasCapability(NET_CAPABILITY_NOT_METERED), 178 everValidated, 179 config.explicitlySelected, 180 config.acceptUnvalidated, 181 yieldToBadWiFi, 182 destroyed, 183 false /* invincible */); // only prospective scores can be invincible 184 } 185 186 /** 187 * Given a score supplied by a NetworkProvider, produce a prospective score for an offer. 188 * 189 * NetworkOffers have score filters that are compared to the scores of actual networks 190 * to see if they could possibly beat the current satisfier. Some things the agent can't 191 * know in advance; a good example is the validation bit – some networks will validate, 192 * others won't. For comparison purposes, assume the best, so all possibly beneficial 193 * networks will be brought up. 194 * 195 * @param score the score supplied by the agent for this offer 196 * @param caps the capabilities supplied by the agent for this offer 197 * @return a FullScore appropriate for comparing to actual network's scores. 198 */ makeProspectiveScore(@onNull final NetworkScore score, @NonNull final NetworkCapabilities caps, final boolean yieldToBadWiFi)199 public static FullScore makeProspectiveScore(@NonNull final NetworkScore score, 200 @NonNull final NetworkCapabilities caps, final boolean yieldToBadWiFi) { 201 // If the network offers Internet access, it may validate. 202 final boolean mayValidate = caps.hasCapability(NET_CAPABILITY_INTERNET); 203 // VPN transports are known in advance. 204 final boolean vpn = caps.hasTransport(TRANSPORT_VPN); 205 // Prospective scores are always unmetered, because unmetered networks are stronger 206 // than metered networks, and it's not known in advance whether the network is metered. 207 final boolean unmetered = true; 208 // If the offer may validate, then it should be considered to have validated at some point 209 final boolean everValidated = mayValidate; 210 // The network hasn't been chosen by the user (yet, at least). 211 final boolean everUserSelected = false; 212 // Don't assume the user will accept unvalidated connectivity. 213 final boolean acceptUnvalidated = false; 214 // A network can only be destroyed once it has connected. 215 final boolean destroyed = false; 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 return withPolicies(score.getLegacyInt(), score.getPolicies(), KEEP_CONNECTED_NONE, 220 mayValidate, vpn, unmetered, everValidated, everUserSelected, acceptUnvalidated, 221 yieldToBadWiFi, destroyed, invincible); 222 } 223 224 /** 225 * Return a new score given updated caps and config. 226 * 227 * @param caps the NetworkCapabilities of the network 228 * @param config the NetworkAgentConfig of the network 229 * @return a score with the policies from the arguments reset 230 */ 231 // TODO : this shouldn't manage bad wifi avoidance – instead this should be done by the 232 // telephony factory, so that it depends on the carrier. For now this is handled by 233 // connectivity for backward compatibility. mixInScore(@onNull final NetworkCapabilities caps, @NonNull final NetworkAgentConfig config, final boolean everValidated, final boolean yieldToBadWifi, final boolean destroyed)234 public FullScore mixInScore(@NonNull final NetworkCapabilities caps, 235 @NonNull final NetworkAgentConfig config, 236 final boolean everValidated, 237 final boolean yieldToBadWifi, 238 final boolean destroyed) { 239 return withPolicies(mLegacyInt, mPolicies, mKeepConnectedReason, 240 caps.hasCapability(NET_CAPABILITY_VALIDATED), 241 caps.hasTransport(TRANSPORT_VPN), 242 caps.hasCapability(NET_CAPABILITY_NOT_METERED), 243 everValidated, 244 config.explicitlySelected, 245 config.acceptUnvalidated, 246 yieldToBadWifi, 247 destroyed, 248 false /* invincible */); // only prospective scores can be invincible 249 } 250 251 // TODO : this shouldn't manage bad wifi avoidance – instead this should be done by the 252 // telephony factory, so that it depends on the carrier. For now this is handled by 253 // connectivity for backward compatibility. withPolicies(@onNull final int legacyInt, final long externalPolicies, @KeepConnectedReason final int keepConnectedReason, final boolean isValidated, final boolean isVpn, final boolean isUnmetered, final boolean everValidated, final boolean everUserSelected, final boolean acceptUnvalidated, final boolean yieldToBadWiFi, final boolean destroyed, final boolean invincible)254 private static FullScore withPolicies(@NonNull final int legacyInt, 255 final long externalPolicies, 256 @KeepConnectedReason final int keepConnectedReason, 257 final boolean isValidated, 258 final boolean isVpn, 259 final boolean isUnmetered, 260 final boolean everValidated, 261 final boolean everUserSelected, 262 final boolean acceptUnvalidated, 263 final boolean yieldToBadWiFi, 264 final boolean destroyed, 265 final boolean invincible) { 266 return new FullScore(legacyInt, (externalPolicies & EXTERNAL_POLICIES_MASK) 267 | (isValidated ? 1L << POLICY_IS_VALIDATED : 0) 268 | (isVpn ? 1L << POLICY_IS_VPN : 0) 269 | (isUnmetered ? 1L << POLICY_IS_UNMETERED : 0) 270 | (everValidated ? 1L << POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD : 0) 271 | (everUserSelected ? 1L << POLICY_EVER_USER_SELECTED : 0) 272 | (acceptUnvalidated ? 1L << POLICY_ACCEPT_UNVALIDATED : 0) 273 | (yieldToBadWiFi ? 1L << POLICY_YIELD_TO_BAD_WIFI : 0) 274 | (destroyed ? 1L << POLICY_IS_DESTROYED : 0) 275 | (invincible ? 1L << POLICY_IS_INVINCIBLE : 0), 276 keepConnectedReason); 277 } 278 279 /** 280 * Returns this score but with the specified yield to bad wifi policy. 281 */ withYieldToBadWiFi(final boolean newYield)282 public FullScore withYieldToBadWiFi(final boolean newYield) { 283 return new FullScore(mLegacyInt, 284 newYield ? mPolicies | (1L << POLICY_YIELD_TO_BAD_WIFI) 285 : mPolicies & ~(1L << POLICY_YIELD_TO_BAD_WIFI), 286 mKeepConnectedReason); 287 } 288 289 /** 290 * Returns this score but validated. 291 */ asValidated()292 public FullScore asValidated() { 293 return new FullScore(mLegacyInt, mPolicies | (1L << POLICY_IS_VALIDATED), 294 mKeepConnectedReason); 295 } 296 297 /** 298 * For backward compatibility, get the legacy int. 299 * This will be removed before S is published. 300 */ getLegacyInt()301 public int getLegacyInt() { 302 return getLegacyInt(false /* pretendValidated */); 303 } 304 getLegacyIntAsValidated()305 public int getLegacyIntAsValidated() { 306 return getLegacyInt(true /* pretendValidated */); 307 } 308 309 // TODO : remove these two constants 310 // Penalty applied to scores of Networks that have not been validated. 311 private static final int UNVALIDATED_SCORE_PENALTY = 40; 312 313 // Score for a network that can be used unvalidated 314 private static final int ACCEPT_UNVALIDATED_NETWORK_SCORE = 100; 315 getLegacyInt(boolean pretendValidated)316 private int getLegacyInt(boolean pretendValidated) { 317 // If the user has chosen this network at least once, give it the maximum score when 318 // checking to pretend it's validated, or if it doesn't need to validate because the 319 // user said to use it even if it doesn't validate. 320 // This ensures that networks that have been selected in UI are not torn down before the 321 // user gets a chance to prefer it when a higher-scoring network (e.g., Ethernet) is 322 // available. 323 if (hasPolicy(POLICY_EVER_USER_SELECTED) 324 && (hasPolicy(POLICY_ACCEPT_UNVALIDATED) || pretendValidated)) { 325 return ACCEPT_UNVALIDATED_NETWORK_SCORE; 326 } 327 328 int score = mLegacyInt; 329 // Except for VPNs, networks are subject to a penalty for not being validated. 330 // Apply the penalty unless the network is a VPN, or it's validated or pretending to be. 331 if (!hasPolicy(POLICY_IS_VALIDATED) && !pretendValidated && !hasPolicy(POLICY_IS_VPN)) { 332 score -= UNVALIDATED_SCORE_PENALTY; 333 } 334 if (score < 0) score = 0; 335 return score; 336 } 337 338 /** 339 * @return whether this score has a particular policy. 340 */ 341 @VisibleForTesting hasPolicy(final int policy)342 public boolean hasPolicy(final int policy) { 343 return 0 != (mPolicies & (1L << policy)); 344 } 345 346 /** 347 * Returns the keep-connected reason, or KEEP_CONNECTED_NONE. 348 */ getKeepConnectedReason()349 public int getKeepConnectedReason() { 350 return mKeepConnectedReason; 351 } 352 353 // Example output : 354 // Score(50 ; Policies : EVER_USER_SELECTED&IS_VALIDATED) 355 @Override toString()356 public String toString() { 357 final StringJoiner sj = new StringJoiner( 358 "&", // delimiter 359 "Score(" + mLegacyInt + " ; KeepConnected : " + mKeepConnectedReason 360 + " ; Policies : ", // prefix 361 ")"); // suffix 362 for (int i = NetworkScore.MIN_AGENT_MANAGED_POLICY; 363 i <= NetworkScore.MAX_AGENT_MANAGED_POLICY; ++i) { 364 if (hasPolicy(i)) sj.add(policyNameOf(i)); 365 } 366 for (int i = MIN_CS_MANAGED_POLICY; i <= MAX_CS_MANAGED_POLICY; ++i) { 367 if (hasPolicy(i)) sj.add(policyNameOf(i)); 368 } 369 return sj.toString(); 370 } 371 } 372