blob: 98a5b618f2a3085076c0c3598abe57d82a2fdd1f [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 Jensen79a08052014-08-21 12:44:07 -040019import android.app.AlarmManager;
Paul Jensen869868be2014-05-15 10:33:05 -040020import android.app.PendingIntent;
21import android.content.BroadcastReceiver;
22import android.content.ComponentName;
Paul Jensenca8f16a2014-05-09 12:47:55 -040023import android.content.Context;
Paul Jensen869868be2014-05-15 10:33:05 -040024import android.content.Intent;
25import android.content.IntentFilter;
26import android.net.ConnectivityManager;
27import android.net.Network;
Paul Jensenca8f16a2014-05-09 12:47:55 -040028import android.net.NetworkCapabilities;
29import android.net.NetworkInfo;
Paul Jensen306f1a42014-08-04 10:59:01 -040030import android.net.wifi.WifiInfo;
31import android.net.wifi.WifiManager;
Paul Jensenca8f16a2014-05-09 12:47:55 -040032import android.os.Handler;
33import android.os.Message;
Paul Jensen306f1a42014-08-04 10:59:01 -040034import android.os.SystemClock;
Paul Jensenca8f16a2014-05-09 12:47:55 -040035import android.os.SystemProperties;
Paul Jensen869868be2014-05-15 10:33:05 -040036import android.os.UserHandle;
Paul Jensenca8f16a2014-05-09 12:47:55 -040037import android.provider.Settings;
Paul Jensen306f1a42014-08-04 10:59:01 -040038import android.telephony.CellIdentityCdma;
39import android.telephony.CellIdentityGsm;
40import android.telephony.CellIdentityLte;
41import android.telephony.CellIdentityWcdma;
42import android.telephony.CellInfo;
43import android.telephony.CellInfoCdma;
44import android.telephony.CellInfoGsm;
45import android.telephony.CellInfoLte;
46import android.telephony.CellInfoWcdma;
47import android.telephony.TelephonyManager;
Paul Jensenca8f16a2014-05-09 12:47:55 -040048
49import com.android.internal.util.Protocol;
50import com.android.internal.util.State;
51import com.android.internal.util.StateMachine;
Paul Jensen869868be2014-05-15 10:33:05 -040052import com.android.server.ConnectivityService;
Paul Jensenca8f16a2014-05-09 12:47:55 -040053import com.android.server.connectivity.NetworkAgentInfo;
54
Paul Jensenca8f16a2014-05-09 12:47:55 -040055import java.io.IOException;
Paul Jensenca8f16a2014-05-09 12:47:55 -040056import java.net.HttpURLConnection;
Paul Jensenca8f16a2014-05-09 12:47:55 -040057import java.net.URL;
Paul Jensen306f1a42014-08-04 10:59:01 -040058import java.util.List;
Paul Jensenca8f16a2014-05-09 12:47:55 -040059
60/**
61 * {@hide}
62 */
63public class NetworkMonitor extends StateMachine {
64 private static final boolean DBG = true;
65 private static final String TAG = "NetworkMonitor";
66 private static final String DEFAULT_SERVER = "clients3.google.com";
67 private static final int SOCKET_TIMEOUT_MS = 10000;
Paul Jensen306f1a42014-08-04 10:59:01 -040068 public static final String ACTION_NETWORK_CONDITIONS_MEASURED =
69 "android.net.conn.NETWORK_CONDITIONS_MEASURED";
70 public static final String EXTRA_CONNECTIVITY_TYPE = "extra_connectivity_type";
71 public static final String EXTRA_NETWORK_TYPE = "extra_network_type";
72 public static final String EXTRA_RESPONSE_RECEIVED = "extra_response_received";
73 public static final String EXTRA_IS_CAPTIVE_PORTAL = "extra_is_captive_portal";
74 public static final String EXTRA_CELL_ID = "extra_cellid";
75 public static final String EXTRA_SSID = "extra_ssid";
76 public static final String EXTRA_BSSID = "extra_bssid";
77 /** real time since boot */
78 public static final String EXTRA_REQUEST_TIMESTAMP_MS = "extra_request_timestamp_ms";
79 public static final String EXTRA_RESPONSE_TIMESTAMP_MS = "extra_response_timestamp_ms";
80
81 private static final String PERMISSION_ACCESS_NETWORK_CONDITIONS =
82 "android.permission.ACCESS_NETWORK_CONDITIONS";
Paul Jensenca8f16a2014-05-09 12:47:55 -040083
Paul Jensen869868be2014-05-15 10:33:05 -040084 // Intent broadcast when user selects sign-in notification.
85 private static final String ACTION_SIGN_IN_REQUESTED =
86 "android.net.netmon.sign_in_requested";
87
88 // Keep these in sync with CaptivePortalLoginActivity.java.
89 // Intent broadcast from CaptivePortalLogin indicating sign-in is complete.
90 // Extras:
91 // EXTRA_TEXT = netId
92 // LOGGED_IN_RESULT = "1" if we should use network, "0" if not.
93 private static final String ACTION_CAPTIVE_PORTAL_LOGGED_IN =
94 "android.net.netmon.captive_portal_logged_in";
95 private static final String LOGGED_IN_RESULT = "result";
96
Paul Jensenca8f16a2014-05-09 12:47:55 -040097 private static final int BASE = Protocol.BASE_NETWORK_MONITOR;
98
99 /**
100 * Inform NetworkMonitor that their network is connected.
101 * Initiates Network Validation.
102 */
103 public static final int CMD_NETWORK_CONNECTED = BASE + 1;
104
105 /**
106 * Inform ConnectivityService that the network is validated.
107 * obj = NetworkAgentInfo
108 */
109 public static final int EVENT_NETWORK_VALIDATED = BASE + 2;
110
111 /**
112 * Inform NetworkMonitor to linger a network. The Monitor should
113 * start a timer and/or start watching for zero live connections while
114 * moving towards LINGER_COMPLETE. After the Linger period expires
115 * (or other events mark the end of the linger state) the LINGER_COMPLETE
116 * event should be sent and the network will be shut down. If a
117 * CMD_NETWORK_CONNECTED happens before the LINGER completes
118 * it indicates further desire to keep the network alive and so
119 * the LINGER is aborted.
120 */
121 public static final int CMD_NETWORK_LINGER = BASE + 3;
122
123 /**
124 * Message to self indicating linger delay has expired.
125 * arg1 = Token to ignore old messages.
126 */
127 private static final int CMD_LINGER_EXPIRED = BASE + 4;
128
129 /**
130 * Inform ConnectivityService that the network LINGER period has
131 * expired.
132 * obj = NetworkAgentInfo
133 */
134 public static final int EVENT_NETWORK_LINGER_COMPLETE = BASE + 5;
135
136 /**
Paul Jensenca8f16a2014-05-09 12:47:55 -0400137 * Message to self indicating it's time to evaluate a network's connectivity.
138 * arg1 = Token to ignore old messages.
139 */
Paul Jensen869868be2014-05-15 10:33:05 -0400140 private static final int CMD_REEVALUATE = BASE + 6;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400141
142 /**
143 * Message to self indicating network evaluation is complete.
144 * arg1 = Token to ignore old messages.
145 * arg2 = HTTP response code of network evaluation.
146 */
Paul Jensen869868be2014-05-15 10:33:05 -0400147 private static final int EVENT_REEVALUATION_COMPLETE = BASE + 7;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400148
149 /**
150 * Inform NetworkMonitor that the network has disconnected.
151 */
Paul Jensen869868be2014-05-15 10:33:05 -0400152 public static final int CMD_NETWORK_DISCONNECTED = BASE + 8;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400153
154 /**
155 * Force evaluation even if it has succeeded in the past.
156 */
Paul Jensen869868be2014-05-15 10:33:05 -0400157 public static final int CMD_FORCE_REEVALUATION = BASE + 9;
158
159 /**
160 * Message to self indicating captive portal login is complete.
161 * arg1 = Token to ignore old messages.
162 * arg2 = 1 if we should use this network, 0 otherwise.
163 */
164 private static final int CMD_CAPTIVE_PORTAL_LOGGED_IN = BASE + 10;
165
166 /**
167 * Message to self indicating user desires to log into captive portal.
168 * arg1 = Token to ignore old messages.
169 */
170 private static final int CMD_USER_WANTS_SIGN_IN = BASE + 11;
171
172 /**
173 * Request ConnectivityService display provisioning notification.
174 * arg1 = Whether to make the notification visible.
Paul Jensenfdc4e4a2014-07-15 12:07:36 -0400175 * arg2 = NetID.
176 * obj = Intent to be launched when notification selected by user, null if !arg1.
Paul Jensen869868be2014-05-15 10:33:05 -0400177 */
178 public static final int EVENT_PROVISIONING_NOTIFICATION = BASE + 12;
179
180 /**
181 * Message to self indicating sign-in app bypassed captive portal.
182 */
183 private static final int EVENT_APP_BYPASSED_CAPTIVE_PORTAL = BASE + 13;
184
185 /**
186 * Message to self indicating no sign-in app responded.
187 */
188 private static final int EVENT_NO_APP_RESPONSE = BASE + 14;
189
190 /**
191 * Message to self indicating sign-in app indicates sign-in is not possible.
192 */
193 private static final int EVENT_APP_INDICATES_SIGN_IN_IMPOSSIBLE = BASE + 15;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400194
195 private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger";
196 // Default to 30s linger time-out.
197 private static final int DEFAULT_LINGER_DELAY_MS = 30000;
198 private final int mLingerDelayMs;
199 private int mLingerToken = 0;
200
Paul Jensenca8f16a2014-05-09 12:47:55 -0400201 // Negative values disable reevaluation.
202 private static final String REEVALUATE_DELAY_PROPERTY = "persist.netmon.reeval_delay";
203 // Default to 5s reevaluation delay.
204 private static final int DEFAULT_REEVALUATE_DELAY_MS = 5000;
Paul Jensen869868be2014-05-15 10:33:05 -0400205 private static final int MAX_RETRIES = 10;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400206 private final int mReevaluateDelayMs;
207 private int mReevaluateToken = 0;
208
Paul Jensen869868be2014-05-15 10:33:05 -0400209 private int mCaptivePortalLoggedInToken = 0;
210 private int mUserPromptedToken = 0;
211
Paul Jensenca8f16a2014-05-09 12:47:55 -0400212 private final Context mContext;
213 private final Handler mConnectivityServiceHandler;
214 private final NetworkAgentInfo mNetworkAgentInfo;
Paul Jensen306f1a42014-08-04 10:59:01 -0400215 private final TelephonyManager mTelephonyManager;
216 private final WifiManager mWifiManager;
Paul Jensen79a08052014-08-21 12:44:07 -0400217 private final AlarmManager mAlarmManager;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400218
219 private String mServer;
220 private boolean mIsCaptivePortalCheckEnabled = false;
221
Robert Greenwaltfb68f8f2014-08-13 13:43:32 -0700222 public boolean systemReady = false;
223
Paul Jensenca8f16a2014-05-09 12:47:55 -0400224 private State mDefaultState = new DefaultState();
225 private State mOfflineState = new OfflineState();
226 private State mValidatedState = new ValidatedState();
227 private State mEvaluatingState = new EvaluatingState();
Paul Jensen869868be2014-05-15 10:33:05 -0400228 private State mUninteractiveAppsPromptedState = new UninteractiveAppsPromptedState();
229 private State mUserPromptedState = new UserPromptedState();
230 private State mInteractiveAppsPromptedState = new InteractiveAppsPromptedState();
Paul Jensenca8f16a2014-05-09 12:47:55 -0400231 private State mCaptivePortalState = new CaptivePortalState();
232 private State mLingeringState = new LingeringState();
233
234 public NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo) {
235 // Add suffix indicating which NetworkMonitor we're talking about.
236 super(TAG + networkAgentInfo.name());
237
238 mContext = context;
239 mConnectivityServiceHandler = handler;
240 mNetworkAgentInfo = networkAgentInfo;
Paul Jensen306f1a42014-08-04 10:59:01 -0400241 mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
242 mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
Paul Jensen79a08052014-08-21 12:44:07 -0400243 mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Paul Jensenca8f16a2014-05-09 12:47:55 -0400244
245 addState(mDefaultState);
246 addState(mOfflineState, mDefaultState);
247 addState(mValidatedState, mDefaultState);
248 addState(mEvaluatingState, mDefaultState);
Paul Jensen869868be2014-05-15 10:33:05 -0400249 addState(mUninteractiveAppsPromptedState, mDefaultState);
250 addState(mUserPromptedState, mDefaultState);
251 addState(mInteractiveAppsPromptedState, mDefaultState);
Paul Jensenca8f16a2014-05-09 12:47:55 -0400252 addState(mCaptivePortalState, mDefaultState);
253 addState(mLingeringState, mDefaultState);
254 setInitialState(mOfflineState);
255
256 mServer = Settings.Global.getString(mContext.getContentResolver(),
257 Settings.Global.CAPTIVE_PORTAL_SERVER);
258 if (mServer == null) mServer = DEFAULT_SERVER;
259
260 mLingerDelayMs = SystemProperties.getInt(LINGER_DELAY_PROPERTY, DEFAULT_LINGER_DELAY_MS);
261 mReevaluateDelayMs = SystemProperties.getInt(REEVALUATE_DELAY_PROPERTY,
262 DEFAULT_REEVALUATE_DELAY_MS);
263
Paul Jensen869868be2014-05-15 10:33:05 -0400264 mIsCaptivePortalCheckEnabled = Settings.Global.getInt(mContext.getContentResolver(),
265 Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED, 1) == 1;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400266
267 start();
268 }
269
270 private class DefaultState extends State {
271 @Override
272 public boolean processMessage(Message message) {
273 if (DBG) log(getName() + message.toString());
274 switch (message.what) {
275 case CMD_NETWORK_LINGER:
276 if (DBG) log("Lingering");
277 transitionTo(mLingeringState);
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400278 return HANDLED;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400279 case CMD_NETWORK_CONNECTED:
280 if (DBG) log("Connected");
281 transitionTo(mEvaluatingState);
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400282 return HANDLED;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400283 case CMD_NETWORK_DISCONNECTED:
Robert Greenwalt1fd9aee2014-07-17 16:11:38 -0700284 if (DBG) log("Disconnected - quitting");
285 quit();
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400286 return HANDLED;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400287 case CMD_FORCE_REEVALUATION:
288 if (DBG) log("Forcing reevaluation");
289 transitionTo(mEvaluatingState);
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400290 return HANDLED;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400291 default:
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400292 return HANDLED;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400293 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400294 }
295 }
296
297 private class OfflineState extends State {
298 @Override
299 public boolean processMessage(Message message) {
300 if (DBG) log(getName() + message.toString());
301 return NOT_HANDLED;
302 }
303 }
304
305 private class ValidatedState extends State {
306 @Override
307 public void enter() {
308 if (DBG) log("Validated");
309 mConnectivityServiceHandler.sendMessage(
310 obtainMessage(EVENT_NETWORK_VALIDATED, mNetworkAgentInfo));
311 }
312
313 @Override
314 public boolean processMessage(Message message) {
315 if (DBG) log(getName() + message.toString());
316 switch (message.what) {
317 case CMD_NETWORK_CONNECTED:
318 transitionTo(mValidatedState);
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400319 return HANDLED;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400320 default:
321 return NOT_HANDLED;
322 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400323 }
324 }
325
326 private class EvaluatingState extends State {
Paul Jensen869868be2014-05-15 10:33:05 -0400327 private int mRetries;
328
Paul Jensenca8f16a2014-05-09 12:47:55 -0400329 private class EvaluateInternetConnectivity extends Thread {
330 private int mToken;
331 EvaluateInternetConnectivity(int token) {
332 mToken = token;
333 }
334 public void run() {
335 sendMessage(EVENT_REEVALUATION_COMPLETE, mToken, isCaptivePortal());
336 }
337 }
338
339 @Override
340 public void enter() {
Paul Jensen869868be2014-05-15 10:33:05 -0400341 mRetries = 0;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400342 sendMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
343 }
344
345 @Override
346 public boolean processMessage(Message message) {
347 if (DBG) log(getName() + message.toString());
348 switch (message.what) {
349 case CMD_REEVALUATE:
350 if (message.arg1 != mReevaluateToken)
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400351 return HANDLED;
Paul Jensen6bc2c2c2014-05-07 15:27:40 -0400352 if (mNetworkAgentInfo.isVPN()) {
353 transitionTo(mValidatedState);
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400354 return HANDLED;
Paul Jensen6bc2c2c2014-05-07 15:27:40 -0400355 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400356 // If network provides no internet connectivity adjust evaluation.
Paul Jensen869868be2014-05-15 10:33:05 -0400357 if (!mNetworkAgentInfo.networkCapabilities.hasCapability(
Paul Jensenca8f16a2014-05-09 12:47:55 -0400358 NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
359 // TODO: Try to verify something works. Do all gateways respond to pings?
360 transitionTo(mValidatedState);
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400361 return HANDLED;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400362 }
363 // Kick off a thread to perform internet connectivity evaluation.
364 Thread thread = new EvaluateInternetConnectivity(mReevaluateToken);
365 thread.run();
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400366 return HANDLED;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400367 case EVENT_REEVALUATION_COMPLETE:
368 if (message.arg1 != mReevaluateToken)
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400369 return HANDLED;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400370 int httpResponseCode = message.arg2;
371 if (httpResponseCode == 204) {
372 transitionTo(mValidatedState);
373 } else if (httpResponseCode >= 200 && httpResponseCode <= 399) {
Paul Jensen869868be2014-05-15 10:33:05 -0400374 transitionTo(mUninteractiveAppsPromptedState);
375 } else if (++mRetries > MAX_RETRIES) {
376 transitionTo(mOfflineState);
377 } else if (mReevaluateDelayMs >= 0) {
378 Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
379 sendMessageDelayed(msg, mReevaluateDelayMs);
Paul Jensenca8f16a2014-05-09 12:47:55 -0400380 }
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400381 return HANDLED;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400382 default:
383 return NOT_HANDLED;
384 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400385 }
386 }
387
Paul Jensen869868be2014-05-15 10:33:05 -0400388 private class AppRespondedBroadcastReceiver extends BroadcastReceiver {
389 private static final int CAPTIVE_PORTAL_UNINITIALIZED_RETURN_CODE = 0;
390 private boolean mCanceled;
391 AppRespondedBroadcastReceiver() {
392 mCanceled = false;
393 }
394 public void send(String action) {
395 Intent intent = new Intent(action);
396 intent.putExtra(ConnectivityManager.EXTRA_NETWORK, mNetworkAgentInfo.network);
397 mContext.sendOrderedBroadcastAsUser(intent, UserHandle.ALL, null, this, getHandler(),
398 CAPTIVE_PORTAL_UNINITIALIZED_RETURN_CODE, null, null);
399 }
400 public void cancel() {
401 mCanceled = true;
402 }
403 @Override
404 public void onReceive(Context context, Intent intent) {
405 if (!mCanceled) {
406 cancel();
407 switch (getResultCode()) {
408 case ConnectivityManager.CAPTIVE_PORTAL_SIGNED_IN:
409 sendMessage(EVENT_APP_BYPASSED_CAPTIVE_PORTAL);
410 break;
411 case ConnectivityManager.CAPTIVE_PORTAL_DISCONNECT:
412 sendMessage(EVENT_APP_INDICATES_SIGN_IN_IMPOSSIBLE);
413 break;
414 // NOTE: This case label makes compiler enforce no overlap between result codes.
415 case CAPTIVE_PORTAL_UNINITIALIZED_RETURN_CODE:
416 default:
417 sendMessage(EVENT_NO_APP_RESPONSE);
418 break;
419 }
420 }
421 }
422 }
423
424 private class UninteractiveAppsPromptedState extends State {
425 private AppRespondedBroadcastReceiver mReceiver;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400426 @Override
427 public void enter() {
Paul Jensen869868be2014-05-15 10:33:05 -0400428 mReceiver = new AppRespondedBroadcastReceiver();
429 mReceiver.send(ConnectivityManager.ACTION_CAPTIVE_PORTAL_DETECTED);
430 }
431 @Override
432 public boolean processMessage(Message message) {
433 if (DBG) log(getName() + message.toString());
434 switch (message.what) {
435 case EVENT_APP_BYPASSED_CAPTIVE_PORTAL:
436 transitionTo(mValidatedState);
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400437 return HANDLED;
Paul Jensen869868be2014-05-15 10:33:05 -0400438 case EVENT_APP_INDICATES_SIGN_IN_IMPOSSIBLE:
439 transitionTo(mOfflineState);
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400440 return HANDLED;
Paul Jensen869868be2014-05-15 10:33:05 -0400441 case EVENT_NO_APP_RESPONSE:
442 transitionTo(mUserPromptedState);
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400443 return HANDLED;
Paul Jensen869868be2014-05-15 10:33:05 -0400444 default:
445 return NOT_HANDLED;
446 }
Paul Jensen869868be2014-05-15 10:33:05 -0400447 }
448 public void exit() {
449 mReceiver.cancel();
450 }
451 }
452
453 private class UserPromptedState extends State {
454 private class UserRespondedBroadcastReceiver extends BroadcastReceiver {
455 private final int mToken;
456 UserRespondedBroadcastReceiver(int token) {
457 mToken = token;
458 }
459 @Override
460 public void onReceive(Context context, Intent intent) {
461 if (Integer.parseInt(intent.getStringExtra(Intent.EXTRA_TEXT)) ==
462 mNetworkAgentInfo.network.netId) {
463 sendMessage(obtainMessage(CMD_USER_WANTS_SIGN_IN, mToken));
464 }
465 }
466 }
467
468 private UserRespondedBroadcastReceiver mUserRespondedBroadcastReceiver;
469
470 @Override
471 public void enter() {
472 // Wait for user to select sign-in notifcation.
473 mUserRespondedBroadcastReceiver = new UserRespondedBroadcastReceiver(
474 ++mUserPromptedToken);
475 IntentFilter filter = new IntentFilter(ACTION_SIGN_IN_REQUESTED);
476 mContext.registerReceiver(mUserRespondedBroadcastReceiver, filter);
477 // Initiate notification to sign-in.
478 Intent intent = new Intent(ACTION_SIGN_IN_REQUESTED);
479 intent.putExtra(Intent.EXTRA_TEXT, String.valueOf(mNetworkAgentInfo.network.netId));
Paul Jensenfdc4e4a2014-07-15 12:07:36 -0400480 Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 1,
481 mNetworkAgentInfo.network.netId,
Paul Jensen869868be2014-05-15 10:33:05 -0400482 PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT));
Paul Jensen869868be2014-05-15 10:33:05 -0400483 mConnectivityServiceHandler.sendMessage(message);
Paul Jensenca8f16a2014-05-09 12:47:55 -0400484 }
485
486 @Override
487 public boolean processMessage(Message message) {
488 if (DBG) log(getName() + message.toString());
489 switch (message.what) {
Paul Jensen869868be2014-05-15 10:33:05 -0400490 case CMD_USER_WANTS_SIGN_IN:
491 if (message.arg1 != mUserPromptedToken)
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400492 return HANDLED;
Paul Jensen869868be2014-05-15 10:33:05 -0400493 transitionTo(mInteractiveAppsPromptedState);
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400494 return HANDLED;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400495 default:
496 return NOT_HANDLED;
497 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400498 }
Paul Jensen869868be2014-05-15 10:33:05 -0400499
500 @Override
501 public void exit() {
Paul Jensenfdc4e4a2014-07-15 12:07:36 -0400502 Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 0,
503 mNetworkAgentInfo.network.netId, null);
Paul Jensen869868be2014-05-15 10:33:05 -0400504 mConnectivityServiceHandler.sendMessage(message);
505 mContext.unregisterReceiver(mUserRespondedBroadcastReceiver);
506 mUserRespondedBroadcastReceiver = null;
507 }
508 }
509
510 private class InteractiveAppsPromptedState extends State {
511 private AppRespondedBroadcastReceiver mReceiver;
512 @Override
513 public void enter() {
514 mReceiver = new AppRespondedBroadcastReceiver();
515 mReceiver.send(ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN);
516 }
517 @Override
518 public boolean processMessage(Message message) {
519 if (DBG) log(getName() + message.toString());
520 switch (message.what) {
521 case EVENT_APP_BYPASSED_CAPTIVE_PORTAL:
522 transitionTo(mValidatedState);
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400523 return HANDLED;
Paul Jensen869868be2014-05-15 10:33:05 -0400524 case EVENT_APP_INDICATES_SIGN_IN_IMPOSSIBLE:
525 transitionTo(mOfflineState);
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400526 return HANDLED;
Paul Jensen869868be2014-05-15 10:33:05 -0400527 case EVENT_NO_APP_RESPONSE:
528 transitionTo(mCaptivePortalState);
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400529 return HANDLED;
Paul Jensen869868be2014-05-15 10:33:05 -0400530 default:
531 return NOT_HANDLED;
532 }
Paul Jensen869868be2014-05-15 10:33:05 -0400533 }
534 public void exit() {
535 mReceiver.cancel();
536 }
537 }
538
539 private class CaptivePortalState extends State {
540 private class CaptivePortalLoggedInBroadcastReceiver extends BroadcastReceiver {
541 private final int mToken;
542
543 CaptivePortalLoggedInBroadcastReceiver(int token) {
544 mToken = token;
545 }
546
547 @Override
548 public void onReceive(Context context, Intent intent) {
549 if (Integer.parseInt(intent.getStringExtra(Intent.EXTRA_TEXT)) ==
550 mNetworkAgentInfo.network.netId) {
551 sendMessage(obtainMessage(CMD_CAPTIVE_PORTAL_LOGGED_IN, mToken,
552 Integer.parseInt(intent.getStringExtra(LOGGED_IN_RESULT))));
553 }
554 }
555 }
556
557 private CaptivePortalLoggedInBroadcastReceiver mCaptivePortalLoggedInBroadcastReceiver;
558
559 @Override
560 public void enter() {
561 Intent intent = new Intent(Intent.ACTION_SEND);
562 intent.putExtra(Intent.EXTRA_TEXT, String.valueOf(mNetworkAgentInfo.network.netId));
563 intent.setType("text/plain");
564 intent.setComponent(new ComponentName("com.android.captiveportallogin",
565 "com.android.captiveportallogin.CaptivePortalLoginActivity"));
566 intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
567
568 // Wait for result.
569 mCaptivePortalLoggedInBroadcastReceiver = new CaptivePortalLoggedInBroadcastReceiver(
570 ++mCaptivePortalLoggedInToken);
571 IntentFilter filter = new IntentFilter(ACTION_CAPTIVE_PORTAL_LOGGED_IN);
572 mContext.registerReceiver(mCaptivePortalLoggedInBroadcastReceiver, filter);
573 // Initiate app to log in.
574 mContext.startActivityAsUser(intent, UserHandle.CURRENT);
575 }
576
577 @Override
578 public boolean processMessage(Message message) {
579 if (DBG) log(getName() + message.toString());
580 switch (message.what) {
581 case CMD_CAPTIVE_PORTAL_LOGGED_IN:
582 if (message.arg1 != mCaptivePortalLoggedInToken)
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400583 return HANDLED;
Paul Jensen869868be2014-05-15 10:33:05 -0400584 if (message.arg2 == 0) {
585 // TODO: Should teardown network.
586 transitionTo(mOfflineState);
587 } else {
588 transitionTo(mValidatedState);
589 }
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400590 return HANDLED;
Paul Jensen869868be2014-05-15 10:33:05 -0400591 default:
592 return NOT_HANDLED;
593 }
Paul Jensen869868be2014-05-15 10:33:05 -0400594 }
595
596 @Override
597 public void exit() {
598 mContext.unregisterReceiver(mCaptivePortalLoggedInBroadcastReceiver);
599 mCaptivePortalLoggedInBroadcastReceiver = null;
600 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400601 }
602
603 private class LingeringState extends State {
Paul Jensen79a08052014-08-21 12:44:07 -0400604 private static final String ACTION_LINGER_EXPIRED = "android.net.netmon.lingerExpired";
605 private static final String EXTRA_NETID = "lingerExpiredNetId";
606 private static final String EXTRA_TOKEN = "lingerExpiredToken";
607
608 private class LingerExpiredBroadcastReceiver extends BroadcastReceiver {
609 @Override
610 public void onReceive(Context context, Intent intent) {
611 if (intent.getAction().equals(ACTION_LINGER_EXPIRED) &&
612 Integer.parseInt(intent.getStringExtra(EXTRA_NETID)) ==
613 mNetworkAgentInfo.network.netId) {
614 sendMessage(CMD_LINGER_EXPIRED,
615 Integer.parseInt(intent.getStringExtra(EXTRA_TOKEN)));
616 }
617 }
618 }
619
620 private BroadcastReceiver mBroadcastReceiver;
621 private PendingIntent mIntent;
622
Paul Jensenca8f16a2014-05-09 12:47:55 -0400623 @Override
624 public void enter() {
Paul Jensen79a08052014-08-21 12:44:07 -0400625 mBroadcastReceiver = new LingerExpiredBroadcastReceiver();
626 mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(ACTION_LINGER_EXPIRED));
627
628 Intent intent = new Intent(ACTION_LINGER_EXPIRED, null);
629 intent.putExtra(EXTRA_NETID, String.valueOf(mNetworkAgentInfo.network.netId));
630 intent.putExtra(EXTRA_TOKEN, String.valueOf(++mLingerToken));
631 mIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
632 long wakeupTime = SystemClock.elapsedRealtime() + mLingerDelayMs;
633 mAlarmManager.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP, wakeupTime,
634 // Give a specific window so we aren't subject to unknown inexactitude.
635 mLingerDelayMs / 6, mIntent);
Paul Jensenca8f16a2014-05-09 12:47:55 -0400636 }
637
638 @Override
639 public boolean processMessage(Message message) {
640 if (DBG) log(getName() + message.toString());
641 switch (message.what) {
642 case CMD_NETWORK_CONNECTED:
643 // Go straight to active as we've already evaluated.
644 transitionTo(mValidatedState);
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400645 return HANDLED;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400646 case CMD_LINGER_EXPIRED:
647 if (message.arg1 != mLingerToken)
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400648 return HANDLED;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400649 mConnectivityServiceHandler.sendMessage(
650 obtainMessage(EVENT_NETWORK_LINGER_COMPLETE, mNetworkAgentInfo));
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400651 return HANDLED;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400652 default:
653 return NOT_HANDLED;
654 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400655 }
Paul Jensen79a08052014-08-21 12:44:07 -0400656
657 @Override
658 public void exit() {
659 mAlarmManager.cancel(mIntent);
660 mContext.unregisterReceiver(mBroadcastReceiver);
661 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400662 }
663
664 /**
665 * Do a URL fetch on a known server to see if we get the data we expect.
666 * Returns HTTP response code.
667 */
668 private int isCaptivePortal() {
669 if (!mIsCaptivePortalCheckEnabled) return 204;
670
Paul Jensenca8f16a2014-05-09 12:47:55 -0400671 HttpURLConnection urlConnection = null;
Paul Jensen869868be2014-05-15 10:33:05 -0400672 int httpResponseCode = 599;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400673 try {
Paul Jensene547ff22014-08-04 09:12:24 -0400674 URL url = new URL("http", mServer, "/generate_204");
675 if (DBG) {
676 log("Checking " + url.toString() + " on " +
677 mNetworkAgentInfo.networkInfo.getExtraInfo());
Paul Jensenca8f16a2014-05-09 12:47:55 -0400678 }
Paul Jensene547ff22014-08-04 09:12:24 -0400679 url = mNetworkAgentInfo.network.getBoundURL(url);
680 urlConnection = (HttpURLConnection) url.openConnection();
681 urlConnection.setInstanceFollowRedirects(false);
682 urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
683 urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
684 urlConnection.setUseCaches(false);
Paul Jensen306f1a42014-08-04 10:59:01 -0400685
686 // Time how long it takes to get a response to our request
687 long requestTimestamp = SystemClock.elapsedRealtime();
688
Paul Jensene547ff22014-08-04 09:12:24 -0400689 urlConnection.getInputStream();
Paul Jensen306f1a42014-08-04 10:59:01 -0400690
691 // Time how long it takes to get a response to our request
692 long responseTimestamp = SystemClock.elapsedRealtime();
693
Paul Jensene547ff22014-08-04 09:12:24 -0400694 httpResponseCode = urlConnection.getResponseCode();
695 if (DBG) {
696 log("isCaptivePortal: ret=" + httpResponseCode +
697 " headers=" + urlConnection.getHeaderFields());
698 }
699 // NOTE: We may want to consider an "HTTP/1.0 204" response to be a captive
700 // portal. The only example of this seen so far was a captive portal. For
701 // the time being go with prior behavior of assuming it's not a captive
702 // portal. If it is considered a captive portal, a different sign-in URL
703 // is needed (i.e. can't browse a 204). This could be the result of an HTTP
704 // proxy server.
705
706 // Consider 200 response with "Content-length=0" to not be a captive portal.
707 // There's no point in considering this a captive portal as the user cannot
708 // sign-in to an empty page. Probably the result of a broken transparent proxy.
709 // See http://b/9972012.
710 if (httpResponseCode == 200 && urlConnection.getContentLength() == 0) {
711 if (DBG) log("Empty 200 response interpreted as 204 response.");
712 httpResponseCode = 204;
713 }
Paul Jensen306f1a42014-08-04 10:59:01 -0400714
715 sendNetworkConditionsBroadcast(true /* response received */, httpResponseCode == 204,
716 requestTimestamp, responseTimestamp);
Paul Jensenca8f16a2014-05-09 12:47:55 -0400717 } catch (IOException e) {
718 if (DBG) log("Probably not a portal: exception " + e);
Paul Jensen869868be2014-05-15 10:33:05 -0400719 if (httpResponseCode == 599) {
720 // TODO: Ping gateway and DNS server and log results.
721 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400722 } finally {
723 if (urlConnection != null) {
724 urlConnection.disconnect();
725 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400726 }
727 return httpResponseCode;
728 }
Paul Jensen306f1a42014-08-04 10:59:01 -0400729
730 /**
731 * @param responseReceived - whether or not we received a valid HTTP response to our request.
732 * If false, isCaptivePortal and responseTimestampMs are ignored
733 * TODO: This should be moved to the transports. The latency could be passed to the transports
734 * along with the captive portal result. Currently the TYPE_MOBILE broadcasts appear unused so
735 * perhaps this could just be added to the WiFi transport only.
736 */
737 private void sendNetworkConditionsBroadcast(boolean responseReceived, boolean isCaptivePortal,
738 long requestTimestampMs, long responseTimestampMs) {
739 if (Settings.Global.getInt(mContext.getContentResolver(),
740 Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 0) {
741 if (DBG) log("Don't send network conditions - lacking user consent.");
742 return;
743 }
744
Robert Greenwaltfb68f8f2014-08-13 13:43:32 -0700745 if (systemReady == false) return;
746
Paul Jensen306f1a42014-08-04 10:59:01 -0400747 Intent latencyBroadcast = new Intent(ACTION_NETWORK_CONDITIONS_MEASURED);
748 switch (mNetworkAgentInfo.networkInfo.getType()) {
749 case ConnectivityManager.TYPE_WIFI:
750 WifiInfo currentWifiInfo = mWifiManager.getConnectionInfo();
751 if (currentWifiInfo != null) {
752 // NOTE: getSSID()'s behavior changed in API 17; before that, SSIDs were not
753 // surrounded by double quotation marks (thus violating the Javadoc), but this
754 // was changed to match the Javadoc in API 17. Since clients may have started
755 // sanitizing the output of this method since API 17 was released, we should
756 // not change it here as it would become impossible to tell whether the SSID is
757 // simply being surrounded by quotes due to the API, or whether those quotes
758 // are actually part of the SSID.
759 latencyBroadcast.putExtra(EXTRA_SSID, currentWifiInfo.getSSID());
760 latencyBroadcast.putExtra(EXTRA_BSSID, currentWifiInfo.getBSSID());
761 } else {
762 if (DBG) logw("network info is TYPE_WIFI but no ConnectionInfo found");
763 return;
764 }
765 break;
766 case ConnectivityManager.TYPE_MOBILE:
767 latencyBroadcast.putExtra(EXTRA_NETWORK_TYPE, mTelephonyManager.getNetworkType());
768 List<CellInfo> info = mTelephonyManager.getAllCellInfo();
769 if (info == null) return;
770 int numRegisteredCellInfo = 0;
771 for (CellInfo cellInfo : info) {
772 if (cellInfo.isRegistered()) {
773 numRegisteredCellInfo++;
774 if (numRegisteredCellInfo > 1) {
775 if (DBG) log("more than one registered CellInfo. Can't " +
776 "tell which is active. Bailing.");
777 return;
778 }
779 if (cellInfo instanceof CellInfoCdma) {
780 CellIdentityCdma cellId = ((CellInfoCdma) cellInfo).getCellIdentity();
781 latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId);
782 } else if (cellInfo instanceof CellInfoGsm) {
783 CellIdentityGsm cellId = ((CellInfoGsm) cellInfo).getCellIdentity();
784 latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId);
785 } else if (cellInfo instanceof CellInfoLte) {
786 CellIdentityLte cellId = ((CellInfoLte) cellInfo).getCellIdentity();
787 latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId);
788 } else if (cellInfo instanceof CellInfoWcdma) {
789 CellIdentityWcdma cellId = ((CellInfoWcdma) cellInfo).getCellIdentity();
790 latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId);
791 } else {
792 if (DBG) logw("Registered cellinfo is unrecognized");
793 return;
794 }
795 }
796 }
797 break;
798 default:
799 return;
800 }
801 latencyBroadcast.putExtra(EXTRA_CONNECTIVITY_TYPE, mNetworkAgentInfo.networkInfo.getType());
802 latencyBroadcast.putExtra(EXTRA_RESPONSE_RECEIVED, responseReceived);
803 latencyBroadcast.putExtra(EXTRA_REQUEST_TIMESTAMP_MS, requestTimestampMs);
804
805 if (responseReceived) {
806 latencyBroadcast.putExtra(EXTRA_IS_CAPTIVE_PORTAL, isCaptivePortal);
807 latencyBroadcast.putExtra(EXTRA_RESPONSE_TIMESTAMP_MS, responseTimestampMs);
808 }
809 mContext.sendBroadcast(latencyBroadcast, PERMISSION_ACCESS_NETWORK_CONDITIONS);
810 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400811}