FileDocCategorySizeDatePackage
NetworkMonitor.javaAPI DocAndroid 5.1 API40400Thu Mar 12 22:22:42 GMT 2015com.android.server.connectivity

NetworkMonitor

public class NetworkMonitor extends com.android.internal.util.StateMachine
{@hide}

Fields Summary
private static final boolean
DBG
private static final String
TAG
private static final String
DEFAULT_SERVER
private static final int
SOCKET_TIMEOUT_MS
public static final String
ACTION_NETWORK_CONDITIONS_MEASURED
public static final String
EXTRA_CONNECTIVITY_TYPE
public static final String
EXTRA_NETWORK_TYPE
public static final String
EXTRA_RESPONSE_RECEIVED
public static final String
EXTRA_IS_CAPTIVE_PORTAL
public static final String
EXTRA_CELL_ID
public static final String
EXTRA_SSID
public static final String
EXTRA_BSSID
public static final String
EXTRA_REQUEST_TIMESTAMP_MS
real time since boot
public static final String
EXTRA_RESPONSE_TIMESTAMP_MS
private static final String
PERMISSION_ACCESS_NETWORK_CONDITIONS
private static final String
ACTION_CAPTIVE_PORTAL_LOGGED_IN
private static final String
LOGGED_IN_RESULT
private static final String
RESPONSE_TOKEN
public static final int
NETWORK_TEST_RESULT_VALID
public static final int
NETWORK_TEST_RESULT_INVALID
private static final int
BASE
public static final int
CMD_NETWORK_CONNECTED
Inform NetworkMonitor that their network is connected. Initiates Network Validation.
public static final int
EVENT_NETWORK_TESTED
Inform ConnectivityService that the network has been tested. obj = NetworkAgentInfo arg1 = One of the NETWORK_TESTED_RESULT_* constants.
public static final int
CMD_NETWORK_LINGER
Inform NetworkMonitor to linger a network. The Monitor should start a timer and/or start watching for zero live connections while moving towards LINGER_COMPLETE. After the Linger period expires (or other events mark the end of the linger state) the LINGER_COMPLETE event should be sent and the network will be shut down. If a CMD_NETWORK_CONNECTED happens before the LINGER completes it indicates further desire to keep the network alive and so the LINGER is aborted.
private static final int
CMD_LINGER_EXPIRED
Message to self indicating linger delay has expired. arg1 = Token to ignore old messages.
public static final int
EVENT_NETWORK_LINGER_COMPLETE
Inform ConnectivityService that the network LINGER period has expired. obj = NetworkAgentInfo
private static final int
CMD_REEVALUATE
Message to self indicating it's time to evaluate a network's connectivity. arg1 = Token to ignore old messages.
public static final int
CMD_NETWORK_DISCONNECTED
Inform NetworkMonitor that the network has disconnected.
public static final int
CMD_FORCE_REEVALUATION
Force evaluation even if it has succeeded in the past. arg1 = UID responsible for requesting this reeval. Will be billed for data. arg2 = Number of evaluation attempts to make. (If 0, make INITIAL_ATTEMPTS attempts.)
private static final int
CMD_CAPTIVE_PORTAL_APP_FINISHED
Message to self indicating captive portal app finished. arg1 = one of: CAPTIVE_PORTAL_APP_RETURN_APPEASED, CAPTIVE_PORTAL_APP_RETURN_UNWANTED, CAPTIVE_PORTAL_APP_RETURN_WANTED_AS_IS
public static final int
EVENT_PROVISIONING_NOTIFICATION
Request ConnectivityService display provisioning notification. arg1 = Whether to make the notification visible. arg2 = NetID. obj = Intent to be launched when notification selected by user, null if !arg1.
private static final int
EVENT_APP_BYPASSED_CAPTIVE_PORTAL
Message to self indicating sign-in app bypassed captive portal.
private static final int
EVENT_NO_APP_RESPONSE
Message to self indicating no sign-in app responded.
private static final int
EVENT_APP_INDICATES_SIGN_IN_IMPOSSIBLE
Message to self indicating sign-in app indicates sign-in is not possible.
public static final int
CAPTIVE_PORTAL_APP_RETURN_APPEASED
Return codes from captive portal sign-in app.
public static final int
CAPTIVE_PORTAL_APP_RETURN_UNWANTED
public static final int
CAPTIVE_PORTAL_APP_RETURN_WANTED_AS_IS
private static final String
LINGER_DELAY_PROPERTY
private static final int
DEFAULT_LINGER_DELAY_MS
private final int
mLingerDelayMs
private int
mLingerToken
private static final String
REEVALUATE_DELAY_PROPERTY
private static final int
DEFAULT_REEVALUATE_DELAY_MS
private static final int
INITIAL_ATTEMPTS
private static final int
REEVALUATE_PAUSE_MS
private static final int
PERIODIC_ATTEMPTS
private static final int
REEVALUATE_ATTEMPTS
private final int
mReevaluateDelayMs
private int
mReevaluateToken
private static final int
INVALID_UID
private int
mUidResponsibleForReeval
private final android.content.Context
mContext
private final android.os.Handler
mConnectivityServiceHandler
private final com.android.server.connectivity.NetworkAgentInfo
mNetworkAgentInfo
private final android.telephony.TelephonyManager
mTelephonyManager
private final android.net.wifi.WifiManager
mWifiManager
private final android.app.AlarmManager
mAlarmManager
private final android.net.NetworkRequest
mDefaultRequest
private String
mServer
private boolean
mIsCaptivePortalCheckEnabled
private boolean
mUserDoesNotWant
private int
mMaxAttempts
public boolean
systemReady
private final com.android.internal.util.State
mDefaultState
private final com.android.internal.util.State
mOfflineState
private final com.android.internal.util.State
mValidatedState
private final com.android.internal.util.State
mMaybeNotifyState
private final com.android.internal.util.State
mEvaluatingState
private final com.android.internal.util.State
mCaptivePortalState
private final com.android.internal.util.State
mLingeringState
private CaptivePortalLoggedInBroadcastReceiver
mCaptivePortalLoggedInBroadcastReceiver
private String
mCaptivePortalLoggedInResponseToken
Constructors Summary
public NetworkMonitor(android.content.Context context, android.os.Handler handler, com.android.server.connectivity.NetworkAgentInfo networkAgentInfo, android.net.NetworkRequest defaultRequest)


          
              
        // Add suffix indicating which NetworkMonitor we're talking about.
        super(TAG + networkAgentInfo.name());

        mContext = context;
        mConnectivityServiceHandler = handler;
        mNetworkAgentInfo = networkAgentInfo;
        mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
        mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
        mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        mDefaultRequest = defaultRequest;

        addState(mDefaultState);
        addState(mOfflineState, mDefaultState);
        addState(mValidatedState, mDefaultState);
        addState(mMaybeNotifyState, mDefaultState);
            addState(mEvaluatingState, mMaybeNotifyState);
            addState(mCaptivePortalState, mMaybeNotifyState);
        addState(mLingeringState, mDefaultState);
        setInitialState(mDefaultState);

        mServer = Settings.Global.getString(mContext.getContentResolver(),
                Settings.Global.CAPTIVE_PORTAL_SERVER);
        if (mServer == null) mServer = DEFAULT_SERVER;

        mLingerDelayMs = SystemProperties.getInt(LINGER_DELAY_PROPERTY, DEFAULT_LINGER_DELAY_MS);
        mReevaluateDelayMs = SystemProperties.getInt(REEVALUATE_DELAY_PROPERTY,
                DEFAULT_REEVALUATE_DELAY_MS);

        mIsCaptivePortalCheckEnabled = Settings.Global.getInt(mContext.getContentResolver(),
                Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED, 1) == 1;

        mCaptivePortalLoggedInResponseToken = String.valueOf(new Random().nextLong());

        start();
    
Methods Summary
private intisCaptivePortal()
Do a URL fetch on a known server to see if we get the data we expect. Returns HTTP response code.

        if (!mIsCaptivePortalCheckEnabled) return 204;

        HttpURLConnection urlConnection = null;
        int httpResponseCode = 599;
        try {
            URL url = new URL("http", mServer, "/generate_204");
            // On networks with a PAC instead of fetching a URL that should result in a 204
            // reponse, we instead simply fetch the PAC script.  This is done for a few reasons:
            // 1. At present our PAC code does not yet handle multiple PACs on multiple networks
            //    until something like https://android-review.googlesource.com/#/c/115180/ lands.
            //    Network.openConnection() will ignore network-specific PACs and instead fetch
            //    using NO_PROXY.  If a PAC is in place, the only fetch we know will succeed with
            //    NO_PROXY is the fetch of the PAC itself.
            // 2. To proxy the generate_204 fetch through a PAC would require a number of things
            //    happen before the fetch can commence, namely:
            //        a) the PAC script be fetched
            //        b) a PAC script resolver service be fired up and resolve mServer
            //    Network validation could be delayed until these prerequisities are satisifed or
            //    could simply be left to race them.  Neither is an optimal solution.
            // 3. PAC scripts are sometimes used to block or restrict Internet access and may in
            //    fact block fetching of the generate_204 URL which would lead to false negative
            //    results for network validation.
            boolean fetchPac = false;
            {
                final ProxyInfo proxyInfo = mNetworkAgentInfo.linkProperties.getHttpProxy();
                if (proxyInfo != null && !Uri.EMPTY.equals(proxyInfo.getPacFileUrl())) {
                    url = new URL(proxyInfo.getPacFileUrl().toString());
                    fetchPac = true;
                }
            }
            if (DBG) {
                log("Checking " + url.toString() + " on " +
                        mNetworkAgentInfo.networkInfo.getExtraInfo());
            }
            urlConnection = (HttpURLConnection) mNetworkAgentInfo.network.openConnection(url);
            urlConnection.setInstanceFollowRedirects(fetchPac);
            urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
            urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
            urlConnection.setUseCaches(false);

            // Time how long it takes to get a response to our request
            long requestTimestamp = SystemClock.elapsedRealtime();

            urlConnection.getInputStream();

            // Time how long it takes to get a response to our request
            long responseTimestamp = SystemClock.elapsedRealtime();

            httpResponseCode = urlConnection.getResponseCode();
            if (DBG) {
                log("isCaptivePortal: ret=" + httpResponseCode +
                        " headers=" + urlConnection.getHeaderFields());
            }
            // NOTE: We may want to consider an "HTTP/1.0 204" response to be a captive
            // portal.  The only example of this seen so far was a captive portal.  For
            // the time being go with prior behavior of assuming it's not a captive
            // portal.  If it is considered a captive portal, a different sign-in URL
            // is needed (i.e. can't browse a 204).  This could be the result of an HTTP
            // proxy server.

            // Consider 200 response with "Content-length=0" to not be a captive portal.
            // There's no point in considering this a captive portal as the user cannot
            // sign-in to an empty page.  Probably the result of a broken transparent proxy.
            // See http://b/9972012.
            if (httpResponseCode == 200 && urlConnection.getContentLength() == 0) {
                if (DBG) log("Empty 200 response interpreted as 204 response.");
                httpResponseCode = 204;
            }

            if (httpResponseCode == 200 && fetchPac) {
                if (DBG) log("PAC fetch 200 response interpreted as 204 response.");
                httpResponseCode = 204;
            }

            sendNetworkConditionsBroadcast(true /* response received */,
                    httpResponseCode != 204 /* isCaptivePortal */,
                    requestTimestamp, responseTimestamp);
        } catch (IOException e) {
            if (DBG) log("Probably not a portal: exception " + e);
            if (httpResponseCode == 599) {
                // TODO: Ping gateway and DNS server and log results.
            }
        } finally {
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
        }
        return httpResponseCode;
    
protected voidlog(java.lang.String s)

        Log.d(TAG + "/" + mNetworkAgentInfo.name(), s);
    
private voidsendNetworkConditionsBroadcast(boolean responseReceived, boolean isCaptivePortal, long requestTimestampMs, long responseTimestampMs)

param
responseReceived - whether or not we received a valid HTTP response to our request. If false, isCaptivePortal and responseTimestampMs are ignored TODO: This should be moved to the transports. The latency could be passed to the transports along with the captive portal result. Currently the TYPE_MOBILE broadcasts appear unused so perhaps this could just be added to the WiFi transport only.

        if (Settings.Global.getInt(mContext.getContentResolver(),
                Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 0) {
            if (DBG) log("Don't send network conditions - lacking user consent.");
            return;
        }

        if (systemReady == false) return;

        Intent latencyBroadcast = new Intent(ACTION_NETWORK_CONDITIONS_MEASURED);
        switch (mNetworkAgentInfo.networkInfo.getType()) {
            case ConnectivityManager.TYPE_WIFI:
                WifiInfo currentWifiInfo = mWifiManager.getConnectionInfo();
                if (currentWifiInfo != null) {
                    // NOTE: getSSID()'s behavior changed in API 17; before that, SSIDs were not
                    // surrounded by double quotation marks (thus violating the Javadoc), but this
                    // was changed to match the Javadoc in API 17. Since clients may have started
                    // sanitizing the output of this method since API 17 was released, we should
                    // not change it here as it would become impossible to tell whether the SSID is
                    // simply being surrounded by quotes due to the API, or whether those quotes
                    // are actually part of the SSID.
                    latencyBroadcast.putExtra(EXTRA_SSID, currentWifiInfo.getSSID());
                    latencyBroadcast.putExtra(EXTRA_BSSID, currentWifiInfo.getBSSID());
                } else {
                    if (DBG) logw("network info is TYPE_WIFI but no ConnectionInfo found");
                    return;
                }
                break;
            case ConnectivityManager.TYPE_MOBILE:
                latencyBroadcast.putExtra(EXTRA_NETWORK_TYPE, mTelephonyManager.getNetworkType());
                List<CellInfo> info = mTelephonyManager.getAllCellInfo();
                if (info == null) return;
                int numRegisteredCellInfo = 0;
                for (CellInfo cellInfo : info) {
                    if (cellInfo.isRegistered()) {
                        numRegisteredCellInfo++;
                        if (numRegisteredCellInfo > 1) {
                            if (DBG) log("more than one registered CellInfo.  Can't " +
                                    "tell which is active.  Bailing.");
                            return;
                        }
                        if (cellInfo instanceof CellInfoCdma) {
                            CellIdentityCdma cellId = ((CellInfoCdma) cellInfo).getCellIdentity();
                            latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId);
                        } else if (cellInfo instanceof CellInfoGsm) {
                            CellIdentityGsm cellId = ((CellInfoGsm) cellInfo).getCellIdentity();
                            latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId);
                        } else if (cellInfo instanceof CellInfoLte) {
                            CellIdentityLte cellId = ((CellInfoLte) cellInfo).getCellIdentity();
                            latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId);
                        } else if (cellInfo instanceof CellInfoWcdma) {
                            CellIdentityWcdma cellId = ((CellInfoWcdma) cellInfo).getCellIdentity();
                            latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId);
                        } else {
                            if (DBG) logw("Registered cellinfo is unrecognized");
                            return;
                        }
                    }
                }
                break;
            default:
                return;
        }
        latencyBroadcast.putExtra(EXTRA_CONNECTIVITY_TYPE, mNetworkAgentInfo.networkInfo.getType());
        latencyBroadcast.putExtra(EXTRA_RESPONSE_RECEIVED, responseReceived);
        latencyBroadcast.putExtra(EXTRA_REQUEST_TIMESTAMP_MS, requestTimestampMs);

        if (responseReceived) {
            latencyBroadcast.putExtra(EXTRA_IS_CAPTIVE_PORTAL, isCaptivePortal);
            latencyBroadcast.putExtra(EXTRA_RESPONSE_TIMESTAMP_MS, responseTimestampMs);
        }
        mContext.sendBroadcastAsUser(latencyBroadcast, UserHandle.CURRENT,
                PERMISSION_ACCESS_NETWORK_CONDITIONS);