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