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