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 LingerTimer(NetworkRequest request, long expiryMs)167 public LingerTimer(NetworkRequest request, long expiryMs) { 168 this.request = request; 169 this.expiryMs = expiryMs; 170 } equals(Object o)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 } hashCode()176 public int hashCode() { 177 return Objects.hash(request.requestId, expiryMs); 178 } compareTo(LingerTimer other)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 } toString()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 NetworkAgentInfo(Messenger messenger, AsyncChannel ac, Network net, NetworkInfo info, LinkProperties lp, NetworkCapabilities nc, int score, Context context, Handler handler, NetworkMisc misc, NetworkRequest defaultRequest, ConnectivityService connService)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 updateRequestCounts(boolean add, NetworkRequest request)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 */ addRequest(NetworkRequest networkRequest)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 */ removeRequest(int requestId)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 */ isSatisfyingRequest(int id)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 */ requestAt(int index)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 */ numRequestNetworkRequests()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 */ numBackgroundNetworkRequests()361 public int numBackgroundNetworkRequests() { 362 return mNumBackgroundNetworkRequests; 363 } 364 365 /** 366 * Returns the number of foreground requests currently satisfied by this network. 367 */ numForegroundNetworkRequests()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 */ numNetworkRequests()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 */ isBackgroundNetwork()385 public boolean isBackgroundNetwork() { 386 return !isVPN() && numForegroundNetworkRequests() == 0 && mNumBackgroundNetworkRequests > 0; 387 } 388 389 // Does this network satisfy request? satisfies(NetworkRequest request)390 public boolean satisfies(NetworkRequest request) { 391 return created && 392 request.networkCapabilities.satisfiedByNetworkCapabilities(networkCapabilities); 393 } 394 satisfiesImmutableCapabilitiesOf(NetworkRequest request)395 public boolean satisfiesImmutableCapabilitiesOf(NetworkRequest request) { 396 return created && 397 request.networkCapabilities.satisfiedByImmutableNetworkCapabilities( 398 networkCapabilities); 399 } 400 isVPN()401 public boolean isVPN() { 402 return networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN); 403 } 404 getCurrentScore(boolean pretendValidated)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). ignoreWifiUnvalidationPenalty()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. getCurrentScore()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. getCurrentScoreAsValidated()445 public int getCurrentScoreAsValidated() { 446 return getCurrentScore(true); 447 } 448 setCurrentScore(int newScore)449 public void setCurrentScore(int newScore) { 450 currentScore = newScore; 451 } 452 getNetworkState()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 */ lingerRequest(NetworkRequest request, long now, long duration)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 */ unlingerRequest(NetworkRequest request)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 getLingerExpiry()497 public long getLingerExpiry() { 498 return mLingerExpiryMs; 499 } 500 updateLingerTimer()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 linger()527 public void linger() { 528 mLingering = true; 529 } 530 unlinger()531 public void unlinger() { 532 mLingering = false; 533 } 534 isLingering()535 public boolean isLingering() { 536 return mLingering; 537 } 538 clearLingerState()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 dumpLingerTimers(PrintWriter pw)550 public void dumpLingerTimers(PrintWriter pw) { 551 for (LingerTimer timer : mLingerTimers) { pw.println(timer); } 552 } 553 toString()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 name()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 compareTo(NetworkAgentInfo other)576 public int compareTo(NetworkAgentInfo other) { 577 return other.getCurrentScore() - getCurrentScore(); 578 } 579 } 580