NetworkMonitor.java revision 2c311d61eaf331818e601f97485f88c4cf26384d
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 17package com.android.server.connectivity; 18 19import android.app.AlarmManager; 20import android.app.PendingIntent; 21import android.content.BroadcastReceiver; 22import android.content.ComponentName; 23import android.content.Context; 24import android.content.Intent; 25import android.content.IntentFilter; 26import android.net.ConnectivityManager; 27import android.net.Network; 28import android.net.NetworkCapabilities; 29import android.net.NetworkInfo; 30import android.net.NetworkRequest; 31import android.net.TrafficStats; 32import android.net.wifi.WifiInfo; 33import android.net.wifi.WifiManager; 34import android.os.Handler; 35import android.os.Message; 36import android.os.SystemClock; 37import android.os.SystemProperties; 38import android.os.UserHandle; 39import android.provider.Settings; 40import android.telephony.CellIdentityCdma; 41import android.telephony.CellIdentityGsm; 42import android.telephony.CellIdentityLte; 43import android.telephony.CellIdentityWcdma; 44import android.telephony.CellInfo; 45import android.telephony.CellInfoCdma; 46import android.telephony.CellInfoGsm; 47import android.telephony.CellInfoLte; 48import android.telephony.CellInfoWcdma; 49import android.telephony.TelephonyManager; 50import android.util.Log; 51 52import com.android.internal.util.Protocol; 53import com.android.internal.util.State; 54import com.android.internal.util.StateMachine; 55import com.android.server.ConnectivityService; 56import com.android.server.connectivity.NetworkAgentInfo; 57 58import java.io.IOException; 59import java.net.HttpURLConnection; 60import java.net.URL; 61import java.util.List; 62 63/** 64 * {@hide} 65 */ 66public class NetworkMonitor extends StateMachine { 67 private static final boolean DBG = true; 68 private static final String TAG = "NetworkMonitor"; 69 private static final String DEFAULT_SERVER = "clients3.google.com"; 70 private static final int SOCKET_TIMEOUT_MS = 10000; 71 public static final String ACTION_NETWORK_CONDITIONS_MEASURED = 72 "android.net.conn.NETWORK_CONDITIONS_MEASURED"; 73 public static final String EXTRA_CONNECTIVITY_TYPE = "extra_connectivity_type"; 74 public static final String EXTRA_NETWORK_TYPE = "extra_network_type"; 75 public static final String EXTRA_RESPONSE_RECEIVED = "extra_response_received"; 76 public static final String EXTRA_IS_CAPTIVE_PORTAL = "extra_is_captive_portal"; 77 public static final String EXTRA_CELL_ID = "extra_cellid"; 78 public static final String EXTRA_SSID = "extra_ssid"; 79 public static final String EXTRA_BSSID = "extra_bssid"; 80 /** real time since boot */ 81 public static final String EXTRA_REQUEST_TIMESTAMP_MS = "extra_request_timestamp_ms"; 82 public static final String EXTRA_RESPONSE_TIMESTAMP_MS = "extra_response_timestamp_ms"; 83 84 private static final String PERMISSION_ACCESS_NETWORK_CONDITIONS = 85 "android.permission.ACCESS_NETWORK_CONDITIONS"; 86 87 // Keep these in sync with CaptivePortalLoginActivity.java. 88 // Intent broadcast from CaptivePortalLogin indicating sign-in is complete. 89 // Extras: 90 // EXTRA_TEXT = netId 91 // LOGGED_IN_RESULT = "1" if we should use network, "0" if not. 92 private static final String ACTION_CAPTIVE_PORTAL_LOGGED_IN = 93 "android.net.netmon.captive_portal_logged_in"; 94 private static final String LOGGED_IN_RESULT = "result"; 95 96 // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED. 97 // The network should be used as a default internet connection. It was found to be: 98 // 1. a functioning network providing internet access, or 99 // 2. a captive portal and the user decided to use it as is. 100 public static final int NETWORK_TEST_RESULT_VALID = 0; 101 // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED. 102 // The network should not be used as a default internet connection. It was found to be: 103 // 1. a captive portal and the user is prompted to sign-in, or 104 // 2. a captive portal and the user did not want to use it, or 105 // 3. a broken network (e.g. DNS failed, connect failed, HTTP request failed). 106 public static final int NETWORK_TEST_RESULT_INVALID = 1; 107 108 private static final int BASE = Protocol.BASE_NETWORK_MONITOR; 109 110 /** 111 * Inform NetworkMonitor that their network is connected. 112 * Initiates Network Validation. 113 */ 114 public static final int CMD_NETWORK_CONNECTED = BASE + 1; 115 116 /** 117 * Inform ConnectivityService that the network has been tested. 118 * obj = NetworkAgentInfo 119 * arg1 = One of the NETWORK_TESTED_RESULT_* constants. 120 */ 121 public static final int EVENT_NETWORK_TESTED = BASE + 2; 122 123 /** 124 * Inform NetworkMonitor to linger a network. The Monitor should 125 * start a timer and/or start watching for zero live connections while 126 * moving towards LINGER_COMPLETE. After the Linger period expires 127 * (or other events mark the end of the linger state) the LINGER_COMPLETE 128 * event should be sent and the network will be shut down. If a 129 * CMD_NETWORK_CONNECTED happens before the LINGER completes 130 * it indicates further desire to keep the network alive and so 131 * the LINGER is aborted. 132 */ 133 public static final int CMD_NETWORK_LINGER = BASE + 3; 134 135 /** 136 * Message to self indicating linger delay has expired. 137 * arg1 = Token to ignore old messages. 138 */ 139 private static final int CMD_LINGER_EXPIRED = BASE + 4; 140 141 /** 142 * Inform ConnectivityService that the network LINGER period has 143 * expired. 144 * obj = NetworkAgentInfo 145 */ 146 public static final int EVENT_NETWORK_LINGER_COMPLETE = BASE + 5; 147 148 /** 149 * Message to self indicating it's time to evaluate a network's connectivity. 150 * arg1 = Token to ignore old messages. 151 */ 152 private static final int CMD_REEVALUATE = BASE + 6; 153 154 /** 155 * Inform NetworkMonitor that the network has disconnected. 156 */ 157 public static final int CMD_NETWORK_DISCONNECTED = BASE + 7; 158 159 /** 160 * Force evaluation even if it has succeeded in the past. 161 * arg1 = UID responsible for requesting this reeval. Will be billed for data. 162 */ 163 public static final int CMD_FORCE_REEVALUATION = BASE + 8; 164 165 /** 166 * Message to self indicating captive portal login is complete. 167 * arg1 = Token to ignore old messages. 168 * arg2 = 1 if we should use this network, 0 otherwise. 169 */ 170 private static final int CMD_CAPTIVE_PORTAL_LOGGED_IN = BASE + 9; 171 172 /** 173 * Message to self indicating user desires to log into captive portal. 174 * arg1 = Token to ignore old messages. 175 */ 176 private static final int CMD_USER_WANTS_SIGN_IN = BASE + 10; 177 178 /** 179 * Request ConnectivityService display provisioning notification. 180 * arg1 = Whether to make the notification visible. 181 * arg2 = NetID. 182 * obj = Intent to be launched when notification selected by user, null if !arg1. 183 */ 184 public static final int EVENT_PROVISIONING_NOTIFICATION = BASE + 11; 185 186 /** 187 * Message to self indicating sign-in app bypassed captive portal. 188 */ 189 private static final int EVENT_APP_BYPASSED_CAPTIVE_PORTAL = BASE + 12; 190 191 /** 192 * Message to self indicating no sign-in app responded. 193 */ 194 private static final int EVENT_NO_APP_RESPONSE = BASE + 13; 195 196 /** 197 * Message to self indicating sign-in app indicates sign-in is not possible. 198 */ 199 private static final int EVENT_APP_INDICATES_SIGN_IN_IMPOSSIBLE = BASE + 14; 200 201 private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger"; 202 // Default to 30s linger time-out. 203 private static final int DEFAULT_LINGER_DELAY_MS = 30000; 204 private final int mLingerDelayMs; 205 private int mLingerToken = 0; 206 207 // Negative values disable reevaluation. 208 private static final String REEVALUATE_DELAY_PROPERTY = "persist.netmon.reeval_delay"; 209 // Default to 5s reevaluation delay. 210 private static final int DEFAULT_REEVALUATE_DELAY_MS = 5000; 211 private static final int MAX_RETRIES = 10; 212 private final int mReevaluateDelayMs; 213 private int mReevaluateToken = 0; 214 private static final int INVALID_UID = -1; 215 private int mUidResponsibleForReeval = INVALID_UID; 216 217 private int mCaptivePortalLoggedInToken = 0; 218 private int mUserPromptedToken = 0; 219 220 private final Context mContext; 221 private final Handler mConnectivityServiceHandler; 222 private final NetworkAgentInfo mNetworkAgentInfo; 223 private final TelephonyManager mTelephonyManager; 224 private final WifiManager mWifiManager; 225 private final AlarmManager mAlarmManager; 226 private final NetworkRequest mDefaultRequest; 227 228 private String mServer; 229 private boolean mIsCaptivePortalCheckEnabled = false; 230 231 // Set if the user explicitly selected "Do not use this network" in captive portal sign-in app. 232 private boolean mUserDoesNotWant = false; 233 234 public boolean systemReady = false; 235 236 private State mDefaultState = new DefaultState(); 237 private State mOfflineState = new OfflineState(); 238 private State mValidatedState = new ValidatedState(); 239 private State mEvaluatingState = new EvaluatingState(); 240 private State mUserPromptedState = new UserPromptedState(); 241 private State mCaptivePortalState = new CaptivePortalState(); 242 private State mLingeringState = new LingeringState(); 243 244 public NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo, 245 NetworkRequest defaultRequest) { 246 // Add suffix indicating which NetworkMonitor we're talking about. 247 super(TAG + networkAgentInfo.name()); 248 249 mContext = context; 250 mConnectivityServiceHandler = handler; 251 mNetworkAgentInfo = networkAgentInfo; 252 mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 253 mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); 254 mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); 255 mDefaultRequest = defaultRequest; 256 257 addState(mDefaultState); 258 addState(mOfflineState, mDefaultState); 259 addState(mValidatedState, mDefaultState); 260 addState(mEvaluatingState, mDefaultState); 261 addState(mUserPromptedState, mDefaultState); 262 addState(mCaptivePortalState, mDefaultState); 263 addState(mLingeringState, mDefaultState); 264 setInitialState(mDefaultState); 265 266 mServer = Settings.Global.getString(mContext.getContentResolver(), 267 Settings.Global.CAPTIVE_PORTAL_SERVER); 268 if (mServer == null) mServer = DEFAULT_SERVER; 269 270 mLingerDelayMs = SystemProperties.getInt(LINGER_DELAY_PROPERTY, DEFAULT_LINGER_DELAY_MS); 271 mReevaluateDelayMs = SystemProperties.getInt(REEVALUATE_DELAY_PROPERTY, 272 DEFAULT_REEVALUATE_DELAY_MS); 273 274 mIsCaptivePortalCheckEnabled = Settings.Global.getInt(mContext.getContentResolver(), 275 Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED, 1) == 1; 276 277 start(); 278 } 279 280 @Override 281 protected void log(String s) { 282 Log.d(TAG + mNetworkAgentInfo.name(), s); 283 } 284 285 private class DefaultState extends State { 286 @Override 287 public boolean processMessage(Message message) { 288 if (DBG) log(getName() + message.toString()); 289 switch (message.what) { 290 case CMD_NETWORK_LINGER: 291 if (DBG) log("Lingering"); 292 transitionTo(mLingeringState); 293 return HANDLED; 294 case CMD_NETWORK_CONNECTED: 295 if (DBG) log("Connected"); 296 transitionTo(mEvaluatingState); 297 return HANDLED; 298 case CMD_NETWORK_DISCONNECTED: 299 if (DBG) log("Disconnected - quitting"); 300 quit(); 301 return HANDLED; 302 case CMD_FORCE_REEVALUATION: 303 if (DBG) log("Forcing reevaluation"); 304 mUidResponsibleForReeval = message.arg1; 305 transitionTo(mEvaluatingState); 306 return HANDLED; 307 default: 308 return HANDLED; 309 } 310 } 311 } 312 313 private class OfflineState extends State { 314 @Override 315 public void enter() { 316 mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED, 317 NETWORK_TEST_RESULT_INVALID, 0, mNetworkAgentInfo)); 318 } 319 320 @Override 321 public boolean processMessage(Message message) { 322 if (DBG) log(getName() + message.toString()); 323 switch (message.what) { 324 case CMD_FORCE_REEVALUATION: 325 // If the user has indicated they explicitly do not want to use this network, 326 // don't allow a reevaluation as this will be pointless and could result in 327 // the user being annoyed with repeated unwanted notifications. 328 return mUserDoesNotWant ? HANDLED : NOT_HANDLED; 329 default: 330 return NOT_HANDLED; 331 } 332 } 333 } 334 335 private class ValidatedState extends State { 336 @Override 337 public void enter() { 338 if (DBG) log("Validated"); 339 mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED, 340 NETWORK_TEST_RESULT_VALID, 0, mNetworkAgentInfo)); 341 } 342 343 @Override 344 public boolean processMessage(Message message) { 345 if (DBG) log(getName() + message.toString()); 346 switch (message.what) { 347 case CMD_NETWORK_CONNECTED: 348 transitionTo(mValidatedState); 349 return HANDLED; 350 default: 351 return NOT_HANDLED; 352 } 353 } 354 } 355 356 private class EvaluatingState extends State { 357 private int mRetries; 358 359 @Override 360 public void enter() { 361 mRetries = 0; 362 sendMessage(CMD_REEVALUATE, ++mReevaluateToken, 0); 363 if (mUidResponsibleForReeval != INVALID_UID) { 364 TrafficStats.setThreadStatsUid(mUidResponsibleForReeval); 365 mUidResponsibleForReeval = INVALID_UID; 366 } 367 } 368 369 @Override 370 public boolean processMessage(Message message) { 371 if (DBG) log(getName() + message.toString()); 372 switch (message.what) { 373 case CMD_REEVALUATE: 374 if (message.arg1 != mReevaluateToken) 375 return HANDLED; 376 // Don't bother validating networks that don't satisify the default request. 377 // This includes: 378 // - VPNs which can be considered explicitly desired by the user and the 379 // user's desire trumps whether the network validates. 380 // - Networks that don't provide internet access. It's unclear how to 381 // validate such networks. 382 // - Untrusted networks. It's unsafe to prompt the user to sign-in to 383 // such networks and the user didn't express interest in connecting to 384 // such networks (an app did) so the user may be unhappily surprised when 385 // asked to sign-in to a network they didn't want to connect to in the 386 // first place. Validation could be done to adjust the network scores 387 // however these networks are app-requested and may not be intended for 388 // general usage, in which case general validation may not be an accurate 389 // measure of the network's quality. Only the app knows how to evaluate 390 // the network so don't bother validating here. Furthermore sending HTTP 391 // packets over the network may be undesirable, for example an extremely 392 // expensive metered network, or unwanted leaking of the User Agent string. 393 if (!mDefaultRequest.networkCapabilities.satisfiedByNetworkCapabilities( 394 mNetworkAgentInfo.networkCapabilities)) { 395 transitionTo(mValidatedState); 396 return HANDLED; 397 } 398 int httpResponseCode = isCaptivePortal(); 399 if (httpResponseCode == 204) { 400 transitionTo(mValidatedState); 401 } else if (httpResponseCode >= 200 && httpResponseCode <= 399) { 402 transitionTo(mUserPromptedState); 403 } else if (++mRetries > MAX_RETRIES) { 404 transitionTo(mOfflineState); 405 } else if (mReevaluateDelayMs >= 0) { 406 Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0); 407 sendMessageDelayed(msg, mReevaluateDelayMs); 408 } 409 return HANDLED; 410 case CMD_FORCE_REEVALUATION: 411 // Ignore duplicate requests. 412 return HANDLED; 413 default: 414 return NOT_HANDLED; 415 } 416 } 417 418 @Override 419 public void exit() { 420 TrafficStats.clearThreadStatsUid(); 421 } 422 } 423 424 // BroadcastReceiver that waits for a particular Intent and then posts a message. 425 private class CustomIntentReceiver extends BroadcastReceiver { 426 private final Message mMessage; 427 private final String mAction; 428 CustomIntentReceiver(String action, int token, int message) { 429 mMessage = obtainMessage(message, token); 430 mAction = action + "_" + mNetworkAgentInfo.network.netId + "_" + token; 431 mContext.registerReceiver(this, new IntentFilter(mAction)); 432 } 433 public PendingIntent getPendingIntent() { 434 return PendingIntent.getBroadcast(mContext, 0, new Intent(mAction), 0); 435 } 436 @Override 437 public void onReceive(Context context, Intent intent) { 438 if (intent.getAction().equals(mAction)) sendMessage(mMessage); 439 } 440 } 441 442 private class UserPromptedState extends State { 443 // Intent broadcast when user selects sign-in notification. 444 private static final String ACTION_SIGN_IN_REQUESTED = 445 "android.net.netmon.sign_in_requested"; 446 447 private CustomIntentReceiver mUserRespondedBroadcastReceiver; 448 449 @Override 450 public void enter() { 451 mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED, 452 NETWORK_TEST_RESULT_INVALID, 0, mNetworkAgentInfo)); 453 // Wait for user to select sign-in notifcation. 454 mUserRespondedBroadcastReceiver = new CustomIntentReceiver(ACTION_SIGN_IN_REQUESTED, 455 ++mUserPromptedToken, CMD_USER_WANTS_SIGN_IN); 456 // Initiate notification to sign-in. 457 Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 1, 458 mNetworkAgentInfo.network.netId, 459 mUserRespondedBroadcastReceiver.getPendingIntent()); 460 mConnectivityServiceHandler.sendMessage(message); 461 } 462 463 @Override 464 public boolean processMessage(Message message) { 465 if (DBG) log(getName() + message.toString()); 466 switch (message.what) { 467 case CMD_USER_WANTS_SIGN_IN: 468 if (message.arg1 != mUserPromptedToken) 469 return HANDLED; 470 transitionTo(mCaptivePortalState); 471 return HANDLED; 472 default: 473 return NOT_HANDLED; 474 } 475 } 476 477 @Override 478 public void exit() { 479 Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 0, 480 mNetworkAgentInfo.network.netId, null); 481 mConnectivityServiceHandler.sendMessage(message); 482 mContext.unregisterReceiver(mUserRespondedBroadcastReceiver); 483 mUserRespondedBroadcastReceiver = null; 484 } 485 } 486 487 private class CaptivePortalState extends State { 488 private class CaptivePortalLoggedInBroadcastReceiver extends BroadcastReceiver { 489 private final int mToken; 490 491 CaptivePortalLoggedInBroadcastReceiver(int token) { 492 mToken = token; 493 } 494 495 @Override 496 public void onReceive(Context context, Intent intent) { 497 if (Integer.parseInt(intent.getStringExtra(Intent.EXTRA_TEXT)) == 498 mNetworkAgentInfo.network.netId) { 499 sendMessage(obtainMessage(CMD_CAPTIVE_PORTAL_LOGGED_IN, mToken, 500 Integer.parseInt(intent.getStringExtra(LOGGED_IN_RESULT)))); 501 } 502 } 503 } 504 505 private CaptivePortalLoggedInBroadcastReceiver mCaptivePortalLoggedInBroadcastReceiver; 506 507 @Override 508 public void enter() { 509 Intent intent = new Intent(Intent.ACTION_SEND); 510 intent.putExtra(Intent.EXTRA_TEXT, String.valueOf(mNetworkAgentInfo.network.netId)); 511 intent.setType("text/plain"); 512 intent.setComponent(new ComponentName("com.android.captiveportallogin", 513 "com.android.captiveportallogin.CaptivePortalLoginActivity")); 514 intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK); 515 516 // Wait for result. 517 mCaptivePortalLoggedInBroadcastReceiver = new CaptivePortalLoggedInBroadcastReceiver( 518 ++mCaptivePortalLoggedInToken); 519 IntentFilter filter = new IntentFilter(ACTION_CAPTIVE_PORTAL_LOGGED_IN); 520 mContext.registerReceiver(mCaptivePortalLoggedInBroadcastReceiver, filter); 521 // Initiate app to log in. 522 mContext.startActivityAsUser(intent, UserHandle.CURRENT); 523 } 524 525 @Override 526 public boolean processMessage(Message message) { 527 if (DBG) log(getName() + message.toString()); 528 switch (message.what) { 529 case CMD_CAPTIVE_PORTAL_LOGGED_IN: 530 if (message.arg1 != mCaptivePortalLoggedInToken) 531 return HANDLED; 532 if (message.arg2 == 0) { 533 mUserDoesNotWant = true; 534 // TODO: Should teardown network. 535 transitionTo(mOfflineState); 536 } else { 537 transitionTo(mValidatedState); 538 } 539 return HANDLED; 540 default: 541 return NOT_HANDLED; 542 } 543 } 544 545 @Override 546 public void exit() { 547 mContext.unregisterReceiver(mCaptivePortalLoggedInBroadcastReceiver); 548 mCaptivePortalLoggedInBroadcastReceiver = null; 549 } 550 } 551 552 private class LingeringState extends State { 553 private static final String ACTION_LINGER_EXPIRED = "android.net.netmon.lingerExpired"; 554 555 private CustomIntentReceiver mBroadcastReceiver; 556 private PendingIntent mIntent; 557 558 @Override 559 public void enter() { 560 mBroadcastReceiver = new CustomIntentReceiver(ACTION_LINGER_EXPIRED, ++mLingerToken, 561 CMD_LINGER_EXPIRED); 562 mIntent = mBroadcastReceiver.getPendingIntent(); 563 long wakeupTime = SystemClock.elapsedRealtime() + mLingerDelayMs; 564 mAlarmManager.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP, wakeupTime, 565 // Give a specific window so we aren't subject to unknown inexactitude. 566 mLingerDelayMs / 6, mIntent); 567 } 568 569 @Override 570 public boolean processMessage(Message message) { 571 if (DBG) log(getName() + message.toString()); 572 switch (message.what) { 573 case CMD_NETWORK_CONNECTED: 574 // Go straight to active as we've already evaluated. 575 transitionTo(mValidatedState); 576 return HANDLED; 577 case CMD_LINGER_EXPIRED: 578 if (message.arg1 != mLingerToken) 579 return HANDLED; 580 mConnectivityServiceHandler.sendMessage( 581 obtainMessage(EVENT_NETWORK_LINGER_COMPLETE, mNetworkAgentInfo)); 582 return HANDLED; 583 case CMD_FORCE_REEVALUATION: 584 // Ignore reevaluation attempts when lingering. A reevaluation could result 585 // in a transition to the validated state which would abort the linger 586 // timeout. Lingering is the result of score assessment; validity is 587 // irrelevant. 588 return HANDLED; 589 default: 590 return NOT_HANDLED; 591 } 592 } 593 594 @Override 595 public void exit() { 596 mAlarmManager.cancel(mIntent); 597 mContext.unregisterReceiver(mBroadcastReceiver); 598 } 599 } 600 601 /** 602 * Do a URL fetch on a known server to see if we get the data we expect. 603 * Returns HTTP response code. 604 */ 605 private int isCaptivePortal() { 606 if (!mIsCaptivePortalCheckEnabled) return 204; 607 608 HttpURLConnection urlConnection = null; 609 int httpResponseCode = 599; 610 try { 611 URL url = new URL("http", mServer, "/generate_204"); 612 if (DBG) { 613 log("Checking " + url.toString() + " on " + 614 mNetworkAgentInfo.networkInfo.getExtraInfo()); 615 } 616 urlConnection = (HttpURLConnection) mNetworkAgentInfo.network.openConnection(url); 617 urlConnection.setInstanceFollowRedirects(false); 618 urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS); 619 urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS); 620 urlConnection.setUseCaches(false); 621 622 // Time how long it takes to get a response to our request 623 long requestTimestamp = SystemClock.elapsedRealtime(); 624 625 urlConnection.getInputStream(); 626 627 // Time how long it takes to get a response to our request 628 long responseTimestamp = SystemClock.elapsedRealtime(); 629 630 httpResponseCode = urlConnection.getResponseCode(); 631 if (DBG) { 632 log("isCaptivePortal: ret=" + httpResponseCode + 633 " headers=" + urlConnection.getHeaderFields()); 634 } 635 // NOTE: We may want to consider an "HTTP/1.0 204" response to be a captive 636 // portal. The only example of this seen so far was a captive portal. For 637 // the time being go with prior behavior of assuming it's not a captive 638 // portal. If it is considered a captive portal, a different sign-in URL 639 // is needed (i.e. can't browse a 204). This could be the result of an HTTP 640 // proxy server. 641 642 // Consider 200 response with "Content-length=0" to not be a captive portal. 643 // There's no point in considering this a captive portal as the user cannot 644 // sign-in to an empty page. Probably the result of a broken transparent proxy. 645 // See http://b/9972012. 646 if (httpResponseCode == 200 && urlConnection.getContentLength() == 0) { 647 if (DBG) log("Empty 200 response interpreted as 204 response."); 648 httpResponseCode = 204; 649 } 650 651 sendNetworkConditionsBroadcast(true /* response received */, httpResponseCode == 204, 652 requestTimestamp, responseTimestamp); 653 } catch (IOException e) { 654 if (DBG) log("Probably not a portal: exception " + e); 655 if (httpResponseCode == 599) { 656 // TODO: Ping gateway and DNS server and log results. 657 } 658 } finally { 659 if (urlConnection != null) { 660 urlConnection.disconnect(); 661 } 662 } 663 return httpResponseCode; 664 } 665 666 /** 667 * @param responseReceived - whether or not we received a valid HTTP response to our request. 668 * If false, isCaptivePortal and responseTimestampMs are ignored 669 * TODO: This should be moved to the transports. The latency could be passed to the transports 670 * along with the captive portal result. Currently the TYPE_MOBILE broadcasts appear unused so 671 * perhaps this could just be added to the WiFi transport only. 672 */ 673 private void sendNetworkConditionsBroadcast(boolean responseReceived, boolean isCaptivePortal, 674 long requestTimestampMs, long responseTimestampMs) { 675 if (Settings.Global.getInt(mContext.getContentResolver(), 676 Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 0) { 677 if (DBG) log("Don't send network conditions - lacking user consent."); 678 return; 679 } 680 681 if (systemReady == false) return; 682 683 Intent latencyBroadcast = new Intent(ACTION_NETWORK_CONDITIONS_MEASURED); 684 switch (mNetworkAgentInfo.networkInfo.getType()) { 685 case ConnectivityManager.TYPE_WIFI: 686 WifiInfo currentWifiInfo = mWifiManager.getConnectionInfo(); 687 if (currentWifiInfo != null) { 688 // NOTE: getSSID()'s behavior changed in API 17; before that, SSIDs were not 689 // surrounded by double quotation marks (thus violating the Javadoc), but this 690 // was changed to match the Javadoc in API 17. Since clients may have started 691 // sanitizing the output of this method since API 17 was released, we should 692 // not change it here as it would become impossible to tell whether the SSID is 693 // simply being surrounded by quotes due to the API, or whether those quotes 694 // are actually part of the SSID. 695 latencyBroadcast.putExtra(EXTRA_SSID, currentWifiInfo.getSSID()); 696 latencyBroadcast.putExtra(EXTRA_BSSID, currentWifiInfo.getBSSID()); 697 } else { 698 if (DBG) logw("network info is TYPE_WIFI but no ConnectionInfo found"); 699 return; 700 } 701 break; 702 case ConnectivityManager.TYPE_MOBILE: 703 latencyBroadcast.putExtra(EXTRA_NETWORK_TYPE, mTelephonyManager.getNetworkType()); 704 List<CellInfo> info = mTelephonyManager.getAllCellInfo(); 705 if (info == null) return; 706 int numRegisteredCellInfo = 0; 707 for (CellInfo cellInfo : info) { 708 if (cellInfo.isRegistered()) { 709 numRegisteredCellInfo++; 710 if (numRegisteredCellInfo > 1) { 711 if (DBG) log("more than one registered CellInfo. Can't " + 712 "tell which is active. Bailing."); 713 return; 714 } 715 if (cellInfo instanceof CellInfoCdma) { 716 CellIdentityCdma cellId = ((CellInfoCdma) cellInfo).getCellIdentity(); 717 latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId); 718 } else if (cellInfo instanceof CellInfoGsm) { 719 CellIdentityGsm cellId = ((CellInfoGsm) cellInfo).getCellIdentity(); 720 latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId); 721 } else if (cellInfo instanceof CellInfoLte) { 722 CellIdentityLte cellId = ((CellInfoLte) cellInfo).getCellIdentity(); 723 latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId); 724 } else if (cellInfo instanceof CellInfoWcdma) { 725 CellIdentityWcdma cellId = ((CellInfoWcdma) cellInfo).getCellIdentity(); 726 latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId); 727 } else { 728 if (DBG) logw("Registered cellinfo is unrecognized"); 729 return; 730 } 731 } 732 } 733 break; 734 default: 735 return; 736 } 737 latencyBroadcast.putExtra(EXTRA_CONNECTIVITY_TYPE, mNetworkAgentInfo.networkInfo.getType()); 738 latencyBroadcast.putExtra(EXTRA_RESPONSE_RECEIVED, responseReceived); 739 latencyBroadcast.putExtra(EXTRA_REQUEST_TIMESTAMP_MS, requestTimestampMs); 740 741 if (responseReceived) { 742 latencyBroadcast.putExtra(EXTRA_IS_CAPTIVE_PORTAL, isCaptivePortal); 743 latencyBroadcast.putExtra(EXTRA_RESPONSE_TIMESTAMP_MS, responseTimestampMs); 744 } 745 mContext.sendBroadcastAsUser(latencyBroadcast, UserHandle.CURRENT, 746 PERMISSION_ACCESS_NETWORK_CONDITIONS); 747 } 748} 749