blob: 3039ede6848409883e37d6c66e7d07bcd8ed52e6 [file] [log] [blame]
Paul Jensenca8f16a2014-05-09 12:47:55 -04001/*
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
Paul Jensen869868be2014-05-15 10:33:05 -040019import android.app.PendingIntent;
20import android.content.BroadcastReceiver;
21import android.content.ComponentName;
Paul Jensenca8f16a2014-05-09 12:47:55 -040022import android.content.Context;
Paul Jensen869868be2014-05-15 10:33:05 -040023import android.content.Intent;
24import android.content.IntentFilter;
25import android.net.ConnectivityManager;
26import android.net.Network;
Paul Jensenca8f16a2014-05-09 12:47:55 -040027import android.net.NetworkCapabilities;
28import android.net.NetworkInfo;
29import android.os.Handler;
30import android.os.Message;
31import android.os.SystemProperties;
Paul Jensen869868be2014-05-15 10:33:05 -040032import android.os.UserHandle;
Paul Jensenca8f16a2014-05-09 12:47:55 -040033import android.provider.Settings;
34
35import com.android.internal.util.Protocol;
36import com.android.internal.util.State;
37import com.android.internal.util.StateMachine;
Paul Jensen869868be2014-05-15 10:33:05 -040038import com.android.server.ConnectivityService;
Paul Jensenca8f16a2014-05-09 12:47:55 -040039import com.android.server.connectivity.NetworkAgentInfo;
40
Paul Jensenca8f16a2014-05-09 12:47:55 -040041import java.io.IOException;
Paul Jensenca8f16a2014-05-09 12:47:55 -040042import java.net.HttpURLConnection;
Paul Jensenca8f16a2014-05-09 12:47:55 -040043import java.net.URL;
44
45/**
46 * {@hide}
47 */
48public class NetworkMonitor extends StateMachine {
49 private static final boolean DBG = true;
50 private static final String TAG = "NetworkMonitor";
51 private static final String DEFAULT_SERVER = "clients3.google.com";
52 private static final int SOCKET_TIMEOUT_MS = 10000;
53
Paul Jensen869868be2014-05-15 10:33:05 -040054 // Intent broadcast when user selects sign-in notification.
55 private static final String ACTION_SIGN_IN_REQUESTED =
56 "android.net.netmon.sign_in_requested";
57
58 // Keep these in sync with CaptivePortalLoginActivity.java.
59 // Intent broadcast from CaptivePortalLogin indicating sign-in is complete.
60 // Extras:
61 // EXTRA_TEXT = netId
62 // LOGGED_IN_RESULT = "1" if we should use network, "0" if not.
63 private static final String ACTION_CAPTIVE_PORTAL_LOGGED_IN =
64 "android.net.netmon.captive_portal_logged_in";
65 private static final String LOGGED_IN_RESULT = "result";
66
Paul Jensenca8f16a2014-05-09 12:47:55 -040067 private static final int BASE = Protocol.BASE_NETWORK_MONITOR;
68
69 /**
70 * Inform NetworkMonitor that their network is connected.
71 * Initiates Network Validation.
72 */
73 public static final int CMD_NETWORK_CONNECTED = BASE + 1;
74
75 /**
76 * Inform ConnectivityService that the network is validated.
77 * obj = NetworkAgentInfo
78 */
79 public static final int EVENT_NETWORK_VALIDATED = BASE + 2;
80
81 /**
82 * Inform NetworkMonitor to linger a network. The Monitor should
83 * start a timer and/or start watching for zero live connections while
84 * moving towards LINGER_COMPLETE. After the Linger period expires
85 * (or other events mark the end of the linger state) the LINGER_COMPLETE
86 * event should be sent and the network will be shut down. If a
87 * CMD_NETWORK_CONNECTED happens before the LINGER completes
88 * it indicates further desire to keep the network alive and so
89 * the LINGER is aborted.
90 */
91 public static final int CMD_NETWORK_LINGER = BASE + 3;
92
93 /**
94 * Message to self indicating linger delay has expired.
95 * arg1 = Token to ignore old messages.
96 */
97 private static final int CMD_LINGER_EXPIRED = BASE + 4;
98
99 /**
100 * Inform ConnectivityService that the network LINGER period has
101 * expired.
102 * obj = NetworkAgentInfo
103 */
104 public static final int EVENT_NETWORK_LINGER_COMPLETE = BASE + 5;
105
106 /**
Paul Jensenca8f16a2014-05-09 12:47:55 -0400107 * Message to self indicating it's time to evaluate a network's connectivity.
108 * arg1 = Token to ignore old messages.
109 */
Paul Jensen869868be2014-05-15 10:33:05 -0400110 private static final int CMD_REEVALUATE = BASE + 6;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400111
112 /**
113 * Message to self indicating network evaluation is complete.
114 * arg1 = Token to ignore old messages.
115 * arg2 = HTTP response code of network evaluation.
116 */
Paul Jensen869868be2014-05-15 10:33:05 -0400117 private static final int EVENT_REEVALUATION_COMPLETE = BASE + 7;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400118
119 /**
120 * Inform NetworkMonitor that the network has disconnected.
121 */
Paul Jensen869868be2014-05-15 10:33:05 -0400122 public static final int CMD_NETWORK_DISCONNECTED = BASE + 8;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400123
124 /**
125 * Force evaluation even if it has succeeded in the past.
126 */
Paul Jensen869868be2014-05-15 10:33:05 -0400127 public static final int CMD_FORCE_REEVALUATION = BASE + 9;
128
129 /**
130 * Message to self indicating captive portal login is complete.
131 * arg1 = Token to ignore old messages.
132 * arg2 = 1 if we should use this network, 0 otherwise.
133 */
134 private static final int CMD_CAPTIVE_PORTAL_LOGGED_IN = BASE + 10;
135
136 /**
137 * Message to self indicating user desires to log into captive portal.
138 * arg1 = Token to ignore old messages.
139 */
140 private static final int CMD_USER_WANTS_SIGN_IN = BASE + 11;
141
142 /**
143 * Request ConnectivityService display provisioning notification.
144 * arg1 = Whether to make the notification visible.
Paul Jensenfdc4e4a2014-07-15 12:07:36 -0400145 * arg2 = NetID.
146 * obj = Intent to be launched when notification selected by user, null if !arg1.
Paul Jensen869868be2014-05-15 10:33:05 -0400147 */
148 public static final int EVENT_PROVISIONING_NOTIFICATION = BASE + 12;
149
150 /**
151 * Message to self indicating sign-in app bypassed captive portal.
152 */
153 private static final int EVENT_APP_BYPASSED_CAPTIVE_PORTAL = BASE + 13;
154
155 /**
156 * Message to self indicating no sign-in app responded.
157 */
158 private static final int EVENT_NO_APP_RESPONSE = BASE + 14;
159
160 /**
161 * Message to self indicating sign-in app indicates sign-in is not possible.
162 */
163 private static final int EVENT_APP_INDICATES_SIGN_IN_IMPOSSIBLE = BASE + 15;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400164
165 private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger";
166 // Default to 30s linger time-out.
167 private static final int DEFAULT_LINGER_DELAY_MS = 30000;
168 private final int mLingerDelayMs;
169 private int mLingerToken = 0;
170
Paul Jensenca8f16a2014-05-09 12:47:55 -0400171 // Negative values disable reevaluation.
172 private static final String REEVALUATE_DELAY_PROPERTY = "persist.netmon.reeval_delay";
173 // Default to 5s reevaluation delay.
174 private static final int DEFAULT_REEVALUATE_DELAY_MS = 5000;
Paul Jensen869868be2014-05-15 10:33:05 -0400175 private static final int MAX_RETRIES = 10;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400176 private final int mReevaluateDelayMs;
177 private int mReevaluateToken = 0;
178
Paul Jensen869868be2014-05-15 10:33:05 -0400179 private int mCaptivePortalLoggedInToken = 0;
180 private int mUserPromptedToken = 0;
181
Paul Jensenca8f16a2014-05-09 12:47:55 -0400182 private final Context mContext;
183 private final Handler mConnectivityServiceHandler;
184 private final NetworkAgentInfo mNetworkAgentInfo;
185
186 private String mServer;
187 private boolean mIsCaptivePortalCheckEnabled = false;
188
189 private State mDefaultState = new DefaultState();
190 private State mOfflineState = new OfflineState();
191 private State mValidatedState = new ValidatedState();
192 private State mEvaluatingState = new EvaluatingState();
Paul Jensen869868be2014-05-15 10:33:05 -0400193 private State mUninteractiveAppsPromptedState = new UninteractiveAppsPromptedState();
194 private State mUserPromptedState = new UserPromptedState();
195 private State mInteractiveAppsPromptedState = new InteractiveAppsPromptedState();
Paul Jensenca8f16a2014-05-09 12:47:55 -0400196 private State mCaptivePortalState = new CaptivePortalState();
197 private State mLingeringState = new LingeringState();
198
199 public NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo) {
200 // Add suffix indicating which NetworkMonitor we're talking about.
201 super(TAG + networkAgentInfo.name());
202
203 mContext = context;
204 mConnectivityServiceHandler = handler;
205 mNetworkAgentInfo = networkAgentInfo;
206
207 addState(mDefaultState);
208 addState(mOfflineState, mDefaultState);
209 addState(mValidatedState, mDefaultState);
210 addState(mEvaluatingState, mDefaultState);
Paul Jensen869868be2014-05-15 10:33:05 -0400211 addState(mUninteractiveAppsPromptedState, mDefaultState);
212 addState(mUserPromptedState, mDefaultState);
213 addState(mInteractiveAppsPromptedState, mDefaultState);
Paul Jensenca8f16a2014-05-09 12:47:55 -0400214 addState(mCaptivePortalState, mDefaultState);
215 addState(mLingeringState, mDefaultState);
216 setInitialState(mOfflineState);
217
218 mServer = Settings.Global.getString(mContext.getContentResolver(),
219 Settings.Global.CAPTIVE_PORTAL_SERVER);
220 if (mServer == null) mServer = DEFAULT_SERVER;
221
222 mLingerDelayMs = SystemProperties.getInt(LINGER_DELAY_PROPERTY, DEFAULT_LINGER_DELAY_MS);
223 mReevaluateDelayMs = SystemProperties.getInt(REEVALUATE_DELAY_PROPERTY,
224 DEFAULT_REEVALUATE_DELAY_MS);
225
Paul Jensen869868be2014-05-15 10:33:05 -0400226 mIsCaptivePortalCheckEnabled = Settings.Global.getInt(mContext.getContentResolver(),
227 Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED, 1) == 1;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400228
229 start();
230 }
231
232 private class DefaultState extends State {
233 @Override
234 public boolean processMessage(Message message) {
235 if (DBG) log(getName() + message.toString());
236 switch (message.what) {
237 case CMD_NETWORK_LINGER:
238 if (DBG) log("Lingering");
239 transitionTo(mLingeringState);
240 break;
241 case CMD_NETWORK_CONNECTED:
242 if (DBG) log("Connected");
243 transitionTo(mEvaluatingState);
244 break;
245 case CMD_NETWORK_DISCONNECTED:
Robert Greenwalt1fd9aee2014-07-17 16:11:38 -0700246 if (DBG) log("Disconnected - quitting");
247 quit();
Paul Jensenca8f16a2014-05-09 12:47:55 -0400248 break;
249 case CMD_FORCE_REEVALUATION:
250 if (DBG) log("Forcing reevaluation");
251 transitionTo(mEvaluatingState);
252 break;
253 default:
254 break;
255 }
256 return HANDLED;
257 }
258 }
259
260 private class OfflineState extends State {
261 @Override
262 public boolean processMessage(Message message) {
263 if (DBG) log(getName() + message.toString());
264 return NOT_HANDLED;
265 }
266 }
267
268 private class ValidatedState extends State {
269 @Override
270 public void enter() {
271 if (DBG) log("Validated");
272 mConnectivityServiceHandler.sendMessage(
273 obtainMessage(EVENT_NETWORK_VALIDATED, mNetworkAgentInfo));
274 }
275
276 @Override
277 public boolean processMessage(Message message) {
278 if (DBG) log(getName() + message.toString());
279 switch (message.what) {
280 case CMD_NETWORK_CONNECTED:
281 transitionTo(mValidatedState);
282 break;
283 default:
284 return NOT_HANDLED;
285 }
286 return HANDLED;
287 }
288 }
289
290 private class EvaluatingState extends State {
Paul Jensen869868be2014-05-15 10:33:05 -0400291 private int mRetries;
292
Paul Jensenca8f16a2014-05-09 12:47:55 -0400293 private class EvaluateInternetConnectivity extends Thread {
294 private int mToken;
295 EvaluateInternetConnectivity(int token) {
296 mToken = token;
297 }
298 public void run() {
299 sendMessage(EVENT_REEVALUATION_COMPLETE, mToken, isCaptivePortal());
300 }
301 }
302
303 @Override
304 public void enter() {
Paul Jensen869868be2014-05-15 10:33:05 -0400305 mRetries = 0;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400306 sendMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
307 }
308
309 @Override
310 public boolean processMessage(Message message) {
311 if (DBG) log(getName() + message.toString());
312 switch (message.what) {
313 case CMD_REEVALUATE:
314 if (message.arg1 != mReevaluateToken)
315 break;
Paul Jensen6bc2c2c2014-05-07 15:27:40 -0400316 if (mNetworkAgentInfo.isVPN()) {
317 transitionTo(mValidatedState);
318 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400319 // If network provides no internet connectivity adjust evaluation.
Paul Jensen869868be2014-05-15 10:33:05 -0400320 if (!mNetworkAgentInfo.networkCapabilities.hasCapability(
Paul Jensenca8f16a2014-05-09 12:47:55 -0400321 NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
322 // TODO: Try to verify something works. Do all gateways respond to pings?
323 transitionTo(mValidatedState);
324 }
325 // Kick off a thread to perform internet connectivity evaluation.
326 Thread thread = new EvaluateInternetConnectivity(mReevaluateToken);
327 thread.run();
328 break;
329 case EVENT_REEVALUATION_COMPLETE:
330 if (message.arg1 != mReevaluateToken)
331 break;
332 int httpResponseCode = message.arg2;
333 if (httpResponseCode == 204) {
334 transitionTo(mValidatedState);
335 } else if (httpResponseCode >= 200 && httpResponseCode <= 399) {
Paul Jensen869868be2014-05-15 10:33:05 -0400336 transitionTo(mUninteractiveAppsPromptedState);
337 } else if (++mRetries > MAX_RETRIES) {
338 transitionTo(mOfflineState);
339 } else if (mReevaluateDelayMs >= 0) {
340 Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
341 sendMessageDelayed(msg, mReevaluateDelayMs);
Paul Jensenca8f16a2014-05-09 12:47:55 -0400342 }
343 break;
344 default:
345 return NOT_HANDLED;
346 }
347 return HANDLED;
348 }
349 }
350
Paul Jensen869868be2014-05-15 10:33:05 -0400351 private class AppRespondedBroadcastReceiver extends BroadcastReceiver {
352 private static final int CAPTIVE_PORTAL_UNINITIALIZED_RETURN_CODE = 0;
353 private boolean mCanceled;
354 AppRespondedBroadcastReceiver() {
355 mCanceled = false;
356 }
357 public void send(String action) {
358 Intent intent = new Intent(action);
359 intent.putExtra(ConnectivityManager.EXTRA_NETWORK, mNetworkAgentInfo.network);
360 mContext.sendOrderedBroadcastAsUser(intent, UserHandle.ALL, null, this, getHandler(),
361 CAPTIVE_PORTAL_UNINITIALIZED_RETURN_CODE, null, null);
362 }
363 public void cancel() {
364 mCanceled = true;
365 }
366 @Override
367 public void onReceive(Context context, Intent intent) {
368 if (!mCanceled) {
369 cancel();
370 switch (getResultCode()) {
371 case ConnectivityManager.CAPTIVE_PORTAL_SIGNED_IN:
372 sendMessage(EVENT_APP_BYPASSED_CAPTIVE_PORTAL);
373 break;
374 case ConnectivityManager.CAPTIVE_PORTAL_DISCONNECT:
375 sendMessage(EVENT_APP_INDICATES_SIGN_IN_IMPOSSIBLE);
376 break;
377 // NOTE: This case label makes compiler enforce no overlap between result codes.
378 case CAPTIVE_PORTAL_UNINITIALIZED_RETURN_CODE:
379 default:
380 sendMessage(EVENT_NO_APP_RESPONSE);
381 break;
382 }
383 }
384 }
385 }
386
387 private class UninteractiveAppsPromptedState extends State {
388 private AppRespondedBroadcastReceiver mReceiver;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400389 @Override
390 public void enter() {
Paul Jensen869868be2014-05-15 10:33:05 -0400391 mReceiver = new AppRespondedBroadcastReceiver();
392 mReceiver.send(ConnectivityManager.ACTION_CAPTIVE_PORTAL_DETECTED);
393 }
394 @Override
395 public boolean processMessage(Message message) {
396 if (DBG) log(getName() + message.toString());
397 switch (message.what) {
398 case EVENT_APP_BYPASSED_CAPTIVE_PORTAL:
399 transitionTo(mValidatedState);
400 break;
401 case EVENT_APP_INDICATES_SIGN_IN_IMPOSSIBLE:
402 transitionTo(mOfflineState);
403 break;
404 case EVENT_NO_APP_RESPONSE:
405 transitionTo(mUserPromptedState);
406 break;
407 default:
408 return NOT_HANDLED;
409 }
410 return HANDLED;
411 }
412 public void exit() {
413 mReceiver.cancel();
414 }
415 }
416
417 private class UserPromptedState extends State {
418 private class UserRespondedBroadcastReceiver extends BroadcastReceiver {
419 private final int mToken;
420 UserRespondedBroadcastReceiver(int token) {
421 mToken = token;
422 }
423 @Override
424 public void onReceive(Context context, Intent intent) {
425 if (Integer.parseInt(intent.getStringExtra(Intent.EXTRA_TEXT)) ==
426 mNetworkAgentInfo.network.netId) {
427 sendMessage(obtainMessage(CMD_USER_WANTS_SIGN_IN, mToken));
428 }
429 }
430 }
431
432 private UserRespondedBroadcastReceiver mUserRespondedBroadcastReceiver;
433
434 @Override
435 public void enter() {
436 // Wait for user to select sign-in notifcation.
437 mUserRespondedBroadcastReceiver = new UserRespondedBroadcastReceiver(
438 ++mUserPromptedToken);
439 IntentFilter filter = new IntentFilter(ACTION_SIGN_IN_REQUESTED);
440 mContext.registerReceiver(mUserRespondedBroadcastReceiver, filter);
441 // Initiate notification to sign-in.
442 Intent intent = new Intent(ACTION_SIGN_IN_REQUESTED);
443 intent.putExtra(Intent.EXTRA_TEXT, String.valueOf(mNetworkAgentInfo.network.netId));
Paul Jensenfdc4e4a2014-07-15 12:07:36 -0400444 Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 1,
445 mNetworkAgentInfo.network.netId,
Paul Jensen869868be2014-05-15 10:33:05 -0400446 PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT));
Paul Jensen869868be2014-05-15 10:33:05 -0400447 mConnectivityServiceHandler.sendMessage(message);
Paul Jensenca8f16a2014-05-09 12:47:55 -0400448 }
449
450 @Override
451 public boolean processMessage(Message message) {
452 if (DBG) log(getName() + message.toString());
453 switch (message.what) {
Paul Jensen869868be2014-05-15 10:33:05 -0400454 case CMD_USER_WANTS_SIGN_IN:
455 if (message.arg1 != mUserPromptedToken)
Paul Jensenca8f16a2014-05-09 12:47:55 -0400456 break;
Paul Jensen869868be2014-05-15 10:33:05 -0400457 transitionTo(mInteractiveAppsPromptedState);
Paul Jensenca8f16a2014-05-09 12:47:55 -0400458 break;
459 default:
460 return NOT_HANDLED;
461 }
462 return HANDLED;
463 }
Paul Jensen869868be2014-05-15 10:33:05 -0400464
465 @Override
466 public void exit() {
Paul Jensenfdc4e4a2014-07-15 12:07:36 -0400467 Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 0,
468 mNetworkAgentInfo.network.netId, null);
Paul Jensen869868be2014-05-15 10:33:05 -0400469 mConnectivityServiceHandler.sendMessage(message);
470 mContext.unregisterReceiver(mUserRespondedBroadcastReceiver);
471 mUserRespondedBroadcastReceiver = null;
472 }
473 }
474
475 private class InteractiveAppsPromptedState extends State {
476 private AppRespondedBroadcastReceiver mReceiver;
477 @Override
478 public void enter() {
479 mReceiver = new AppRespondedBroadcastReceiver();
480 mReceiver.send(ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN);
481 }
482 @Override
483 public boolean processMessage(Message message) {
484 if (DBG) log(getName() + message.toString());
485 switch (message.what) {
486 case EVENT_APP_BYPASSED_CAPTIVE_PORTAL:
487 transitionTo(mValidatedState);
488 break;
489 case EVENT_APP_INDICATES_SIGN_IN_IMPOSSIBLE:
490 transitionTo(mOfflineState);
491 break;
492 case EVENT_NO_APP_RESPONSE:
493 transitionTo(mCaptivePortalState);
494 break;
495 default:
496 return NOT_HANDLED;
497 }
498 return HANDLED;
499 }
500 public void exit() {
501 mReceiver.cancel();
502 }
503 }
504
505 private class CaptivePortalState extends State {
506 private class CaptivePortalLoggedInBroadcastReceiver extends BroadcastReceiver {
507 private final int mToken;
508
509 CaptivePortalLoggedInBroadcastReceiver(int token) {
510 mToken = token;
511 }
512
513 @Override
514 public void onReceive(Context context, Intent intent) {
515 if (Integer.parseInt(intent.getStringExtra(Intent.EXTRA_TEXT)) ==
516 mNetworkAgentInfo.network.netId) {
517 sendMessage(obtainMessage(CMD_CAPTIVE_PORTAL_LOGGED_IN, mToken,
518 Integer.parseInt(intent.getStringExtra(LOGGED_IN_RESULT))));
519 }
520 }
521 }
522
523 private CaptivePortalLoggedInBroadcastReceiver mCaptivePortalLoggedInBroadcastReceiver;
524
525 @Override
526 public void enter() {
527 Intent intent = new Intent(Intent.ACTION_SEND);
528 intent.putExtra(Intent.EXTRA_TEXT, String.valueOf(mNetworkAgentInfo.network.netId));
529 intent.setType("text/plain");
530 intent.setComponent(new ComponentName("com.android.captiveportallogin",
531 "com.android.captiveportallogin.CaptivePortalLoginActivity"));
532 intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
533
534 // Wait for result.
535 mCaptivePortalLoggedInBroadcastReceiver = new CaptivePortalLoggedInBroadcastReceiver(
536 ++mCaptivePortalLoggedInToken);
537 IntentFilter filter = new IntentFilter(ACTION_CAPTIVE_PORTAL_LOGGED_IN);
538 mContext.registerReceiver(mCaptivePortalLoggedInBroadcastReceiver, filter);
539 // Initiate app to log in.
540 mContext.startActivityAsUser(intent, UserHandle.CURRENT);
541 }
542
543 @Override
544 public boolean processMessage(Message message) {
545 if (DBG) log(getName() + message.toString());
546 switch (message.what) {
547 case CMD_CAPTIVE_PORTAL_LOGGED_IN:
548 if (message.arg1 != mCaptivePortalLoggedInToken)
549 break;
550 if (message.arg2 == 0) {
551 // TODO: Should teardown network.
552 transitionTo(mOfflineState);
553 } else {
554 transitionTo(mValidatedState);
555 }
556 break;
557 default:
558 return NOT_HANDLED;
559 }
560 return HANDLED;
561 }
562
563 @Override
564 public void exit() {
565 mContext.unregisterReceiver(mCaptivePortalLoggedInBroadcastReceiver);
566 mCaptivePortalLoggedInBroadcastReceiver = null;
567 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400568 }
569
570 private class LingeringState extends State {
571 @Override
572 public void enter() {
573 Message message = obtainMessage(CMD_LINGER_EXPIRED, ++mLingerToken, 0);
574 sendMessageDelayed(message, mLingerDelayMs);
575 }
576
577 @Override
578 public boolean processMessage(Message message) {
579 if (DBG) log(getName() + message.toString());
580 switch (message.what) {
581 case CMD_NETWORK_CONNECTED:
582 // Go straight to active as we've already evaluated.
583 transitionTo(mValidatedState);
584 break;
585 case CMD_LINGER_EXPIRED:
586 if (message.arg1 != mLingerToken)
587 break;
588 mConnectivityServiceHandler.sendMessage(
589 obtainMessage(EVENT_NETWORK_LINGER_COMPLETE, mNetworkAgentInfo));
590 break;
591 default:
592 return NOT_HANDLED;
593 }
594 return HANDLED;
595 }
596 }
597
598 /**
599 * Do a URL fetch on a known server to see if we get the data we expect.
600 * Returns HTTP response code.
601 */
602 private int isCaptivePortal() {
603 if (!mIsCaptivePortalCheckEnabled) return 204;
604
Paul Jensenca8f16a2014-05-09 12:47:55 -0400605 HttpURLConnection urlConnection = null;
Paul Jensen869868be2014-05-15 10:33:05 -0400606 int httpResponseCode = 599;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400607 try {
Paul Jensene547ff22014-08-04 09:12:24 -0400608 URL url = new URL("http", mServer, "/generate_204");
609 if (DBG) {
610 log("Checking " + url.toString() + " on " +
611 mNetworkAgentInfo.networkInfo.getExtraInfo());
Paul Jensenca8f16a2014-05-09 12:47:55 -0400612 }
Paul Jensene547ff22014-08-04 09:12:24 -0400613 url = mNetworkAgentInfo.network.getBoundURL(url);
614 urlConnection = (HttpURLConnection) url.openConnection();
615 urlConnection.setInstanceFollowRedirects(false);
616 urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
617 urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
618 urlConnection.setUseCaches(false);
619 urlConnection.getInputStream();
620 httpResponseCode = urlConnection.getResponseCode();
621 if (DBG) {
622 log("isCaptivePortal: ret=" + httpResponseCode +
623 " headers=" + urlConnection.getHeaderFields());
624 }
625 // NOTE: We may want to consider an "HTTP/1.0 204" response to be a captive
626 // portal. The only example of this seen so far was a captive portal. For
627 // the time being go with prior behavior of assuming it's not a captive
628 // portal. If it is considered a captive portal, a different sign-in URL
629 // is needed (i.e. can't browse a 204). This could be the result of an HTTP
630 // proxy server.
631
632 // Consider 200 response with "Content-length=0" to not be a captive portal.
633 // There's no point in considering this a captive portal as the user cannot
634 // sign-in to an empty page. Probably the result of a broken transparent proxy.
635 // See http://b/9972012.
636 if (httpResponseCode == 200 && urlConnection.getContentLength() == 0) {
637 if (DBG) log("Empty 200 response interpreted as 204 response.");
638 httpResponseCode = 204;
639 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400640 } catch (IOException e) {
641 if (DBG) log("Probably not a portal: exception " + e);
Paul Jensen869868be2014-05-15 10:33:05 -0400642 if (httpResponseCode == 599) {
643 // TODO: Ping gateway and DNS server and log results.
644 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400645 } finally {
646 if (urlConnection != null) {
647 urlConnection.disconnect();
648 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400649 }
650 return httpResponseCode;
651 }
652}