blob: 593a28a02cc47a364bded62fdc9e77567f341a7b [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 Jensen7ccd3df2014-08-29 09:54:01 -040030import android.net.TrafficStats;
Paul Jensen306f1a42014-08-04 10:59:01 -040031import android.net.wifi.WifiInfo;
32import android.net.wifi.WifiManager;
Paul Jensenca8f16a2014-05-09 12:47:55 -040033import android.os.Handler;
34import android.os.Message;
Paul Jensen306f1a42014-08-04 10:59:01 -040035import android.os.SystemClock;
Paul Jensenca8f16a2014-05-09 12:47:55 -040036import android.os.SystemProperties;
Paul Jensen869868be2014-05-15 10:33:05 -040037import android.os.UserHandle;
Paul Jensenca8f16a2014-05-09 12:47:55 -040038import android.provider.Settings;
Paul Jensen306f1a42014-08-04 10:59:01 -040039import 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;
Paul Jensen532b61432014-11-10 09:50:02 -050049import android.util.Log;
Paul Jensenca8f16a2014-05-09 12:47:55 -040050
51import com.android.internal.util.Protocol;
52import com.android.internal.util.State;
53import com.android.internal.util.StateMachine;
Paul Jensen869868be2014-05-15 10:33:05 -040054import com.android.server.ConnectivityService;
Paul Jensenca8f16a2014-05-09 12:47:55 -040055import com.android.server.connectivity.NetworkAgentInfo;
56
Paul Jensenca8f16a2014-05-09 12:47:55 -040057import java.io.IOException;
Paul Jensenca8f16a2014-05-09 12:47:55 -040058import java.net.HttpURLConnection;
Paul Jensenca8f16a2014-05-09 12:47:55 -040059import java.net.URL;
Paul Jensen306f1a42014-08-04 10:59:01 -040060import java.util.List;
Paul Jensenca8f16a2014-05-09 12:47:55 -040061
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;
Paul Jensen306f1a42014-08-04 10:59:01 -040070 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";
Paul Jensenca8f16a2014-05-09 12:47:55 -040085
Paul Jensen869868be2014-05-15 10:33:05 -040086 // 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
Paul Jensenad50a1f2014-09-05 12:06:44 -040095 // 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
Paul Jensenca8f16a2014-05-09 12:47:55 -0400107 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 /**
Paul Jensenad50a1f2014-09-05 12:06:44 -0400116 * Inform ConnectivityService that the network has been tested.
Paul Jensenca8f16a2014-05-09 12:47:55 -0400117 * obj = NetworkAgentInfo
Paul Jensenad50a1f2014-09-05 12:06:44 -0400118 * arg1 = One of the NETWORK_TESTED_RESULT_* constants.
Paul Jensenca8f16a2014-05-09 12:47:55 -0400119 */
Paul Jensenad50a1f2014-09-05 12:06:44 -0400120 public static final int EVENT_NETWORK_TESTED = BASE + 2;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400121
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 /**
Paul Jensenca8f16a2014-05-09 12:47:55 -0400148 * Message to self indicating it's time to evaluate a network's connectivity.
149 * arg1 = Token to ignore old messages.
150 */
Paul Jensen869868be2014-05-15 10:33:05 -0400151 private static final int CMD_REEVALUATE = BASE + 6;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400152
153 /**
Paul Jensenca8f16a2014-05-09 12:47:55 -0400154 * Inform NetworkMonitor that the network has disconnected.
155 */
Paul Jensen7ccd3df2014-08-29 09:54:01 -0400156 public static final int CMD_NETWORK_DISCONNECTED = BASE + 7;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400157
158 /**
159 * Force evaluation even if it has succeeded in the past.
Paul Jensen7ccd3df2014-08-29 09:54:01 -0400160 * arg1 = UID responsible for requesting this reeval. Will be billed for data.
Paul Jensenca8f16a2014-05-09 12:47:55 -0400161 */
Paul Jensen7ccd3df2014-08-29 09:54:01 -0400162 public static final int CMD_FORCE_REEVALUATION = BASE + 8;
Paul Jensen869868be2014-05-15 10:33:05 -0400163
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 */
Paul Jensen7ccd3df2014-08-29 09:54:01 -0400169 private static final int CMD_CAPTIVE_PORTAL_LOGGED_IN = BASE + 9;
Paul Jensen869868be2014-05-15 10:33:05 -0400170
171 /**
172 * Message to self indicating user desires to log into captive portal.
173 * arg1 = Token to ignore old messages.
174 */
Paul Jensen7ccd3df2014-08-29 09:54:01 -0400175 private static final int CMD_USER_WANTS_SIGN_IN = BASE + 10;
Paul Jensen869868be2014-05-15 10:33:05 -0400176
177 /**
178 * Request ConnectivityService display provisioning notification.
179 * arg1 = Whether to make the notification visible.
Paul Jensenfdc4e4a2014-07-15 12:07:36 -0400180 * arg2 = NetID.
181 * obj = Intent to be launched when notification selected by user, null if !arg1.
Paul Jensen869868be2014-05-15 10:33:05 -0400182 */
Paul Jensen7ccd3df2014-08-29 09:54:01 -0400183 public static final int EVENT_PROVISIONING_NOTIFICATION = BASE + 11;
Paul Jensen869868be2014-05-15 10:33:05 -0400184
185 /**
186 * Message to self indicating sign-in app bypassed captive portal.
187 */
Paul Jensen7ccd3df2014-08-29 09:54:01 -0400188 private static final int EVENT_APP_BYPASSED_CAPTIVE_PORTAL = BASE + 12;
Paul Jensen869868be2014-05-15 10:33:05 -0400189
190 /**
191 * Message to self indicating no sign-in app responded.
192 */
Paul Jensen7ccd3df2014-08-29 09:54:01 -0400193 private static final int EVENT_NO_APP_RESPONSE = BASE + 13;
Paul Jensen869868be2014-05-15 10:33:05 -0400194
195 /**
196 * Message to self indicating sign-in app indicates sign-in is not possible.
197 */
Paul Jensen7ccd3df2014-08-29 09:54:01 -0400198 private static final int EVENT_APP_INDICATES_SIGN_IN_IMPOSSIBLE = BASE + 14;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400199
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
Paul Jensenca8f16a2014-05-09 12:47:55 -0400206 // 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;
Paul Jensen869868be2014-05-15 10:33:05 -0400210 private static final int MAX_RETRIES = 10;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400211 private final int mReevaluateDelayMs;
212 private int mReevaluateToken = 0;
Paul Jensen7ccd3df2014-08-29 09:54:01 -0400213 private static final int INVALID_UID = -1;
214 private int mUidResponsibleForReeval = INVALID_UID;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400215
Paul Jensen869868be2014-05-15 10:33:05 -0400216 private int mCaptivePortalLoggedInToken = 0;
217 private int mUserPromptedToken = 0;
218
Paul Jensenca8f16a2014-05-09 12:47:55 -0400219 private final Context mContext;
220 private final Handler mConnectivityServiceHandler;
221 private final NetworkAgentInfo mNetworkAgentInfo;
Paul Jensen306f1a42014-08-04 10:59:01 -0400222 private final TelephonyManager mTelephonyManager;
223 private final WifiManager mWifiManager;
Paul Jensen79a08052014-08-21 12:44:07 -0400224 private final AlarmManager mAlarmManager;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400225
226 private String mServer;
227 private boolean mIsCaptivePortalCheckEnabled = false;
228
Paul Jensenad50a1f2014-09-05 12:06:44 -0400229 // Set if the user explicitly selected "Do not use this network" in captive portal sign-in app.
230 private boolean mUserDoesNotWant = false;
231
Robert Greenwaltfb68f8f2014-08-13 13:43:32 -0700232 public boolean systemReady = false;
233
Paul Jensenca8f16a2014-05-09 12:47:55 -0400234 private State mDefaultState = new DefaultState();
235 private State mOfflineState = new OfflineState();
236 private State mValidatedState = new ValidatedState();
237 private State mEvaluatingState = new EvaluatingState();
Paul Jensen869868be2014-05-15 10:33:05 -0400238 private State mUserPromptedState = new UserPromptedState();
Paul Jensenca8f16a2014-05-09 12:47:55 -0400239 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;
Paul Jensen306f1a42014-08-04 10:59:01 -0400249 mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
250 mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
Paul Jensen79a08052014-08-21 12:44:07 -0400251 mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Paul Jensenca8f16a2014-05-09 12:47:55 -0400252
253 addState(mDefaultState);
254 addState(mOfflineState, mDefaultState);
255 addState(mValidatedState, mDefaultState);
256 addState(mEvaluatingState, mDefaultState);
Paul Jensen869868be2014-05-15 10:33:05 -0400257 addState(mUserPromptedState, mDefaultState);
Paul Jensenca8f16a2014-05-09 12:47:55 -0400258 addState(mCaptivePortalState, mDefaultState);
259 addState(mLingeringState, mDefaultState);
Robert Greenwalt49f63fb2014-09-13 12:04:12 -0700260 setInitialState(mDefaultState);
Paul Jensenca8f16a2014-05-09 12:47:55 -0400261
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
Paul Jensen869868be2014-05-15 10:33:05 -0400270 mIsCaptivePortalCheckEnabled = Settings.Global.getInt(mContext.getContentResolver(),
271 Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED, 1) == 1;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400272
273 start();
274 }
275
Paul Jensen532b61432014-11-10 09:50:02 -0500276 @Override
277 protected void log(String s) {
278 Log.d(TAG + mNetworkAgentInfo.name(), s);
279 }
280
Paul Jensenca8f16a2014-05-09 12:47:55 -0400281 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);
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400289 return HANDLED;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400290 case CMD_NETWORK_CONNECTED:
291 if (DBG) log("Connected");
292 transitionTo(mEvaluatingState);
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400293 return HANDLED;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400294 case CMD_NETWORK_DISCONNECTED:
Robert Greenwalt1fd9aee2014-07-17 16:11:38 -0700295 if (DBG) log("Disconnected - quitting");
296 quit();
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400297 return HANDLED;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400298 case CMD_FORCE_REEVALUATION:
299 if (DBG) log("Forcing reevaluation");
Paul Jensen7ccd3df2014-08-29 09:54:01 -0400300 mUidResponsibleForReeval = message.arg1;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400301 transitionTo(mEvaluatingState);
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400302 return HANDLED;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400303 default:
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400304 return HANDLED;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400305 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400306 }
307 }
308
309 private class OfflineState extends State {
310 @Override
Paul Jensenad50a1f2014-09-05 12:06:44 -0400311 public void enter() {
312 mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED,
313 NETWORK_TEST_RESULT_INVALID, 0, mNetworkAgentInfo));
314 }
315
316 @Override
Paul Jensenca8f16a2014-05-09 12:47:55 -0400317 public boolean processMessage(Message message) {
318 if (DBG) log(getName() + message.toString());
Paul Jensenad50a1f2014-09-05 12:06:44 -0400319 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 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400328 }
329 }
330
331 private class ValidatedState extends State {
332 @Override
333 public void enter() {
334 if (DBG) log("Validated");
Paul Jensenad50a1f2014-09-05 12:06:44 -0400335 mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED,
336 NETWORK_TEST_RESULT_VALID, 0, mNetworkAgentInfo));
Paul Jensenca8f16a2014-05-09 12:47:55 -0400337 }
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);
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400345 return HANDLED;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400346 default:
347 return NOT_HANDLED;
348 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400349 }
350 }
351
352 private class EvaluatingState extends State {
Paul Jensen869868be2014-05-15 10:33:05 -0400353 private int mRetries;
354
Paul Jensenca8f16a2014-05-09 12:47:55 -0400355 @Override
356 public void enter() {
Paul Jensen869868be2014-05-15 10:33:05 -0400357 mRetries = 0;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400358 sendMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
Paul Jensen7ccd3df2014-08-29 09:54:01 -0400359 if (mUidResponsibleForReeval != INVALID_UID) {
360 TrafficStats.setThreadStatsUid(mUidResponsibleForReeval);
361 mUidResponsibleForReeval = INVALID_UID;
362 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400363 }
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)
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400371 return HANDLED;
Paul Jensen6bc2c2c2014-05-07 15:27:40 -0400372 if (mNetworkAgentInfo.isVPN()) {
373 transitionTo(mValidatedState);
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400374 return HANDLED;
Paul Jensen6bc2c2c2014-05-07 15:27:40 -0400375 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400376 // If network provides no internet connectivity adjust evaluation.
Paul Jensen869868be2014-05-15 10:33:05 -0400377 if (!mNetworkAgentInfo.networkCapabilities.hasCapability(
Paul Jensenca8f16a2014-05-09 12:47:55 -0400378 NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
379 // TODO: Try to verify something works. Do all gateways respond to pings?
380 transitionTo(mValidatedState);
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400381 return HANDLED;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400382 }
Paul Jensen7ccd3df2014-08-29 09:54:01 -0400383 int httpResponseCode = isCaptivePortal();
Paul Jensenca8f16a2014-05-09 12:47:55 -0400384 if (httpResponseCode == 204) {
385 transitionTo(mValidatedState);
386 } else if (httpResponseCode >= 200 && httpResponseCode <= 399) {
Paul Jensena68d2132014-08-22 09:32:07 -0400387 transitionTo(mUserPromptedState);
Paul Jensen869868be2014-05-15 10:33:05 -0400388 } 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);
Paul Jensenca8f16a2014-05-09 12:47:55 -0400393 }
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400394 return HANDLED;
Paul Jensen7ccd3df2014-08-29 09:54:01 -0400395 case CMD_FORCE_REEVALUATION:
396 // Ignore duplicate requests.
397 return HANDLED;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400398 default:
399 return NOT_HANDLED;
400 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400401 }
Paul Jensen7ccd3df2014-08-29 09:54:01 -0400402
403 @Override
404 public void exit() {
405 TrafficStats.clearThreadStatsUid();
406 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400407 }
408
Paul Jensendcbe8352014-09-16 16:28:34 -0400409 // 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));
Paul Jensen869868be2014-05-15 10:33:05 -0400417 }
Paul Jensendcbe8352014-09-16 16:28:34 -0400418 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 }
Paul Jensen869868be2014-05-15 10:33:05 -0400426
Paul Jensendcbe8352014-09-16 16:28:34 -0400427 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;
Paul Jensen869868be2014-05-15 10:33:05 -0400433
434 @Override
435 public void enter() {
Paul Jensenad50a1f2014-09-05 12:06:44 -0400436 mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED,
437 NETWORK_TEST_RESULT_INVALID, 0, mNetworkAgentInfo));
Paul Jensen869868be2014-05-15 10:33:05 -0400438 // Wait for user to select sign-in notifcation.
Paul Jensendcbe8352014-09-16 16:28:34 -0400439 mUserRespondedBroadcastReceiver = new CustomIntentReceiver(ACTION_SIGN_IN_REQUESTED,
440 ++mUserPromptedToken, CMD_USER_WANTS_SIGN_IN);
Paul Jensen869868be2014-05-15 10:33:05 -0400441 // Initiate notification to sign-in.
Paul Jensenfdc4e4a2014-07-15 12:07:36 -0400442 Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 1,
443 mNetworkAgentInfo.network.netId,
Paul Jensendcbe8352014-09-16 16:28:34 -0400444 mUserRespondedBroadcastReceiver.getPendingIntent());
Paul Jensen869868be2014-05-15 10:33:05 -0400445 mConnectivityServiceHandler.sendMessage(message);
Paul Jensenca8f16a2014-05-09 12:47:55 -0400446 }
447
448 @Override
449 public boolean processMessage(Message message) {
450 if (DBG) log(getName() + message.toString());
451 switch (message.what) {
Paul Jensen869868be2014-05-15 10:33:05 -0400452 case CMD_USER_WANTS_SIGN_IN:
453 if (message.arg1 != mUserPromptedToken)
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400454 return HANDLED;
Paul Jensena68d2132014-08-22 09:32:07 -0400455 transitionTo(mCaptivePortalState);
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400456 return HANDLED;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400457 default:
458 return NOT_HANDLED;
459 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400460 }
Paul Jensen869868be2014-05-15 10:33:05 -0400461
462 @Override
463 public void exit() {
Paul Jensenfdc4e4a2014-07-15 12:07:36 -0400464 Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 0,
465 mNetworkAgentInfo.network.netId, null);
Paul Jensen869868be2014-05-15 10:33:05 -0400466 mConnectivityServiceHandler.sendMessage(message);
467 mContext.unregisterReceiver(mUserRespondedBroadcastReceiver);
468 mUserRespondedBroadcastReceiver = null;
469 }
470 }
471
Paul Jensen869868be2014-05-15 10:33:05 -0400472 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)
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400516 return HANDLED;
Paul Jensen869868be2014-05-15 10:33:05 -0400517 if (message.arg2 == 0) {
Paul Jensenad50a1f2014-09-05 12:06:44 -0400518 mUserDoesNotWant = true;
Paul Jensen869868be2014-05-15 10:33:05 -0400519 // TODO: Should teardown network.
520 transitionTo(mOfflineState);
521 } else {
522 transitionTo(mValidatedState);
523 }
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400524 return HANDLED;
Paul Jensen869868be2014-05-15 10:33:05 -0400525 default:
526 return NOT_HANDLED;
527 }
Paul Jensen869868be2014-05-15 10:33:05 -0400528 }
529
530 @Override
531 public void exit() {
532 mContext.unregisterReceiver(mCaptivePortalLoggedInBroadcastReceiver);
533 mCaptivePortalLoggedInBroadcastReceiver = null;
534 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400535 }
536
537 private class LingeringState extends State {
Paul Jensen79a08052014-08-21 12:44:07 -0400538 private static final String ACTION_LINGER_EXPIRED = "android.net.netmon.lingerExpired";
Paul Jensen79a08052014-08-21 12:44:07 -0400539
Paul Jensendcbe8352014-09-16 16:28:34 -0400540 private CustomIntentReceiver mBroadcastReceiver;
Paul Jensen79a08052014-08-21 12:44:07 -0400541 private PendingIntent mIntent;
542
Paul Jensenca8f16a2014-05-09 12:47:55 -0400543 @Override
544 public void enter() {
Paul Jensendcbe8352014-09-16 16:28:34 -0400545 mBroadcastReceiver = new CustomIntentReceiver(ACTION_LINGER_EXPIRED, ++mLingerToken,
546 CMD_LINGER_EXPIRED);
547 mIntent = mBroadcastReceiver.getPendingIntent();
Paul Jensen79a08052014-08-21 12:44:07 -0400548 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);
Paul Jensenca8f16a2014-05-09 12:47:55 -0400552 }
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);
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400561 return HANDLED;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400562 case CMD_LINGER_EXPIRED:
563 if (message.arg1 != mLingerToken)
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400564 return HANDLED;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400565 mConnectivityServiceHandler.sendMessage(
566 obtainMessage(EVENT_NETWORK_LINGER_COMPLETE, mNetworkAgentInfo));
Paul Jensend6a3f7e2014-08-19 09:40:11 -0400567 return HANDLED;
Paul Jensenad50a1f2014-09-05 12:06:44 -0400568 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;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400574 default:
575 return NOT_HANDLED;
576 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400577 }
Paul Jensen79a08052014-08-21 12:44:07 -0400578
579 @Override
580 public void exit() {
581 mAlarmManager.cancel(mIntent);
582 mContext.unregisterReceiver(mBroadcastReceiver);
583 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400584 }
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
Paul Jensenca8f16a2014-05-09 12:47:55 -0400593 HttpURLConnection urlConnection = null;
Paul Jensen869868be2014-05-15 10:33:05 -0400594 int httpResponseCode = 599;
Paul Jensenca8f16a2014-05-09 12:47:55 -0400595 try {
Paul Jensene547ff22014-08-04 09:12:24 -0400596 URL url = new URL("http", mServer, "/generate_204");
597 if (DBG) {
598 log("Checking " + url.toString() + " on " +
599 mNetworkAgentInfo.networkInfo.getExtraInfo());
Paul Jensenca8f16a2014-05-09 12:47:55 -0400600 }
Lorenzo Colitti9f1274b2014-08-21 11:45:54 -0700601 urlConnection = (HttpURLConnection) mNetworkAgentInfo.network.openConnection(url);
Paul Jensene547ff22014-08-04 09:12:24 -0400602 urlConnection.setInstanceFollowRedirects(false);
603 urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
604 urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
605 urlConnection.setUseCaches(false);
Paul Jensen306f1a42014-08-04 10:59:01 -0400606
607 // Time how long it takes to get a response to our request
608 long requestTimestamp = SystemClock.elapsedRealtime();
609
Paul Jensene547ff22014-08-04 09:12:24 -0400610 urlConnection.getInputStream();
Paul Jensen306f1a42014-08-04 10:59:01 -0400611
612 // Time how long it takes to get a response to our request
613 long responseTimestamp = SystemClock.elapsedRealtime();
614
Paul Jensene547ff22014-08-04 09:12:24 -0400615 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 }
Paul Jensen306f1a42014-08-04 10:59:01 -0400635
636 sendNetworkConditionsBroadcast(true /* response received */, httpResponseCode == 204,
637 requestTimestamp, responseTimestamp);
Paul Jensenca8f16a2014-05-09 12:47:55 -0400638 } catch (IOException e) {
639 if (DBG) log("Probably not a portal: exception " + e);
Paul Jensen869868be2014-05-15 10:33:05 -0400640 if (httpResponseCode == 599) {
641 // TODO: Ping gateway and DNS server and log results.
642 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400643 } finally {
644 if (urlConnection != null) {
645 urlConnection.disconnect();
646 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400647 }
648 return httpResponseCode;
649 }
Paul Jensen306f1a42014-08-04 10:59:01 -0400650
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
Robert Greenwaltfb68f8f2014-08-13 13:43:32 -0700666 if (systemReady == false) return;
667
Paul Jensen306f1a42014-08-04 10:59:01 -0400668 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 }
Paul Jensen55298582014-08-20 11:01:41 -0400730 mContext.sendBroadcastAsUser(latencyBroadcast, UserHandle.CURRENT,
731 PERMISSION_ACCESS_NETWORK_CONDITIONS);
Paul Jensen306f1a42014-08-04 10:59:01 -0400732 }
Paul Jensenca8f16a2014-05-09 12:47:55 -0400733}