NetworkMonitor.java revision 7ccd3dfd53d8d45c447398ff137f052865dfd3b3
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
17package com.android.server.connectivity;
18
19import android.app.PendingIntent;
20import android.content.BroadcastReceiver;
21import android.content.ComponentName;
22import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.net.ConnectivityManager;
26import android.net.Network;
27import android.net.NetworkCapabilities;
28import android.net.NetworkInfo;
29import android.net.TrafficStats;
30import android.net.wifi.WifiInfo;
31import android.net.wifi.WifiManager;
32import android.os.Handler;
33import android.os.Message;
34import android.os.SystemClock;
35import android.os.SystemProperties;
36import android.os.UserHandle;
37import android.provider.Settings;
38import android.telephony.CellIdentityCdma;
39import android.telephony.CellIdentityGsm;
40import android.telephony.CellIdentityLte;
41import android.telephony.CellIdentityWcdma;
42import android.telephony.CellInfo;
43import android.telephony.CellInfoCdma;
44import android.telephony.CellInfoGsm;
45import android.telephony.CellInfoLte;
46import android.telephony.CellInfoWcdma;
47import android.telephony.TelephonyManager;
48
49import com.android.internal.util.Protocol;
50import com.android.internal.util.State;
51import com.android.internal.util.StateMachine;
52import com.android.server.ConnectivityService;
53import com.android.server.connectivity.NetworkAgentInfo;
54
55import java.io.IOException;
56import java.net.HttpURLConnection;
57import java.net.URL;
58import java.util.List;
59
60/**
61 * {@hide}
62 */
63public class NetworkMonitor extends StateMachine {
64    private static final boolean DBG = true;
65    private static final String TAG = "NetworkMonitor";
66    private static final String DEFAULT_SERVER = "clients3.google.com";
67    private static final int SOCKET_TIMEOUT_MS = 10000;
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}
670