Home | History | Annotate | Download | only in connectivity
      1 /*
      2  * Copyright (C) 2014 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_VALIDATED;
     20 
     21 import android.content.Context;
     22 import android.net.LinkProperties;
     23 import android.net.Network;
     24 import android.net.NetworkCapabilities;
     25 import android.net.NetworkInfo;
     26 import android.net.NetworkMisc;
     27 import android.net.NetworkRequest;
     28 import android.net.NetworkState;
     29 import android.os.Handler;
     30 import android.os.Messenger;
     31 import android.os.SystemClock;
     32 import android.util.Log;
     33 import android.util.SparseArray;
     34 
     35 import com.android.internal.util.AsyncChannel;
     36 import com.android.internal.util.WakeupMessage;
     37 import com.android.server.ConnectivityService;
     38 import com.android.server.connectivity.NetworkMonitor;
     39 
     40 import java.io.PrintWriter;
     41 import java.util.ArrayList;
     42 import java.util.Comparator;
     43 import java.util.Objects;
     44 import java.util.SortedSet;
     45 import java.util.TreeSet;
     46 
     47 /**
     48  * A bag class used by ConnectivityService for holding a collection of most recent
     49  * information published by a particular NetworkAgent as well as the
     50  * AsyncChannel/messenger for reaching that NetworkAgent and lists of NetworkRequests
     51  * interested in using it.  Default sort order is descending by score.
     52  */
     53 // States of a network:
     54 // --------------------
     55 // 1. registered, uncreated, disconnected, unvalidated
     56 //    This state is entered when a NetworkFactory registers a NetworkAgent in any state except
     57 //    the CONNECTED state.
     58 // 2. registered, uncreated, connecting, unvalidated
     59 //    This state is entered when a registered NetworkAgent for a VPN network transitions to the
     60 //    CONNECTING state (TODO: go through this state for every network, not just VPNs).
     61 //    ConnectivityService will tell netd to create the network early in order to add extra UID
     62 //    routing rules referencing the netID. These rules need to be in place before the network is
     63 //    connected to avoid racing against client apps trying to connect to a half-setup network.
     64 // 3. registered, uncreated, connected, unvalidated
     65 //    This state is entered when a registered NetworkAgent transitions to the CONNECTED state.
     66 //    ConnectivityService will tell netd to create the network if it was not already created, and
     67 //    immediately transition to state #4.
     68 // 4. registered, created, connected, unvalidated
     69 //    If this network can satisfy the default NetworkRequest, then NetworkMonitor will
     70 //    probe for Internet connectivity.
     71 //    If this network cannot satisfy the default NetworkRequest, it will immediately be
     72 //    transitioned to state #5.
     73 //    A network may remain in this state if NetworkMonitor fails to find Internet connectivity,
     74 //    for example:
     75 //    a. a captive portal is present, or
     76 //    b. a WiFi router whose Internet backhaul is down, or
     77 //    c. a wireless connection stops transfering packets temporarily (e.g. device is in elevator
     78 //       or tunnel) but does not disconnect from the AP/cell tower, or
     79 //    d. a stand-alone device offering a WiFi AP without an uplink for configuration purposes.
     80 // 5. registered, created, connected, validated
     81 //
     82 // The device's default network connection:
     83 // ----------------------------------------
     84 // Networks in states #4 and #5 may be used as a device's default network connection if they
     85 // satisfy the default NetworkRequest.
     86 // A network, that satisfies the default NetworkRequest, in state #5 should always be chosen
     87 // in favor of a network, that satisfies the default NetworkRequest, in state #4.
     88 // When deciding between two networks, that both satisfy the default NetworkRequest, to select
     89 // for the default network connection, the one with the higher score should be chosen.
     90 //
     91 // When a network disconnects:
     92 // ---------------------------
     93 // If a network's transport disappears, for example:
     94 // a. WiFi turned off, or
     95 // b. cellular data turned off, or
     96 // c. airplane mode is turned on, or
     97 // d. a wireless connection disconnects from AP/cell tower entirely (e.g. device is out of range
     98 //    of AP for an extended period of time, or switches to another AP without roaming)
     99 // then that network can transition from any state (#1-#5) to unregistered.  This happens by
    100 // the transport disconnecting their NetworkAgent's AsyncChannel with ConnectivityManager.
    101 // ConnectivityService also tells netd to destroy the network.
    102 //
    103 // When ConnectivityService disconnects a network:
    104 // -----------------------------------------------
    105 // If a network has no chance of satisfying any requests (even if it were to become validated
    106 // and enter state #5), ConnectivityService will disconnect the NetworkAgent's AsyncChannel.
    107 //
    108 // If the network was satisfying a foreground NetworkRequest (i.e. had been the highest scoring that
    109 // satisfied the NetworkRequest's constraints), but is no longer the highest scoring network for any
    110 // foreground NetworkRequest, then there will be a 30s pause to allow network communication to be
    111 // wrapped up rather than abruptly terminated. During this pause the network is said to be
    112 // "lingering". During this pause if the network begins satisfying a foreground NetworkRequest,
    113 // ConnectivityService will cancel the future disconnection of the NetworkAgent's AsyncChannel, and
    114 // the network is no longer considered "lingering". After the linger timer expires, if the network
    115 // is satisfying one or more background NetworkRequests it is kept up in the background. If it is
    116 // not, ConnectivityService disconnects the NetworkAgent's AsyncChannel.
    117 public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> {
    118 
    119     public NetworkInfo networkInfo;
    120     // This Network object should always be used if possible, so as to encourage reuse of the
    121     // enclosed socket factory and connection pool.  Avoid creating other Network objects.
    122     // This Network object is always valid.
    123     public final Network network;
    124     public LinkProperties linkProperties;
    125     // This should only be modified via ConnectivityService.updateCapabilities().
    126     public NetworkCapabilities networkCapabilities;
    127     public final NetworkMonitor networkMonitor;
    128     public final NetworkMisc networkMisc;
    129     // Indicates if netd has been told to create this Network. From this point on the appropriate
    130     // routing rules are setup and routes are added so packets can begin flowing over the Network.
    131     // This is a sticky bit; once set it is never cleared.
    132     public boolean created;
    133     // Set to true after the first time this network is marked as CONNECTED. Once set, the network
    134     // shows up in API calls, is able to satisfy NetworkRequests and can become the default network.
    135     // This is a sticky bit; once set it is never cleared.
    136     public boolean everConnected;
    137     // Set to true if this Network successfully passed validation or if it did not satisfy the
    138     // default NetworkRequest in which case validation will not be attempted.
    139     // This is a sticky bit; once set it is never cleared even if future validation attempts fail.
    140     public boolean everValidated;
    141 
    142     // The result of the last validation attempt on this network (true if validated, false if not).
    143     public boolean lastValidated;
    144 
    145     // If true, becoming unvalidated will lower the network's score. This is only meaningful if the
    146     // system is configured not to do this for certain networks, e.g., if the
    147     // config_networkAvoidBadWifi option is set to 0 and the user has not overridden that via
    148     // Settings.Global.NETWORK_AVOID_BAD_WIFI.
    149     public boolean avoidUnvalidated;
    150 
    151     // Whether a captive portal was ever detected on this network.
    152     // This is a sticky bit; once set it is never cleared.
    153     public boolean everCaptivePortalDetected;
    154 
    155     // Whether a captive portal was found during the last network validation attempt.
    156     public boolean lastCaptivePortalDetected;
    157 
    158     // Networks are lingered when they become unneeded as a result of their NetworkRequests being
    159     // satisfied by a higher-scoring network. so as to allow communication to wrap up before the
    160     // network is taken down.  This usually only happens to the default network. Lingering ends with
    161     // either the linger timeout expiring and the network being taken down, or the network
    162     // satisfying a request again.
    163     public static class LingerTimer implements Comparable<LingerTimer> {
    164         public final NetworkRequest request;
    165         public final long expiryMs;
    166 
    167         public LingerTimer(NetworkRequest request, long expiryMs) {
    168             this.request = request;
    169             this.expiryMs = expiryMs;
    170         }
    171         public boolean equals(Object o) {
    172             if (!(o instanceof LingerTimer)) return false;
    173             LingerTimer other = (LingerTimer) o;
    174             return (request.requestId == other.request.requestId) && (expiryMs == other.expiryMs);
    175         }
    176         public int hashCode() {
    177             return Objects.hash(request.requestId, expiryMs);
    178         }
    179         public int compareTo(LingerTimer other) {
    180             return (expiryMs != other.expiryMs) ?
    181                     Long.compare(expiryMs, other.expiryMs) :
    182                     Integer.compare(request.requestId, other.request.requestId);
    183         }
    184         public String toString() {
    185             return String.format("%s, expires %dms", request.toString(),
    186                     expiryMs - SystemClock.elapsedRealtime());
    187         }
    188     }
    189 
    190     /**
    191      * Inform ConnectivityService that the network LINGER period has
    192      * expired.
    193      * obj = this NetworkAgentInfo
    194      */
    195     public static final int EVENT_NETWORK_LINGER_COMPLETE = 1001;
    196 
    197     // All linger timers for this network, sorted by expiry time. A linger timer is added whenever
    198     // a request is moved to a network with a better score, regardless of whether the network is or
    199     // was lingering or not.
    200     // TODO: determine if we can replace this with a smaller or unsorted data structure. (e.g.,
    201     // SparseLongArray) combined with the timestamp of when the last timer is scheduled to fire.
    202     private final SortedSet<LingerTimer> mLingerTimers = new TreeSet<>();
    203 
    204     // For fast lookups. Indexes into mLingerTimers by request ID.
    205     private final SparseArray<LingerTimer> mLingerTimerForRequest = new SparseArray<>();
    206 
    207     // Linger expiry timer. Armed whenever mLingerTimers is non-empty, regardless of whether the
    208     // network is lingering or not. Always set to the expiry of the LingerTimer that expires last.
    209     // When the timer fires, all linger state is cleared, and if the network has no requests, it is
    210     // torn down.
    211     private WakeupMessage mLingerMessage;
    212 
    213     // Linger expiry. Holds the expiry time of the linger timer, or 0 if the timer is not armed.
    214     private long mLingerExpiryMs;
    215 
    216     // Whether the network is lingering or not. Must be maintained separately from the above because
    217     // it depends on the state of other networks and requests, which only ConnectivityService knows.
    218     // (Example: we don't linger a network if it would become the best for a NetworkRequest if it
    219     // validated).
    220     private boolean mLingering;
    221 
    222     // This represents the last score received from the NetworkAgent.
    223     private int currentScore;
    224     // Penalty applied to scores of Networks that have not been validated.
    225     private static final int UNVALIDATED_SCORE_PENALTY = 40;
    226 
    227     // Score for explicitly connected network.
    228     //
    229     // This ensures that a) the explicitly selected network is never trumped by anything else, and
    230     // b) the explicitly selected network is never torn down.
    231     private static final int MAXIMUM_NETWORK_SCORE = 100;
    232 
    233     // The list of NetworkRequests being satisfied by this Network.
    234     private final SparseArray<NetworkRequest> mNetworkRequests = new SparseArray<>();
    235 
    236     // How many of the satisfied requests are actual requests and not listens.
    237     private int mNumRequestNetworkRequests = 0;
    238 
    239     // How many of the satisfied requests are of type BACKGROUND_REQUEST.
    240     private int mNumBackgroundNetworkRequests = 0;
    241 
    242     public final Messenger messenger;
    243     public final AsyncChannel asyncChannel;
    244 
    245     // Used by ConnectivityService to keep track of 464xlat.
    246     public Nat464Xlat clatd;
    247 
    248     private static final String TAG = ConnectivityService.class.getSimpleName();
    249     private static final boolean VDBG = false;
    250     private final ConnectivityService mConnService;
    251     private final Context mContext;
    252     private final Handler mHandler;
    253 
    254     public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, Network net, NetworkInfo info,
    255             LinkProperties lp, NetworkCapabilities nc, int score, Context context, Handler handler,
    256             NetworkMisc misc, NetworkRequest defaultRequest, ConnectivityService connService) {
    257         this.messenger = messenger;
    258         asyncChannel = ac;
    259         network = net;
    260         networkInfo = info;
    261         linkProperties = lp;
    262         networkCapabilities = nc;
    263         currentScore = score;
    264         mConnService = connService;
    265         mContext = context;
    266         mHandler = handler;
    267         networkMonitor = mConnService.createNetworkMonitor(context, handler, this, defaultRequest);
    268         networkMisc = misc;
    269     }
    270 
    271     // Functions for manipulating the requests satisfied by this network.
    272     //
    273     // These functions must only called on ConnectivityService's main thread.
    274 
    275     private static final boolean ADD = true;
    276     private static final boolean REMOVE = false;
    277 
    278     private void updateRequestCounts(boolean add, NetworkRequest request) {
    279         int delta = add ? +1 : -1;
    280         switch (request.type) {
    281             case REQUEST:
    282             case TRACK_DEFAULT:
    283                 mNumRequestNetworkRequests += delta;
    284                 break;
    285 
    286             case BACKGROUND_REQUEST:
    287                 mNumRequestNetworkRequests += delta;
    288                 mNumBackgroundNetworkRequests += delta;
    289                 break;
    290 
    291             case LISTEN:
    292                 break;
    293 
    294             case NONE:
    295             default:
    296                 Log.wtf(TAG, "Unhandled request type " + request.type);
    297                 break;
    298         }
    299     }
    300 
    301     /**
    302      * Add {@code networkRequest} to this network as it's satisfied by this network.
    303      * @return true if {@code networkRequest} was added or false if {@code networkRequest} was
    304      *         already present.
    305      */
    306     public boolean addRequest(NetworkRequest networkRequest) {
    307         NetworkRequest existing = mNetworkRequests.get(networkRequest.requestId);
    308         if (existing == networkRequest) return false;
    309         if (existing != null) {
    310             // Should only happen if the requestId wraps. If that happens lots of other things will
    311             // be broken as well.
    312             Log.wtf(TAG, String.format("Duplicate requestId for %s and %s on %s",
    313                     networkRequest, existing, name()));
    314             updateRequestCounts(REMOVE, existing);
    315         }
    316         mNetworkRequests.put(networkRequest.requestId, networkRequest);
    317         updateRequestCounts(ADD, networkRequest);
    318         return true;
    319     }
    320 
    321     /**
    322      * Remove the specified request from this network.
    323      */
    324     public void removeRequest(int requestId) {
    325         NetworkRequest existing = mNetworkRequests.get(requestId);
    326         if (existing == null) return;
    327         updateRequestCounts(REMOVE, existing);
    328         mNetworkRequests.remove(requestId);
    329         if (existing.isRequest()) {
    330             unlingerRequest(existing);
    331         }
    332     }
    333 
    334     /**
    335      * Returns whether this network is currently satisfying the request with the specified ID.
    336      */
    337     public boolean isSatisfyingRequest(int id) {
    338         return mNetworkRequests.get(id) != null;
    339     }
    340 
    341     /**
    342      * Returns the request at the specified position in the list of requests satisfied by this
    343      * network.
    344      */
    345     public NetworkRequest requestAt(int index) {
    346         return mNetworkRequests.valueAt(index);
    347     }
    348 
    349     /**
    350      * Returns the number of requests currently satisfied by this network for which
    351      * {@link android.net.NetworkRequest#isRequest} returns {@code true}.
    352      */
    353     public int numRequestNetworkRequests() {
    354         return mNumRequestNetworkRequests;
    355     }
    356 
    357     /**
    358      * Returns the number of requests currently satisfied by this network of type
    359      * {@link android.net.NetworkRequest.Type.BACKGROUND_REQUEST}.
    360      */
    361     public int numBackgroundNetworkRequests() {
    362         return mNumBackgroundNetworkRequests;
    363     }
    364 
    365     /**
    366      * Returns the number of foreground requests currently satisfied by this network.
    367      */
    368     public int numForegroundNetworkRequests() {
    369         return mNumRequestNetworkRequests - mNumBackgroundNetworkRequests;
    370     }
    371 
    372     /**
    373      * Returns the number of requests of any type currently satisfied by this network.
    374      */
    375     public int numNetworkRequests() {
    376         return mNetworkRequests.size();
    377     }
    378 
    379     /**
    380      * Returns whether the network is a background network. A network is a background network if it
    381      * is satisfying no foreground requests and at least one background request. (If it did not have
    382      * a background request, it would be a speculative network that is only being kept up because
    383      * it might satisfy a request if it validated).
    384      */
    385     public boolean isBackgroundNetwork() {
    386         return !isVPN() && numForegroundNetworkRequests() == 0 && mNumBackgroundNetworkRequests > 0;
    387     }
    388 
    389     // Does this network satisfy request?
    390     public boolean satisfies(NetworkRequest request) {
    391         return created &&
    392                 request.networkCapabilities.satisfiedByNetworkCapabilities(networkCapabilities);
    393     }
    394 
    395     public boolean satisfiesImmutableCapabilitiesOf(NetworkRequest request) {
    396         return created &&
    397                 request.networkCapabilities.satisfiedByImmutableNetworkCapabilities(
    398                         networkCapabilities);
    399     }
    400 
    401     public boolean isVPN() {
    402         return networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN);
    403     }
    404 
    405     private int getCurrentScore(boolean pretendValidated) {
    406         // TODO: We may want to refactor this into a NetworkScore class that takes a base score from
    407         // the NetworkAgent and signals from the NetworkAgent and uses those signals to modify the
    408         // score.  The NetworkScore class would provide a nice place to centralize score constants
    409         // so they are not scattered about the transports.
    410 
    411         // If this network is explicitly selected and the user has decided to use it even if it's
    412         // unvalidated, give it the maximum score. Also give it the maximum score if it's explicitly
    413         // selected and we're trying to see what its score could be. This ensures that we don't tear
    414         // down an explicitly selected network before the user gets a chance to prefer it when
    415         // a higher-scoring network (e.g., Ethernet) is available.
    416         if (networkMisc.explicitlySelected && (networkMisc.acceptUnvalidated || pretendValidated)) {
    417             return MAXIMUM_NETWORK_SCORE;
    418         }
    419 
    420         int score = currentScore;
    421         if (!lastValidated && !pretendValidated && !ignoreWifiUnvalidationPenalty()) {
    422             score -= UNVALIDATED_SCORE_PENALTY;
    423         }
    424         if (score < 0) score = 0;
    425         return score;
    426     }
    427 
    428     // Return true on devices configured to ignore score penalty for wifi networks
    429     // that become unvalidated (b/31075769).
    430     private boolean ignoreWifiUnvalidationPenalty() {
    431         boolean isWifi = networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) &&
    432                 networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
    433         boolean avoidBadWifi = mConnService.avoidBadWifi() || avoidUnvalidated;
    434         return isWifi && !avoidBadWifi && everValidated;
    435     }
    436 
    437     // Get the current score for this Network.  This may be modified from what the
    438     // NetworkAgent sent, as it has modifiers applied to it.
    439     public int getCurrentScore() {
    440         return getCurrentScore(false);
    441     }
    442 
    443     // Get the current score for this Network as if it was validated.  This may be modified from
    444     // what the NetworkAgent sent, as it has modifiers applied to it.
    445     public int getCurrentScoreAsValidated() {
    446         return getCurrentScore(true);
    447     }
    448 
    449     public void setCurrentScore(int newScore) {
    450         currentScore = newScore;
    451     }
    452 
    453     public NetworkState getNetworkState() {
    454         synchronized (this) {
    455             // Network objects are outwardly immutable so there is no point to duplicating.
    456             // Duplicating also precludes sharing socket factories and connection pools.
    457             final String subscriberId = (networkMisc != null) ? networkMisc.subscriberId : null;
    458             return new NetworkState(new NetworkInfo(networkInfo),
    459                     new LinkProperties(linkProperties),
    460                     new NetworkCapabilities(networkCapabilities), network, subscriberId, null);
    461         }
    462     }
    463 
    464     /**
    465      * Sets the specified request to linger on this network for the specified time. Called by
    466      * ConnectivityService when the request is moved to another network with a higher score.
    467      */
    468     public void lingerRequest(NetworkRequest request, long now, long duration) {
    469         if (mLingerTimerForRequest.get(request.requestId) != null) {
    470             // Cannot happen. Once a request is lingering on a particular network, we cannot
    471             // re-linger it unless that network becomes the best for that request again, in which
    472             // case we should have unlingered it.
    473             Log.wtf(TAG, this.name() + ": request " + request.requestId + " already lingered");
    474         }
    475         final long expiryMs = now + duration;
    476         LingerTimer timer = new LingerTimer(request, expiryMs);
    477         if (VDBG) Log.d(TAG, "Adding LingerTimer " + timer + " to " + this.name());
    478         mLingerTimers.add(timer);
    479         mLingerTimerForRequest.put(request.requestId, timer);
    480     }
    481 
    482     /**
    483      * Cancel lingering. Called by ConnectivityService when a request is added to this network.
    484      * Returns true if the given request was lingering on this network, false otherwise.
    485      */
    486     public boolean unlingerRequest(NetworkRequest request) {
    487         LingerTimer timer = mLingerTimerForRequest.get(request.requestId);
    488         if (timer != null) {
    489             if (VDBG) Log.d(TAG, "Removing LingerTimer " + timer + " from " + this.name());
    490             mLingerTimers.remove(timer);
    491             mLingerTimerForRequest.remove(request.requestId);
    492             return true;
    493         }
    494         return false;
    495     }
    496 
    497     public long getLingerExpiry() {
    498         return mLingerExpiryMs;
    499     }
    500 
    501     public void updateLingerTimer() {
    502         long newExpiry = mLingerTimers.isEmpty() ? 0 : mLingerTimers.last().expiryMs;
    503         if (newExpiry == mLingerExpiryMs) return;
    504 
    505         // Even if we're going to reschedule the timer, cancel it first. This is because the
    506         // semantics of WakeupMessage guarantee that if cancel is called then the alarm will
    507         // never call its callback (handleLingerComplete), even if it has already fired.
    508         // WakeupMessage makes no such guarantees about rescheduling a message, so if mLingerMessage
    509         // has already been dispatched, rescheduling to some time in the future it won't stop it
    510         // from calling its callback immediately.
    511         if (mLingerMessage != null) {
    512             mLingerMessage.cancel();
    513             mLingerMessage = null;
    514         }
    515 
    516         if (newExpiry > 0) {
    517             mLingerMessage = mConnService.makeWakeupMessage(
    518                     mContext, mHandler,
    519                     "NETWORK_LINGER_COMPLETE." + network.netId,
    520                     EVENT_NETWORK_LINGER_COMPLETE, this);
    521             mLingerMessage.schedule(newExpiry);
    522         }
    523 
    524         mLingerExpiryMs = newExpiry;
    525     }
    526 
    527     public void linger() {
    528         mLingering = true;
    529     }
    530 
    531     public void unlinger() {
    532         mLingering = false;
    533     }
    534 
    535     public boolean isLingering() {
    536         return mLingering;
    537     }
    538 
    539     public void clearLingerState() {
    540         if (mLingerMessage != null) {
    541             mLingerMessage.cancel();
    542             mLingerMessage = null;
    543         }
    544         mLingerTimers.clear();
    545         mLingerTimerForRequest.clear();
    546         updateLingerTimer();  // Sets mLingerExpiryMs, cancels and nulls out mLingerMessage.
    547         mLingering = false;
    548     }
    549 
    550     public void dumpLingerTimers(PrintWriter pw) {
    551         for (LingerTimer timer : mLingerTimers) { pw.println(timer); }
    552     }
    553 
    554     public String toString() {
    555         return "NetworkAgentInfo{ ni{" + networkInfo + "}  " +
    556                 "network{" + network + "}  nethandle{" + network.getNetworkHandle() + "}  " +
    557                 "lp{" + linkProperties + "}  " +
    558                 "nc{" + networkCapabilities + "}  Score{" + getCurrentScore() + "}  " +
    559                 "everValidated{" + everValidated + "}  lastValidated{" + lastValidated + "}  " +
    560                 "created{" + created + "} lingering{" + isLingering() + "} " +
    561                 "explicitlySelected{" + networkMisc.explicitlySelected + "} " +
    562                 "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} " +
    563                 "everCaptivePortalDetected{" + everCaptivePortalDetected + "} " +
    564                 "lastCaptivePortalDetected{" + lastCaptivePortalDetected + "} " +
    565                 "}";
    566     }
    567 
    568     public String name() {
    569         return "NetworkAgentInfo [" + networkInfo.getTypeName() + " (" +
    570                 networkInfo.getSubtypeName() + ") - " +
    571                 (network == null ? "null" : network.toString()) + "]";
    572     }
    573 
    574     // Enables sorting in descending order of score.
    575     @Override
    576     public int compareTo(NetworkAgentInfo other) {
    577         return other.getCurrentScore() - getCurrentScore();
    578     }
    579 }
    580