OSDN Git Service

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