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 android.net; 18 19 import android.annotation.UnsupportedAppUsage; 20 import android.content.Context; 21 import android.os.Build; 22 import android.os.Bundle; 23 import android.os.Handler; 24 import android.os.Looper; 25 import android.os.Message; 26 import android.os.Messenger; 27 import android.util.Log; 28 29 import com.android.internal.util.AsyncChannel; 30 import com.android.internal.util.Protocol; 31 32 import java.util.ArrayList; 33 import java.util.concurrent.atomic.AtomicBoolean; 34 35 /** 36 * A Utility class for handling for communicating between bearer-specific 37 * code and ConnectivityService. 38 * 39 * A bearer may have more than one NetworkAgent if it can simultaneously 40 * support separate networks (IMS / Internet / MMS Apns on cellular, or 41 * perhaps connections with different SSID or P2P for Wi-Fi). 42 * 43 * @hide 44 */ 45 public abstract class NetworkAgent extends Handler { 46 // Guaranteed to be valid (not NETID_UNSET), otherwise registerNetworkAgent() would have thrown 47 // an exception. 48 public final int netId; 49 50 private volatile AsyncChannel mAsyncChannel; 51 private final String LOG_TAG; 52 private static final boolean DBG = true; 53 private static final boolean VDBG = false; 54 private final Context mContext; 55 private final ArrayList<Message>mPreConnectedQueue = new ArrayList<Message>(); 56 private volatile long mLastBwRefreshTime = 0; 57 private static final long BW_REFRESH_MIN_WIN_MS = 500; 58 private boolean mPollLceScheduled = false; 59 private AtomicBoolean mPollLcePending = new AtomicBoolean(false); 60 public final int mFactorySerialNumber; 61 62 private static final int BASE = Protocol.BASE_NETWORK_AGENT; 63 64 /** 65 * Sent by ConnectivityService to the NetworkAgent to inform it of 66 * suspected connectivity problems on its network. The NetworkAgent 67 * should take steps to verify and correct connectivity. 68 */ 69 public static final int CMD_SUSPECT_BAD = BASE; 70 71 /** 72 * Sent by the NetworkAgent (note the EVENT vs CMD prefix) to 73 * ConnectivityService to pass the current NetworkInfo (connection state). 74 * Sent when the NetworkInfo changes, mainly due to change of state. 75 * obj = NetworkInfo 76 */ 77 public static final int EVENT_NETWORK_INFO_CHANGED = BASE + 1; 78 79 /** 80 * Sent by the NetworkAgent to ConnectivityService to pass the current 81 * NetworkCapabilties. 82 * obj = NetworkCapabilities 83 */ 84 public static final int EVENT_NETWORK_CAPABILITIES_CHANGED = BASE + 2; 85 86 /** 87 * Sent by the NetworkAgent to ConnectivityService to pass the current 88 * NetworkProperties. 89 * obj = NetworkProperties 90 */ 91 public static final int EVENT_NETWORK_PROPERTIES_CHANGED = BASE + 3; 92 93 /* centralize place where base network score, and network score scaling, will be 94 * stored, so as we can consistently compare apple and oranges, or wifi, ethernet and LTE 95 */ 96 public static final int WIFI_BASE_SCORE = 60; 97 98 /** 99 * Sent by the NetworkAgent to ConnectivityService to pass the current 100 * network score. 101 * obj = network score Integer 102 */ 103 public static final int EVENT_NETWORK_SCORE_CHANGED = BASE + 4; 104 105 /** 106 * Sent by ConnectivityService to the NetworkAgent to inform the agent of the 107 * networks status - whether we could use the network or could not, due to 108 * either a bad network configuration (no internet link) or captive portal. 109 * 110 * arg1 = either {@code VALID_NETWORK} or {@code INVALID_NETWORK} 111 * obj = Bundle containing map from {@code REDIRECT_URL_KEY} to {@code String} 112 * representing URL that Internet probe was redirect to, if it was redirected, 113 * or mapping to {@code null} otherwise. 114 */ 115 public static final int CMD_REPORT_NETWORK_STATUS = BASE + 7; 116 117 public static final int VALID_NETWORK = 1; 118 public static final int INVALID_NETWORK = 2; 119 120 public static String REDIRECT_URL_KEY = "redirect URL"; 121 122 /** 123 * Sent by the NetworkAgent to ConnectivityService to indicate this network was 124 * explicitly selected. This should be sent before the NetworkInfo is marked 125 * CONNECTED so it can be given special treatment at that time. 126 * 127 * obj = boolean indicating whether to use this network even if unvalidated 128 */ 129 public static final int EVENT_SET_EXPLICITLY_SELECTED = BASE + 8; 130 131 /** 132 * Sent by ConnectivityService to the NetworkAgent to inform the agent of 133 * whether the network should in the future be used even if not validated. 134 * This decision is made by the user, but it is the network transport's 135 * responsibility to remember it. 136 * 137 * arg1 = 1 if true, 0 if false 138 */ 139 public static final int CMD_SAVE_ACCEPT_UNVALIDATED = BASE + 9; 140 141 /** 142 * Sent by ConnectivityService to the NetworkAgent to inform the agent to pull 143 * the underlying network connection for updated bandwidth information. 144 */ 145 public static final int CMD_REQUEST_BANDWIDTH_UPDATE = BASE + 10; 146 147 /** 148 * Sent by ConnectivityService to the NetworkAgent to request that the specified packet be sent 149 * periodically on the given interval. 150 * 151 * arg1 = the slot number of the keepalive to start 152 * arg2 = interval in seconds 153 * obj = KeepalivePacketData object describing the data to be sent 154 * 155 * Also used internally by ConnectivityService / KeepaliveTracker, with different semantics. 156 */ 157 public static final int CMD_START_SOCKET_KEEPALIVE = BASE + 11; 158 159 /** 160 * Requests that the specified keepalive packet be stopped. 161 * 162 * arg1 = slot number of the keepalive to stop. 163 * 164 * Also used internally by ConnectivityService / KeepaliveTracker, with different semantics. 165 */ 166 public static final int CMD_STOP_SOCKET_KEEPALIVE = BASE + 12; 167 168 /** 169 * Sent by the NetworkAgent to ConnectivityService to provide status on a socket keepalive 170 * request. This may either be the reply to a CMD_START_SOCKET_KEEPALIVE, or an asynchronous 171 * error notification. 172 * 173 * This is also sent by KeepaliveTracker to the app's {@link SocketKeepalive}, 174 * so that the app's {@link SocketKeepalive.Callback} methods can be called. 175 * 176 * arg1 = slot number of the keepalive 177 * arg2 = error code 178 */ 179 public static final int EVENT_SOCKET_KEEPALIVE = BASE + 13; 180 181 // TODO: move the above 2 constants down so they are in order once merge conflicts are resolved 182 /** 183 * Sent by the KeepaliveTracker to NetworkAgent to add a packet filter. 184 * 185 * For TCP keepalive offloads, keepalive packets are sent by the firmware. However, because the 186 * remote site will send ACK packets in response to the keepalive packets, the firmware also 187 * needs to be configured to properly filter the ACKs to prevent the system from waking up. 188 * This does not happen with UDP, so this message is TCP-specific. 189 * arg1 = slot number of the keepalive to filter for. 190 * obj = the keepalive packet to send repeatedly. 191 */ 192 public static final int CMD_ADD_KEEPALIVE_PACKET_FILTER = BASE + 16; 193 194 /** 195 * Sent by the KeepaliveTracker to NetworkAgent to remove a packet filter. See 196 * {@link #CMD_ADD_KEEPALIVE_PACKET_FILTER}. 197 * arg1 = slot number of the keepalive packet filter to remove. 198 */ 199 public static final int CMD_REMOVE_KEEPALIVE_PACKET_FILTER = BASE + 17; 200 201 /** 202 * Sent by ConnectivityService to inform this network transport of signal strength thresholds 203 * that when crossed should trigger a system wakeup and a NetworkCapabilities update. 204 * 205 * obj = int[] describing signal strength thresholds. 206 */ 207 public static final int CMD_SET_SIGNAL_STRENGTH_THRESHOLDS = BASE + 14; 208 209 /** 210 * Sent by ConnectivityService to the NeworkAgent to inform the agent to avoid 211 * automatically reconnecting to this network (e.g. via autojoin). Happens 212 * when user selects "No" option on the "Stay connected?" dialog box. 213 */ 214 public static final int CMD_PREVENT_AUTOMATIC_RECONNECT = BASE + 15; 215 216 // TODO : remove these two constructors. They are a stopgap measure to help sheperding a number 217 // of dependent changes that would conflict throughout the automerger graph. Having these 218 // temporarily helps with the process of going through with all these dependent changes across 219 // the entire tree. NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni, NetworkCapabilities nc, LinkProperties lp, int score)220 public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni, 221 NetworkCapabilities nc, LinkProperties lp, int score) { 222 this(looper, context, logTag, ni, nc, lp, score, null, NetworkFactory.SerialNumber.NONE); 223 } NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni, NetworkCapabilities nc, LinkProperties lp, int score, NetworkMisc misc)224 public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni, 225 NetworkCapabilities nc, LinkProperties lp, int score, NetworkMisc misc) { 226 this(looper, context, logTag, ni, nc, lp, score, misc, NetworkFactory.SerialNumber.NONE); 227 } 228 NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni, NetworkCapabilities nc, LinkProperties lp, int score, int factorySerialNumber)229 public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni, 230 NetworkCapabilities nc, LinkProperties lp, int score, int factorySerialNumber) { 231 this(looper, context, logTag, ni, nc, lp, score, null, factorySerialNumber); 232 } 233 NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni, NetworkCapabilities nc, LinkProperties lp, int score, NetworkMisc misc, int factorySerialNumber)234 public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni, 235 NetworkCapabilities nc, LinkProperties lp, int score, NetworkMisc misc, 236 int factorySerialNumber) { 237 super(looper); 238 LOG_TAG = logTag; 239 mContext = context; 240 mFactorySerialNumber = factorySerialNumber; 241 if (ni == null || nc == null || lp == null) { 242 throw new IllegalArgumentException(); 243 } 244 245 if (VDBG) log("Registering NetworkAgent"); 246 ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService( 247 Context.CONNECTIVITY_SERVICE); 248 netId = cm.registerNetworkAgent(new Messenger(this), new NetworkInfo(ni), 249 new LinkProperties(lp), new NetworkCapabilities(nc), score, misc, 250 factorySerialNumber); 251 } 252 253 @Override handleMessage(Message msg)254 public void handleMessage(Message msg) { 255 switch (msg.what) { 256 case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: { 257 if (mAsyncChannel != null) { 258 log("Received new connection while already connected!"); 259 } else { 260 if (VDBG) log("NetworkAgent fully connected"); 261 AsyncChannel ac = new AsyncChannel(); 262 ac.connected(null, this, msg.replyTo); 263 ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED, 264 AsyncChannel.STATUS_SUCCESSFUL); 265 synchronized (mPreConnectedQueue) { 266 mAsyncChannel = ac; 267 for (Message m : mPreConnectedQueue) { 268 ac.sendMessage(m); 269 } 270 mPreConnectedQueue.clear(); 271 } 272 } 273 break; 274 } 275 case AsyncChannel.CMD_CHANNEL_DISCONNECT: { 276 if (VDBG) log("CMD_CHANNEL_DISCONNECT"); 277 if (mAsyncChannel != null) mAsyncChannel.disconnect(); 278 break; 279 } 280 case AsyncChannel.CMD_CHANNEL_DISCONNECTED: { 281 if (DBG) log("NetworkAgent channel lost"); 282 // let the client know CS is done with us. 283 unwanted(); 284 synchronized (mPreConnectedQueue) { 285 mAsyncChannel = null; 286 } 287 break; 288 } 289 case CMD_SUSPECT_BAD: { 290 log("Unhandled Message " + msg); 291 break; 292 } 293 case CMD_REQUEST_BANDWIDTH_UPDATE: { 294 long currentTimeMs = System.currentTimeMillis(); 295 if (VDBG) { 296 log("CMD_REQUEST_BANDWIDTH_UPDATE request received."); 297 } 298 if (currentTimeMs >= (mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS)) { 299 mPollLceScheduled = false; 300 if (mPollLcePending.getAndSet(true) == false) { 301 pollLceData(); 302 } 303 } else { 304 // deliver the request at a later time rather than discard it completely. 305 if (!mPollLceScheduled) { 306 long waitTime = mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS - 307 currentTimeMs + 1; 308 mPollLceScheduled = sendEmptyMessageDelayed( 309 CMD_REQUEST_BANDWIDTH_UPDATE, waitTime); 310 } 311 } 312 break; 313 } 314 case CMD_REPORT_NETWORK_STATUS: { 315 String redirectUrl = ((Bundle)msg.obj).getString(REDIRECT_URL_KEY); 316 if (VDBG) { 317 log("CMD_REPORT_NETWORK_STATUS(" + 318 (msg.arg1 == VALID_NETWORK ? "VALID, " : "INVALID, ") + redirectUrl); 319 } 320 networkStatus(msg.arg1, redirectUrl); 321 break; 322 } 323 case CMD_SAVE_ACCEPT_UNVALIDATED: { 324 saveAcceptUnvalidated(msg.arg1 != 0); 325 break; 326 } 327 case CMD_START_SOCKET_KEEPALIVE: { 328 startSocketKeepalive(msg); 329 break; 330 } 331 case CMD_STOP_SOCKET_KEEPALIVE: { 332 stopSocketKeepalive(msg); 333 break; 334 } 335 336 case CMD_SET_SIGNAL_STRENGTH_THRESHOLDS: { 337 ArrayList<Integer> thresholds = 338 ((Bundle) msg.obj).getIntegerArrayList("thresholds"); 339 // TODO: Change signal strength thresholds API to use an ArrayList<Integer> 340 // rather than convert to int[]. 341 int[] intThresholds = new int[(thresholds != null) ? thresholds.size() : 0]; 342 for (int i = 0; i < intThresholds.length; i++) { 343 intThresholds[i] = thresholds.get(i); 344 } 345 setSignalStrengthThresholds(intThresholds); 346 break; 347 } 348 case CMD_PREVENT_AUTOMATIC_RECONNECT: { 349 preventAutomaticReconnect(); 350 break; 351 } 352 case CMD_ADD_KEEPALIVE_PACKET_FILTER: { 353 addKeepalivePacketFilter(msg); 354 break; 355 } 356 case CMD_REMOVE_KEEPALIVE_PACKET_FILTER: { 357 removeKeepalivePacketFilter(msg); 358 break; 359 } 360 } 361 } 362 queueOrSendMessage(int what, Object obj)363 private void queueOrSendMessage(int what, Object obj) { 364 queueOrSendMessage(what, 0, 0, obj); 365 } 366 queueOrSendMessage(int what, int arg1, int arg2)367 private void queueOrSendMessage(int what, int arg1, int arg2) { 368 queueOrSendMessage(what, arg1, arg2, null); 369 } 370 queueOrSendMessage(int what, int arg1, int arg2, Object obj)371 private void queueOrSendMessage(int what, int arg1, int arg2, Object obj) { 372 Message msg = Message.obtain(); 373 msg.what = what; 374 msg.arg1 = arg1; 375 msg.arg2 = arg2; 376 msg.obj = obj; 377 queueOrSendMessage(msg); 378 } 379 queueOrSendMessage(Message msg)380 private void queueOrSendMessage(Message msg) { 381 synchronized (mPreConnectedQueue) { 382 if (mAsyncChannel != null) { 383 mAsyncChannel.sendMessage(msg); 384 } else { 385 mPreConnectedQueue.add(msg); 386 } 387 } 388 } 389 390 /** 391 * Called by the bearer code when it has new LinkProperties data. 392 */ sendLinkProperties(LinkProperties linkProperties)393 public void sendLinkProperties(LinkProperties linkProperties) { 394 queueOrSendMessage(EVENT_NETWORK_PROPERTIES_CHANGED, new LinkProperties(linkProperties)); 395 } 396 397 /** 398 * Called by the bearer code when it has new NetworkInfo data. 399 */ 400 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) sendNetworkInfo(NetworkInfo networkInfo)401 public void sendNetworkInfo(NetworkInfo networkInfo) { 402 queueOrSendMessage(EVENT_NETWORK_INFO_CHANGED, new NetworkInfo(networkInfo)); 403 } 404 405 /** 406 * Called by the bearer code when it has new NetworkCapabilities data. 407 */ sendNetworkCapabilities(NetworkCapabilities networkCapabilities)408 public void sendNetworkCapabilities(NetworkCapabilities networkCapabilities) { 409 mPollLcePending.set(false); 410 mLastBwRefreshTime = System.currentTimeMillis(); 411 queueOrSendMessage(EVENT_NETWORK_CAPABILITIES_CHANGED, 412 new NetworkCapabilities(networkCapabilities)); 413 } 414 415 /** 416 * Called by the bearer code when it has a new score for this network. 417 */ sendNetworkScore(int score)418 public void sendNetworkScore(int score) { 419 if (score < 0) { 420 throw new IllegalArgumentException("Score must be >= 0"); 421 } 422 queueOrSendMessage(EVENT_NETWORK_SCORE_CHANGED, score, 0); 423 } 424 425 /** 426 * Called by the bearer to indicate this network was manually selected by the user. 427 * This should be called before the NetworkInfo is marked CONNECTED so that this 428 * Network can be given special treatment at that time. If {@code acceptUnvalidated} is 429 * {@code true}, then the system will switch to this network. If it is {@code false} and the 430 * network cannot be validated, the system will ask the user whether to switch to this network. 431 * If the user confirms and selects "don't ask again", then the system will call 432 * {@link #saveAcceptUnvalidated} to persist the user's choice. Thus, if the transport ever 433 * calls this method with {@code acceptUnvalidated} set to {@code false}, it must also implement 434 * {@link #saveAcceptUnvalidated} to respect the user's choice. 435 */ explicitlySelected(boolean acceptUnvalidated)436 public void explicitlySelected(boolean acceptUnvalidated) { 437 explicitlySelected(true /* explicitlySelected */, acceptUnvalidated); 438 } 439 440 /** 441 * Called by the bearer to indicate this network was manually selected by the user. 442 * This should be called before the NetworkInfo is marked CONNECTED so that this 443 * Network can be given special treatment at that time. If {@code acceptUnvalidated} is 444 * {@code true}, then the system will switch to this network. If it is {@code false} and the 445 * network cannot be validated, the system will ask the user whether to switch to this network. 446 * If the user confirms and selects "don't ask again", then the system will call 447 * {@link #saveAcceptUnvalidated} to persist the user's choice. Thus, if the transport ever 448 * calls this method with {@code acceptUnvalidated} set to {@code false}, it must also implement 449 * {@link #saveAcceptUnvalidated} to respect the user's choice. 450 */ explicitlySelected(boolean explicitlySelected, boolean acceptUnvalidated)451 public void explicitlySelected(boolean explicitlySelected, boolean acceptUnvalidated) { 452 queueOrSendMessage(EVENT_SET_EXPLICITLY_SELECTED, 453 explicitlySelected ? 1 : 0, 454 acceptUnvalidated ? 1 : 0); 455 } 456 457 /** 458 * Called when ConnectivityService has indicated they no longer want this network. 459 * The parent factory should (previously) have received indication of the change 460 * as well, either canceling NetworkRequests or altering their score such that this 461 * network won't be immediately requested again. 462 */ unwanted()463 abstract protected void unwanted(); 464 465 /** 466 * Called when ConnectivityService request a bandwidth update. The parent factory 467 * shall try to overwrite this method and produce a bandwidth update if capable. 468 */ pollLceData()469 protected void pollLceData() { 470 } 471 472 /** 473 * Called when the system determines the usefulness of this network. 474 * 475 * Networks claiming internet connectivity will have their internet 476 * connectivity verified. 477 * 478 * Currently there are two possible values: 479 * {@code VALID_NETWORK} if the system is happy with the connection, 480 * {@code INVALID_NETWORK} if the system is not happy. 481 * TODO - add indications of captive portal-ness and related success/failure, 482 * ie, CAPTIVE_SUCCESS_NETWORK, CAPTIVE_NETWORK for successful login and detection 483 * 484 * This may be called multiple times as the network status changes and may 485 * generate false negatives if we lose ip connectivity before the link is torn down. 486 * 487 * @param status one of {@code VALID_NETWORK} or {@code INVALID_NETWORK}. 488 * @param redirectUrl If the Internet probe was redirected, this is the destination it was 489 * redirected to, otherwise {@code null}. 490 */ networkStatus(int status, String redirectUrl)491 protected void networkStatus(int status, String redirectUrl) { 492 } 493 494 /** 495 * Called when the user asks to remember the choice to use this network even if unvalidated. 496 * The transport is responsible for remembering the choice, and the next time the user connects 497 * to the network, should explicitlySelected with {@code acceptUnvalidated} set to {@code true}. 498 * This method will only be called if {@link #explicitlySelected} was called with 499 * {@code acceptUnvalidated} set to {@code false}. 500 */ saveAcceptUnvalidated(boolean accept)501 protected void saveAcceptUnvalidated(boolean accept) { 502 } 503 504 /** 505 * Requests that the network hardware send the specified packet at the specified interval. 506 */ startSocketKeepalive(Message msg)507 protected void startSocketKeepalive(Message msg) { 508 onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_UNSUPPORTED); 509 } 510 511 /** 512 * Requests that the network hardware send the specified packet at the specified interval. 513 */ stopSocketKeepalive(Message msg)514 protected void stopSocketKeepalive(Message msg) { 515 onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_UNSUPPORTED); 516 } 517 518 /** 519 * Called by the network when a socket keepalive event occurs. 520 */ onSocketKeepaliveEvent(int slot, int reason)521 public void onSocketKeepaliveEvent(int slot, int reason) { 522 queueOrSendMessage(EVENT_SOCKET_KEEPALIVE, slot, reason); 523 } 524 525 /** 526 * Called by ConnectivityService to add specific packet filter to network hardware to block 527 * ACKs matching the sent keepalive packets. Implementations that support this feature must 528 * override this method. 529 */ addKeepalivePacketFilter(Message msg)530 protected void addKeepalivePacketFilter(Message msg) { 531 } 532 533 /** 534 * Called by ConnectivityService to remove a packet filter installed with 535 * {@link #addKeepalivePacketFilter(Message)}. Implementations that support this feature 536 * must override this method. 537 */ removeKeepalivePacketFilter(Message msg)538 protected void removeKeepalivePacketFilter(Message msg) { 539 } 540 541 /** 542 * Called by ConnectivityService to inform this network transport of signal strength thresholds 543 * that when crossed should trigger a system wakeup and a NetworkCapabilities update. 544 */ setSignalStrengthThresholds(int[] thresholds)545 protected void setSignalStrengthThresholds(int[] thresholds) { 546 } 547 548 /** 549 * Called when the user asks to not stay connected to this network because it was found to not 550 * provide Internet access. Usually followed by call to {@code unwanted}. The transport is 551 * responsible for making sure the device does not automatically reconnect to the same network 552 * after the {@code unwanted} call. 553 */ preventAutomaticReconnect()554 protected void preventAutomaticReconnect() { 555 } 556 log(String s)557 protected void log(String s) { 558 Log.d(LOG_TAG, "NetworkAgent: " + s); 559 } 560 } 561