OSDN Git Service

Implement ConnectivityManager.reportBadNetwork() to trigger network validation.
[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.PendingIntent;
20 import android.content.BroadcastReceiver;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.IntentFilter;
25 import android.net.ConnectivityManager;
26 import android.net.Network;
27 import android.net.NetworkCapabilities;
28 import android.net.NetworkInfo;
29 import android.net.TrafficStats;
30 import android.net.wifi.WifiInfo;
31 import android.net.wifi.WifiManager;
32 import android.os.Handler;
33 import android.os.Message;
34 import android.os.SystemClock;
35 import android.os.SystemProperties;
36 import android.os.UserHandle;
37 import android.provider.Settings;
38 import android.telephony.CellIdentityCdma;
39 import android.telephony.CellIdentityGsm;
40 import android.telephony.CellIdentityLte;
41 import android.telephony.CellIdentityWcdma;
42 import android.telephony.CellInfo;
43 import android.telephony.CellInfoCdma;
44 import android.telephony.CellInfoGsm;
45 import android.telephony.CellInfoLte;
46 import android.telephony.CellInfoWcdma;
47 import android.telephony.TelephonyManager;
48
49 import com.android.internal.util.Protocol;
50 import com.android.internal.util.State;
51 import com.android.internal.util.StateMachine;
52 import com.android.server.ConnectivityService;
53 import com.android.server.connectivity.NetworkAgentInfo;
54
55 import java.io.IOException;
56 import java.net.HttpURLConnection;
57 import java.net.URL;
58 import java.util.List;
59
60 /**
61  * {@hide}
62  */
63 public 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;
68     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";
83
84     // 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
97     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     /**
137      * Message to self indicating it's time to evaluate a network's connectivity.
138      * arg1 = Token to ignore old messages.
139      */
140     private static final int CMD_REEVALUATE = BASE + 6;
141
142     /**
143      * Inform NetworkMonitor that the network has disconnected.
144      */
145     public static final int CMD_NETWORK_DISCONNECTED = BASE + 7;
146
147     /**
148      * Force evaluation even if it has succeeded in the past.
149      * arg1 = UID responsible for requesting this reeval.  Will be billed for data.
150      */
151     public static final int CMD_FORCE_REEVALUATION = BASE + 8;
152
153     /**
154      * Message to self indicating captive portal login is complete.
155      * arg1 = Token to ignore old messages.
156      * arg2 = 1 if we should use this network, 0 otherwise.
157      */
158     private static final int CMD_CAPTIVE_PORTAL_LOGGED_IN = BASE + 9;
159
160     /**
161      * Message to self indicating user desires to log into captive portal.
162      * arg1 = Token to ignore old messages.
163      */
164     private static final int CMD_USER_WANTS_SIGN_IN = BASE + 10;
165
166     /**
167      * Request ConnectivityService display provisioning notification.
168      * arg1    = Whether to make the notification visible.
169      * arg2    = NetID.
170      * obj     = Intent to be launched when notification selected by user, null if !arg1.
171      */
172     public static final int EVENT_PROVISIONING_NOTIFICATION = BASE + 11;
173
174     /**
175      * Message to self indicating sign-in app bypassed captive portal.
176      */
177     private static final int EVENT_APP_BYPASSED_CAPTIVE_PORTAL = BASE + 12;
178
179     /**
180      * Message to self indicating no sign-in app responded.
181      */
182     private static final int EVENT_NO_APP_RESPONSE = BASE + 13;
183
184     /**
185      * Message to self indicating sign-in app indicates sign-in is not possible.
186      */
187     private static final int EVENT_APP_INDICATES_SIGN_IN_IMPOSSIBLE = BASE + 14;
188
189     private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger";
190     // Default to 30s linger time-out.
191     private static final int DEFAULT_LINGER_DELAY_MS = 30000;
192     private final int mLingerDelayMs;
193     private int mLingerToken = 0;
194
195     // Negative values disable reevaluation.
196     private static final String REEVALUATE_DELAY_PROPERTY = "persist.netmon.reeval_delay";
197     // Default to 5s reevaluation delay.
198     private static final int DEFAULT_REEVALUATE_DELAY_MS = 5000;
199     private static final int MAX_RETRIES = 10;
200     private final int mReevaluateDelayMs;
201     private int mReevaluateToken = 0;
202     private static final int INVALID_UID = -1;
203     private int mUidResponsibleForReeval = INVALID_UID;
204
205     private int mCaptivePortalLoggedInToken = 0;
206     private int mUserPromptedToken = 0;
207
208     private final Context mContext;
209     private final Handler mConnectivityServiceHandler;
210     private final NetworkAgentInfo mNetworkAgentInfo;
211     private final TelephonyManager mTelephonyManager;
212     private final WifiManager mWifiManager;
213
214     private String mServer;
215     private boolean mIsCaptivePortalCheckEnabled = false;
216
217     public boolean systemReady = false;
218
219     private State mDefaultState = new DefaultState();
220     private State mOfflineState = new OfflineState();
221     private State mValidatedState = new ValidatedState();
222     private State mEvaluatingState = new EvaluatingState();
223     private State mUserPromptedState = new UserPromptedState();
224     private State mCaptivePortalState = new CaptivePortalState();
225     private State mLingeringState = new LingeringState();
226
227     public NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo) {
228         // Add suffix indicating which NetworkMonitor we're talking about.
229         super(TAG + networkAgentInfo.name());
230
231         mContext = context;
232         mConnectivityServiceHandler = handler;
233         mNetworkAgentInfo = networkAgentInfo;
234         mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
235         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
236
237         addState(mDefaultState);
238         addState(mOfflineState, mDefaultState);
239         addState(mValidatedState, mDefaultState);
240         addState(mEvaluatingState, mDefaultState);
241         addState(mUserPromptedState, mDefaultState);
242         addState(mCaptivePortalState, mDefaultState);
243         addState(mLingeringState, mDefaultState);
244         setInitialState(mOfflineState);
245
246         mServer = Settings.Global.getString(mContext.getContentResolver(),
247                 Settings.Global.CAPTIVE_PORTAL_SERVER);
248         if (mServer == null) mServer = DEFAULT_SERVER;
249
250         mLingerDelayMs = SystemProperties.getInt(LINGER_DELAY_PROPERTY, DEFAULT_LINGER_DELAY_MS);
251         mReevaluateDelayMs = SystemProperties.getInt(REEVALUATE_DELAY_PROPERTY,
252                 DEFAULT_REEVALUATE_DELAY_MS);
253
254         mIsCaptivePortalCheckEnabled = Settings.Global.getInt(mContext.getContentResolver(),
255                 Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED, 1) == 1;
256
257         start();
258     }
259
260     private class DefaultState extends State {
261         @Override
262         public boolean processMessage(Message message) {
263             if (DBG) log(getName() + message.toString());
264             switch (message.what) {
265                 case CMD_NETWORK_LINGER:
266                     if (DBG) log("Lingering");
267                     transitionTo(mLingeringState);
268                     return HANDLED;
269                 case CMD_NETWORK_CONNECTED:
270                     if (DBG) log("Connected");
271                     transitionTo(mEvaluatingState);
272                     return HANDLED;
273                 case CMD_NETWORK_DISCONNECTED:
274                     if (DBG) log("Disconnected - quitting");
275                     quit();
276                     return HANDLED;
277                 case CMD_FORCE_REEVALUATION:
278                     if (DBG) log("Forcing reevaluation");
279                     mUidResponsibleForReeval = message.arg1;
280                     transitionTo(mEvaluatingState);
281                     return HANDLED;
282                 default:
283                     return HANDLED;
284             }
285         }
286     }
287
288     private class OfflineState extends State {
289         @Override
290         public boolean processMessage(Message message) {
291             if (DBG) log(getName() + message.toString());
292             return NOT_HANDLED;
293         }
294     }
295
296     private class ValidatedState extends State {
297         @Override
298         public void enter() {
299             if (DBG) log("Validated");
300             mConnectivityServiceHandler.sendMessage(
301                     obtainMessage(EVENT_NETWORK_VALIDATED, mNetworkAgentInfo));
302         }
303
304         @Override
305         public boolean processMessage(Message message) {
306             if (DBG) log(getName() + message.toString());
307             switch (message.what) {
308                 case CMD_NETWORK_CONNECTED:
309                     transitionTo(mValidatedState);
310                     return HANDLED;
311                 default:
312                     return NOT_HANDLED;
313             }
314         }
315     }
316
317     private class EvaluatingState extends State {
318         private int mRetries;
319
320         @Override
321         public void enter() {
322             mRetries = 0;
323             sendMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
324             if (mUidResponsibleForReeval != INVALID_UID) {
325                 TrafficStats.setThreadStatsUid(mUidResponsibleForReeval);
326                 mUidResponsibleForReeval = INVALID_UID;
327             }
328         }
329
330         @Override
331         public boolean processMessage(Message message) {
332             if (DBG) log(getName() + message.toString());
333             switch (message.what) {
334                 case CMD_REEVALUATE:
335                     if (message.arg1 != mReevaluateToken)
336                         return HANDLED;
337                     if (mNetworkAgentInfo.isVPN()) {
338                         transitionTo(mValidatedState);
339                         return HANDLED;
340                     }
341                     // If network provides no internet connectivity adjust evaluation.
342                     if (!mNetworkAgentInfo.networkCapabilities.hasCapability(
343                             NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
344                         // TODO: Try to verify something works.  Do all gateways respond to pings?
345                         transitionTo(mValidatedState);
346                         return HANDLED;
347                     }
348                     int httpResponseCode = isCaptivePortal();
349                     if (httpResponseCode == 204) {
350                         transitionTo(mValidatedState);
351                     } else if (httpResponseCode >= 200 && httpResponseCode <= 399) {
352                         transitionTo(mUserPromptedState);
353                     } else if (++mRetries > MAX_RETRIES) {
354                         transitionTo(mOfflineState);
355                     } else if (mReevaluateDelayMs >= 0) {
356                         Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
357                         sendMessageDelayed(msg, mReevaluateDelayMs);
358                     }
359                     return HANDLED;
360                 case CMD_FORCE_REEVALUATION:
361                     // Ignore duplicate requests.
362                     return HANDLED;
363                 default:
364                     return NOT_HANDLED;
365             }
366         }
367
368         @Override
369         public void exit() {
370             TrafficStats.clearThreadStatsUid();
371         }
372     }
373
374     private class UserPromptedState extends State {
375         private class UserRespondedBroadcastReceiver extends BroadcastReceiver {
376             private final int mToken;
377             UserRespondedBroadcastReceiver(int token) {
378                 mToken = token;
379             }
380             @Override
381             public void onReceive(Context context, Intent intent) {
382                 if (Integer.parseInt(intent.getStringExtra(Intent.EXTRA_TEXT)) ==
383                         mNetworkAgentInfo.network.netId) {
384                     sendMessage(obtainMessage(CMD_USER_WANTS_SIGN_IN, mToken));
385                 }
386             }
387         }
388
389         private UserRespondedBroadcastReceiver mUserRespondedBroadcastReceiver;
390
391         @Override
392         public void enter() {
393             // Wait for user to select sign-in notifcation.
394             mUserRespondedBroadcastReceiver = new UserRespondedBroadcastReceiver(
395                     ++mUserPromptedToken);
396             IntentFilter filter = new IntentFilter(ACTION_SIGN_IN_REQUESTED);
397             mContext.registerReceiver(mUserRespondedBroadcastReceiver, filter);
398             // Initiate notification to sign-in.
399             Intent intent = new Intent(ACTION_SIGN_IN_REQUESTED);
400             intent.putExtra(Intent.EXTRA_TEXT, String.valueOf(mNetworkAgentInfo.network.netId));
401             Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 1,
402                     mNetworkAgentInfo.network.netId,
403                     PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT));
404             mConnectivityServiceHandler.sendMessage(message);
405         }
406
407         @Override
408         public boolean processMessage(Message message) {
409             if (DBG) log(getName() + message.toString());
410             switch (message.what) {
411                 case CMD_USER_WANTS_SIGN_IN:
412                     if (message.arg1 != mUserPromptedToken)
413                         return HANDLED;
414                     transitionTo(mCaptivePortalState);
415                     return HANDLED;
416                 default:
417                     return NOT_HANDLED;
418             }
419         }
420
421         @Override
422         public void exit() {
423             Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 0,
424                     mNetworkAgentInfo.network.netId, null);
425             mConnectivityServiceHandler.sendMessage(message);
426             mContext.unregisterReceiver(mUserRespondedBroadcastReceiver);
427             mUserRespondedBroadcastReceiver = null;
428         }
429     }
430
431     private class CaptivePortalState extends State {
432         private class CaptivePortalLoggedInBroadcastReceiver extends BroadcastReceiver {
433             private final int mToken;
434
435             CaptivePortalLoggedInBroadcastReceiver(int token) {
436                 mToken = token;
437             }
438
439             @Override
440             public void onReceive(Context context, Intent intent) {
441                 if (Integer.parseInt(intent.getStringExtra(Intent.EXTRA_TEXT)) ==
442                         mNetworkAgentInfo.network.netId) {
443                     sendMessage(obtainMessage(CMD_CAPTIVE_PORTAL_LOGGED_IN, mToken,
444                             Integer.parseInt(intent.getStringExtra(LOGGED_IN_RESULT))));
445                 }
446             }
447         }
448
449         private CaptivePortalLoggedInBroadcastReceiver mCaptivePortalLoggedInBroadcastReceiver;
450
451         @Override
452         public void enter() {
453             Intent intent = new Intent(Intent.ACTION_SEND);
454             intent.putExtra(Intent.EXTRA_TEXT, String.valueOf(mNetworkAgentInfo.network.netId));
455             intent.setType("text/plain");
456             intent.setComponent(new ComponentName("com.android.captiveportallogin",
457                     "com.android.captiveportallogin.CaptivePortalLoginActivity"));
458             intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
459
460             // Wait for result.
461             mCaptivePortalLoggedInBroadcastReceiver = new CaptivePortalLoggedInBroadcastReceiver(
462                     ++mCaptivePortalLoggedInToken);
463             IntentFilter filter = new IntentFilter(ACTION_CAPTIVE_PORTAL_LOGGED_IN);
464             mContext.registerReceiver(mCaptivePortalLoggedInBroadcastReceiver, filter);
465             // Initiate app to log in.
466             mContext.startActivityAsUser(intent, UserHandle.CURRENT);
467         }
468
469         @Override
470         public boolean processMessage(Message message) {
471             if (DBG) log(getName() + message.toString());
472             switch (message.what) {
473                 case CMD_CAPTIVE_PORTAL_LOGGED_IN:
474                     if (message.arg1 != mCaptivePortalLoggedInToken)
475                         return HANDLED;
476                     if (message.arg2 == 0) {
477                         // TODO: Should teardown network.
478                         transitionTo(mOfflineState);
479                     } else {
480                         transitionTo(mValidatedState);
481                     }
482                     return HANDLED;
483                 default:
484                     return NOT_HANDLED;
485             }
486         }
487
488         @Override
489         public void exit() {
490             mContext.unregisterReceiver(mCaptivePortalLoggedInBroadcastReceiver);
491             mCaptivePortalLoggedInBroadcastReceiver = null;
492         }
493     }
494
495     private class LingeringState extends State {
496         @Override
497         public void enter() {
498             Message message = obtainMessage(CMD_LINGER_EXPIRED, ++mLingerToken, 0);
499             sendMessageDelayed(message, mLingerDelayMs);
500         }
501
502         @Override
503         public boolean processMessage(Message message) {
504             if (DBG) log(getName() + message.toString());
505             switch (message.what) {
506                 case CMD_NETWORK_CONNECTED:
507                     // Go straight to active as we've already evaluated.
508                     transitionTo(mValidatedState);
509                     return HANDLED;
510                 case CMD_LINGER_EXPIRED:
511                     if (message.arg1 != mLingerToken)
512                         return HANDLED;
513                     mConnectivityServiceHandler.sendMessage(
514                             obtainMessage(EVENT_NETWORK_LINGER_COMPLETE, mNetworkAgentInfo));
515                     return HANDLED;
516                 default:
517                     return NOT_HANDLED;
518             }
519         }
520     }
521
522     /**
523      * Do a URL fetch on a known server to see if we get the data we expect.
524      * Returns HTTP response code.
525      */
526     private int isCaptivePortal() {
527         if (!mIsCaptivePortalCheckEnabled) return 204;
528
529         HttpURLConnection urlConnection = null;
530         int httpResponseCode = 599;
531         try {
532             URL url = new URL("http", mServer, "/generate_204");
533             if (DBG) {
534                 log("Checking " + url.toString() + " on " +
535                         mNetworkAgentInfo.networkInfo.getExtraInfo());
536             }
537             urlConnection = (HttpURLConnection) mNetworkAgentInfo.network.openConnection(url);
538             urlConnection.setInstanceFollowRedirects(false);
539             urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
540             urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
541             urlConnection.setUseCaches(false);
542
543             // Time how long it takes to get a response to our request
544             long requestTimestamp = SystemClock.elapsedRealtime();
545
546             urlConnection.getInputStream();
547
548             // Time how long it takes to get a response to our request
549             long responseTimestamp = SystemClock.elapsedRealtime();
550
551             httpResponseCode = urlConnection.getResponseCode();
552             if (DBG) {
553                 log("isCaptivePortal: ret=" + httpResponseCode +
554                         " headers=" + urlConnection.getHeaderFields());
555             }
556             // NOTE: We may want to consider an "HTTP/1.0 204" response to be a captive
557             // portal.  The only example of this seen so far was a captive portal.  For
558             // the time being go with prior behavior of assuming it's not a captive
559             // portal.  If it is considered a captive portal, a different sign-in URL
560             // is needed (i.e. can't browse a 204).  This could be the result of an HTTP
561             // proxy server.
562
563             // Consider 200 response with "Content-length=0" to not be a captive portal.
564             // There's no point in considering this a captive portal as the user cannot
565             // sign-in to an empty page.  Probably the result of a broken transparent proxy.
566             // See http://b/9972012.
567             if (httpResponseCode == 200 && urlConnection.getContentLength() == 0) {
568                 if (DBG) log("Empty 200 response interpreted as 204 response.");
569                 httpResponseCode = 204;
570             }
571
572             sendNetworkConditionsBroadcast(true /* response received */, httpResponseCode == 204,
573                     requestTimestamp, responseTimestamp);
574         } catch (IOException e) {
575             if (DBG) log("Probably not a portal: exception " + e);
576             if (httpResponseCode == 599) {
577                 // TODO: Ping gateway and DNS server and log results.
578             }
579         } finally {
580             if (urlConnection != null) {
581                 urlConnection.disconnect();
582             }
583         }
584         return httpResponseCode;
585     }
586
587     /**
588      * @param responseReceived - whether or not we received a valid HTTP response to our request.
589      * If false, isCaptivePortal and responseTimestampMs are ignored
590      * TODO: This should be moved to the transports.  The latency could be passed to the transports
591      * along with the captive portal result.  Currently the TYPE_MOBILE broadcasts appear unused so
592      * perhaps this could just be added to the WiFi transport only.
593      */
594     private void sendNetworkConditionsBroadcast(boolean responseReceived, boolean isCaptivePortal,
595             long requestTimestampMs, long responseTimestampMs) {
596         if (Settings.Global.getInt(mContext.getContentResolver(),
597                 Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 0) {
598             if (DBG) log("Don't send network conditions - lacking user consent.");
599             return;
600         }
601
602         if (systemReady == false) return;
603
604         Intent latencyBroadcast = new Intent(ACTION_NETWORK_CONDITIONS_MEASURED);
605         switch (mNetworkAgentInfo.networkInfo.getType()) {
606             case ConnectivityManager.TYPE_WIFI:
607                 WifiInfo currentWifiInfo = mWifiManager.getConnectionInfo();
608                 if (currentWifiInfo != null) {
609                     // NOTE: getSSID()'s behavior changed in API 17; before that, SSIDs were not
610                     // surrounded by double quotation marks (thus violating the Javadoc), but this
611                     // was changed to match the Javadoc in API 17. Since clients may have started
612                     // sanitizing the output of this method since API 17 was released, we should
613                     // not change it here as it would become impossible to tell whether the SSID is
614                     // simply being surrounded by quotes due to the API, or whether those quotes
615                     // are actually part of the SSID.
616                     latencyBroadcast.putExtra(EXTRA_SSID, currentWifiInfo.getSSID());
617                     latencyBroadcast.putExtra(EXTRA_BSSID, currentWifiInfo.getBSSID());
618                 } else {
619                     if (DBG) logw("network info is TYPE_WIFI but no ConnectionInfo found");
620                     return;
621                 }
622                 break;
623             case ConnectivityManager.TYPE_MOBILE:
624                 latencyBroadcast.putExtra(EXTRA_NETWORK_TYPE, mTelephonyManager.getNetworkType());
625                 List<CellInfo> info = mTelephonyManager.getAllCellInfo();
626                 if (info == null) return;
627                 int numRegisteredCellInfo = 0;
628                 for (CellInfo cellInfo : info) {
629                     if (cellInfo.isRegistered()) {
630                         numRegisteredCellInfo++;
631                         if (numRegisteredCellInfo > 1) {
632                             if (DBG) log("more than one registered CellInfo.  Can't " +
633                                     "tell which is active.  Bailing.");
634                             return;
635                         }
636                         if (cellInfo instanceof CellInfoCdma) {
637                             CellIdentityCdma cellId = ((CellInfoCdma) cellInfo).getCellIdentity();
638                             latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId);
639                         } else if (cellInfo instanceof CellInfoGsm) {
640                             CellIdentityGsm cellId = ((CellInfoGsm) cellInfo).getCellIdentity();
641                             latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId);
642                         } else if (cellInfo instanceof CellInfoLte) {
643                             CellIdentityLte cellId = ((CellInfoLte) cellInfo).getCellIdentity();
644                             latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId);
645                         } else if (cellInfo instanceof CellInfoWcdma) {
646                             CellIdentityWcdma cellId = ((CellInfoWcdma) cellInfo).getCellIdentity();
647                             latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId);
648                         } else {
649                             if (DBG) logw("Registered cellinfo is unrecognized");
650                             return;
651                         }
652                     }
653                 }
654                 break;
655             default:
656                 return;
657         }
658         latencyBroadcast.putExtra(EXTRA_CONNECTIVITY_TYPE, mNetworkAgentInfo.networkInfo.getType());
659         latencyBroadcast.putExtra(EXTRA_RESPONSE_RECEIVED, responseReceived);
660         latencyBroadcast.putExtra(EXTRA_REQUEST_TIMESTAMP_MS, requestTimestampMs);
661
662         if (responseReceived) {
663             latencyBroadcast.putExtra(EXTRA_IS_CAPTIVE_PORTAL, isCaptivePortal);
664             latencyBroadcast.putExtra(EXTRA_RESPONSE_TIMESTAMP_MS, responseTimestampMs);
665         }
666         mContext.sendBroadcastAsUser(latencyBroadcast, UserHandle.CURRENT,
667                 PERMISSION_ACCESS_NETWORK_CONDITIONS);
668     }
669 }