Home | History | Annotate | Download | only in connectivity
      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.ProxyInfo;
     32 import android.net.TrafficStats;
     33 import android.net.Uri;
     34 import android.net.wifi.WifiInfo;
     35 import android.net.wifi.WifiManager;
     36 import android.os.Handler;
     37 import android.os.Message;
     38 import android.os.SystemClock;
     39 import android.os.SystemProperties;
     40 import android.os.UserHandle;
     41 import android.provider.Settings;
     42 import android.telephony.CellIdentityCdma;
     43 import android.telephony.CellIdentityGsm;
     44 import android.telephony.CellIdentityLte;
     45 import android.telephony.CellIdentityWcdma;
     46 import android.telephony.CellInfo;
     47 import android.telephony.CellInfoCdma;
     48 import android.telephony.CellInfoGsm;
     49 import android.telephony.CellInfoLte;
     50 import android.telephony.CellInfoWcdma;
     51 import android.telephony.TelephonyManager;
     52 import android.util.Log;
     53 
     54 import com.android.internal.util.Protocol;
     55 import com.android.internal.util.State;
     56 import com.android.internal.util.StateMachine;
     57 import com.android.server.ConnectivityService;
     58 import com.android.server.connectivity.NetworkAgentInfo;
     59 
     60 import java.io.IOException;
     61 import java.net.HttpURLConnection;
     62 import java.net.URL;
     63 import java.util.List;
     64 import java.util.Random;
     65 
     66 /**
     67  * {@hide}
     68  */
     69 public class NetworkMonitor extends StateMachine {
     70     private static final boolean DBG = true;
     71     private static final String TAG = "NetworkMonitor";
     72     private static final String DEFAULT_SERVER = "connectivitycheck.android.com";
     73     private static final int SOCKET_TIMEOUT_MS = 10000;
     74     public static final String ACTION_NETWORK_CONDITIONS_MEASURED =
     75             "android.net.conn.NETWORK_CONDITIONS_MEASURED";
     76     public static final String EXTRA_CONNECTIVITY_TYPE = "extra_connectivity_type";
     77     public static final String EXTRA_NETWORK_TYPE = "extra_network_type";
     78     public static final String EXTRA_RESPONSE_RECEIVED = "extra_response_received";
     79     public static final String EXTRA_IS_CAPTIVE_PORTAL = "extra_is_captive_portal";
     80     public static final String EXTRA_CELL_ID = "extra_cellid";
     81     public static final String EXTRA_SSID = "extra_ssid";
     82     public static final String EXTRA_BSSID = "extra_bssid";
     83     /** real time since boot */
     84     public static final String EXTRA_REQUEST_TIMESTAMP_MS = "extra_request_timestamp_ms";
     85     public static final String EXTRA_RESPONSE_TIMESTAMP_MS = "extra_response_timestamp_ms";
     86 
     87     private static final String PERMISSION_ACCESS_NETWORK_CONDITIONS =
     88             "android.permission.ACCESS_NETWORK_CONDITIONS";
     89 
     90     // Keep these in sync with CaptivePortalLoginActivity.java.
     91     // Intent broadcast from CaptivePortalLogin indicating sign-in is complete.
     92     // Extras:
     93     //     EXTRA_TEXT       = netId
     94     //     LOGGED_IN_RESULT = one of the CAPTIVE_PORTAL_APP_RETURN_* values below.
     95     //     RESPONSE_TOKEN   = data fragment from launching Intent
     96     private static final String ACTION_CAPTIVE_PORTAL_LOGGED_IN =
     97             "android.net.netmon.captive_portal_logged_in";
     98     private static final String LOGGED_IN_RESULT = "result";
     99     private static final String RESPONSE_TOKEN = "response_token";
    100 
    101     // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED.
    102     // The network should be used as a default internet connection.  It was found to be:
    103     // 1. a functioning network providing internet access, or
    104     // 2. a captive portal and the user decided to use it as is.
    105     public static final int NETWORK_TEST_RESULT_VALID = 0;
    106     // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED.
    107     // The network should not be used as a default internet connection.  It was found to be:
    108     // 1. a captive portal and the user is prompted to sign-in, or
    109     // 2. a captive portal and the user did not want to use it, or
    110     // 3. a broken network (e.g. DNS failed, connect failed, HTTP request failed).
    111     public static final int NETWORK_TEST_RESULT_INVALID = 1;
    112 
    113     private static final int BASE = Protocol.BASE_NETWORK_MONITOR;
    114 
    115     /**
    116      * Inform NetworkMonitor that their network is connected.
    117      * Initiates Network Validation.
    118      */
    119     public static final int CMD_NETWORK_CONNECTED = BASE + 1;
    120 
    121     /**
    122      * Inform ConnectivityService that the network has been tested.
    123      * obj = NetworkAgentInfo
    124      * arg1 = One of the NETWORK_TESTED_RESULT_* constants.
    125      */
    126     public static final int EVENT_NETWORK_TESTED = BASE + 2;
    127 
    128     /**
    129      * Inform NetworkMonitor to linger a network.  The Monitor should
    130      * start a timer and/or start watching for zero live connections while
    131      * moving towards LINGER_COMPLETE.  After the Linger period expires
    132      * (or other events mark the end of the linger state) the LINGER_COMPLETE
    133      * event should be sent and the network will be shut down.  If a
    134      * CMD_NETWORK_CONNECTED happens before the LINGER completes
    135      * it indicates further desire to keep the network alive and so
    136      * the LINGER is aborted.
    137      */
    138     public static final int CMD_NETWORK_LINGER = BASE + 3;
    139 
    140     /**
    141      * Message to self indicating linger delay has expired.
    142      * arg1 = Token to ignore old messages.
    143      */
    144     private static final int CMD_LINGER_EXPIRED = BASE + 4;
    145 
    146     /**
    147      * Inform ConnectivityService that the network LINGER period has
    148      * expired.
    149      * obj = NetworkAgentInfo
    150      */
    151     public static final int EVENT_NETWORK_LINGER_COMPLETE = BASE + 5;
    152 
    153     /**
    154      * Message to self indicating it's time to evaluate a network's connectivity.
    155      * arg1 = Token to ignore old messages.
    156      */
    157     private static final int CMD_REEVALUATE = BASE + 6;
    158 
    159     /**
    160      * Inform NetworkMonitor that the network has disconnected.
    161      */
    162     public static final int CMD_NETWORK_DISCONNECTED = BASE + 7;
    163 
    164     /**
    165      * Force evaluation even if it has succeeded in the past.
    166      * arg1 = UID responsible for requesting this reeval.  Will be billed for data.
    167      * arg2 = Number of evaluation attempts to make. (If 0, make INITIAL_ATTEMPTS attempts.)
    168      */
    169     public static final int CMD_FORCE_REEVALUATION = BASE + 8;
    170 
    171     /**
    172      * Message to self indicating captive portal app finished.
    173      * arg1 = one of: CAPTIVE_PORTAL_APP_RETURN_APPEASED,
    174      *                CAPTIVE_PORTAL_APP_RETURN_UNWANTED,
    175      *                CAPTIVE_PORTAL_APP_RETURN_WANTED_AS_IS
    176      */
    177     private static final int CMD_CAPTIVE_PORTAL_APP_FINISHED = BASE + 9;
    178 
    179     /**
    180      * Request ConnectivityService display provisioning notification.
    181      * arg1    = Whether to make the notification visible.
    182      * arg2    = NetID.
    183      * obj     = Intent to be launched when notification selected by user, null if !arg1.
    184      */
    185     public static final int EVENT_PROVISIONING_NOTIFICATION = BASE + 10;
    186 
    187     /**
    188      * Message to self indicating sign-in app bypassed captive portal.
    189      */
    190     private static final int EVENT_APP_BYPASSED_CAPTIVE_PORTAL = BASE + 11;
    191 
    192     /**
    193      * Message to self indicating no sign-in app responded.
    194      */
    195     private static final int EVENT_NO_APP_RESPONSE = BASE + 12;
    196 
    197     /**
    198      * Message to self indicating sign-in app indicates sign-in is not possible.
    199      */
    200     private static final int EVENT_APP_INDICATES_SIGN_IN_IMPOSSIBLE = BASE + 13;
    201 
    202     /**
    203      * Return codes from captive portal sign-in app.
    204      */
    205     public static final int CAPTIVE_PORTAL_APP_RETURN_APPEASED = 0;
    206     public static final int CAPTIVE_PORTAL_APP_RETURN_UNWANTED = 1;
    207     public static final int CAPTIVE_PORTAL_APP_RETURN_WANTED_AS_IS = 2;
    208 
    209     private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger";
    210     // Default to 30s linger time-out.
    211     private static final int DEFAULT_LINGER_DELAY_MS = 30000;
    212     private final int mLingerDelayMs;
    213     private int mLingerToken = 0;
    214 
    215     // Negative values disable reevaluation.
    216     private static final String REEVALUATE_DELAY_PROPERTY = "persist.netmon.reeval_delay";
    217     // When connecting, attempt to validate 3 times, pausing 5s between them.
    218     private static final int DEFAULT_REEVALUATE_DELAY_MS = 5000;
    219     private static final int INITIAL_ATTEMPTS = 3;
    220     // If a network is not validated, make one attempt every 10 mins to see if it starts working.
    221     private static final int REEVALUATE_PAUSE_MS = 10*60*1000;
    222     private static final int PERIODIC_ATTEMPTS = 1;
    223     // When an application calls reportBadNetwork, only make one attempt.
    224     private static final int REEVALUATE_ATTEMPTS = 1;
    225     private final int mReevaluateDelayMs;
    226     private int mReevaluateToken = 0;
    227     private static final int INVALID_UID = -1;
    228     private int mUidResponsibleForReeval = INVALID_UID;
    229 
    230     private final Context mContext;
    231     private final Handler mConnectivityServiceHandler;
    232     private final NetworkAgentInfo mNetworkAgentInfo;
    233     private final TelephonyManager mTelephonyManager;
    234     private final WifiManager mWifiManager;
    235     private final AlarmManager mAlarmManager;
    236     private final NetworkRequest mDefaultRequest;
    237 
    238     private String mServer;
    239     private boolean mIsCaptivePortalCheckEnabled = false;
    240 
    241     // Set if the user explicitly selected "Do not use this network" in captive portal sign-in app.
    242     private boolean mUserDoesNotWant = false;
    243 
    244     // How many times we should attempt validation. Only checked in EvaluatingState; must be set
    245     // before entering EvaluatingState. Note that whatever code causes us to transition to
    246     // EvaluatingState last decides how many attempts will be made, so if one codepath were to
    247     // enter EvaluatingState with a specific number of attempts, and then another were to enter it
    248     // with a different number of attempts, the second number would be used. This is not currently
    249     // a problem because EvaluatingState is not reentrant.
    250     private int mMaxAttempts;
    251 
    252     public boolean systemReady = false;
    253 
    254     private final State mDefaultState = new DefaultState();
    255     private final State mOfflineState = new OfflineState();
    256     private final State mValidatedState = new ValidatedState();
    257     private final State mMaybeNotifyState = new MaybeNotifyState();
    258     private final State mEvaluatingState = new EvaluatingState();
    259     private final State mCaptivePortalState = new CaptivePortalState();
    260     private final State mLingeringState = new LingeringState();
    261 
    262     private CaptivePortalLoggedInBroadcastReceiver mCaptivePortalLoggedInBroadcastReceiver = null;
    263     private String mCaptivePortalLoggedInResponseToken = null;
    264 
    265     public NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo,
    266             NetworkRequest defaultRequest) {
    267         // Add suffix indicating which NetworkMonitor we're talking about.
    268         super(TAG + networkAgentInfo.name());
    269 
    270         mContext = context;
    271         mConnectivityServiceHandler = handler;
    272         mNetworkAgentInfo = networkAgentInfo;
    273         mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
    274         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
    275         mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
    276         mDefaultRequest = defaultRequest;
    277 
    278         addState(mDefaultState);
    279         addState(mOfflineState, mDefaultState);
    280         addState(mValidatedState, mDefaultState);
    281         addState(mMaybeNotifyState, mDefaultState);
    282             addState(mEvaluatingState, mMaybeNotifyState);
    283             addState(mCaptivePortalState, mMaybeNotifyState);
    284         addState(mLingeringState, mDefaultState);
    285         setInitialState(mDefaultState);
    286 
    287         mServer = Settings.Global.getString(mContext.getContentResolver(),
    288                 Settings.Global.CAPTIVE_PORTAL_SERVER);
    289         if (mServer == null) mServer = DEFAULT_SERVER;
    290 
    291         mLingerDelayMs = SystemProperties.getInt(LINGER_DELAY_PROPERTY, DEFAULT_LINGER_DELAY_MS);
    292         mReevaluateDelayMs = SystemProperties.getInt(REEVALUATE_DELAY_PROPERTY,
    293                 DEFAULT_REEVALUATE_DELAY_MS);
    294 
    295         mIsCaptivePortalCheckEnabled = Settings.Global.getInt(mContext.getContentResolver(),
    296                 Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED, 1) == 1;
    297 
    298         mCaptivePortalLoggedInResponseToken = String.valueOf(new Random().nextLong());
    299 
    300         start();
    301     }
    302 
    303     @Override
    304     protected void log(String s) {
    305         Log.d(TAG + "/" + mNetworkAgentInfo.name(), s);
    306     }
    307 
    308     // DefaultState is the parent of all States.  It exists only to handle CMD_* messages but
    309     // does not entail any real state (hence no enter() or exit() routines).
    310     private class DefaultState extends State {
    311         @Override
    312         public boolean processMessage(Message message) {
    313             if (DBG) log(getName() + message.toString());
    314             switch (message.what) {
    315                 case CMD_NETWORK_LINGER:
    316                     if (DBG) log("Lingering");
    317                     transitionTo(mLingeringState);
    318                     return HANDLED;
    319                 case CMD_NETWORK_CONNECTED:
    320                     if (DBG) log("Connected");
    321                     mMaxAttempts = INITIAL_ATTEMPTS;
    322                     transitionTo(mEvaluatingState);
    323                     return HANDLED;
    324                 case CMD_NETWORK_DISCONNECTED:
    325                     if (DBG) log("Disconnected - quitting");
    326                     if (mCaptivePortalLoggedInBroadcastReceiver != null) {
    327                         mContext.unregisterReceiver(mCaptivePortalLoggedInBroadcastReceiver);
    328                         mCaptivePortalLoggedInBroadcastReceiver = null;
    329                     }
    330                     quit();
    331                     return HANDLED;
    332                 case CMD_FORCE_REEVALUATION:
    333                     if (DBG) log("Forcing reevaluation");
    334                     mUidResponsibleForReeval = message.arg1;
    335                     mMaxAttempts = message.arg2 != 0 ? message.arg2 : REEVALUATE_ATTEMPTS;
    336                     transitionTo(mEvaluatingState);
    337                     return HANDLED;
    338                 case CMD_CAPTIVE_PORTAL_APP_FINISHED:
    339                     // Previous token was broadcast, come up with a new one.
    340                     mCaptivePortalLoggedInResponseToken = String.valueOf(new Random().nextLong());
    341                     switch (message.arg1) {
    342                         case CAPTIVE_PORTAL_APP_RETURN_APPEASED:
    343                         case CAPTIVE_PORTAL_APP_RETURN_WANTED_AS_IS:
    344                             transitionTo(mValidatedState);
    345                             break;
    346                         case CAPTIVE_PORTAL_APP_RETURN_UNWANTED:
    347                             mUserDoesNotWant = true;
    348                             // TODO: Should teardown network.
    349                             transitionTo(mOfflineState);
    350                             break;
    351                     }
    352                     return HANDLED;
    353                 default:
    354                     return HANDLED;
    355             }
    356         }
    357     }
    358 
    359     // Being in the OfflineState State indicates a Network is unwanted or failed validation.
    360     private class OfflineState extends State {
    361         @Override
    362         public void enter() {
    363             mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED,
    364                     NETWORK_TEST_RESULT_INVALID, 0, mNetworkAgentInfo));
    365             if (!mUserDoesNotWant) {
    366                 sendMessageDelayed(CMD_FORCE_REEVALUATION, 0 /* no UID */,
    367                         PERIODIC_ATTEMPTS, REEVALUATE_PAUSE_MS);
    368             }
    369         }
    370 
    371         @Override
    372         public boolean processMessage(Message message) {
    373             if (DBG) log(getName() + message.toString());
    374                         switch (message.what) {
    375                 case CMD_FORCE_REEVALUATION:
    376                     // If the user has indicated they explicitly do not want to use this network,
    377                     // don't allow a reevaluation as this will be pointless and could result in
    378                     // the user being annoyed with repeated unwanted notifications.
    379                     return mUserDoesNotWant ? HANDLED : NOT_HANDLED;
    380                 default:
    381                     return NOT_HANDLED;
    382             }
    383         }
    384 
    385         @Override
    386         public void exit() {
    387              // NOTE: This removes the delayed message posted by enter() but will inadvertently
    388              // remove any other CMD_FORCE_REEVALUATION in the message queue.  At the moment this
    389              // is harmless.  If in the future this becomes problematic a different message could
    390              // be used.
    391              removeMessages(CMD_FORCE_REEVALUATION);
    392         }
    393     }
    394 
    395     // Being in the ValidatedState State indicates a Network is:
    396     // - Successfully validated, or
    397     // - Wanted "as is" by the user, or
    398     // - Does not satsify the default NetworkRequest and so validation has been skipped.
    399     private class ValidatedState extends State {
    400         @Override
    401         public void enter() {
    402             if (DBG) log("Validated");
    403             mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED,
    404                     NETWORK_TEST_RESULT_VALID, 0, mNetworkAgentInfo));
    405         }
    406 
    407         @Override
    408         public boolean processMessage(Message message) {
    409             if (DBG) log(getName() + message.toString());
    410             switch (message.what) {
    411                 case CMD_NETWORK_CONNECTED:
    412                     transitionTo(mValidatedState);
    413                     return HANDLED;
    414                 default:
    415                     return NOT_HANDLED;
    416             }
    417         }
    418     }
    419 
    420     // Being in the MaybeNotifyState State indicates the user may have been notified that sign-in
    421     // is required.  This State takes care to clear the notification upon exit from the State.
    422     private class MaybeNotifyState extends State {
    423         @Override
    424         public void exit() {
    425             Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 0,
    426                     mNetworkAgentInfo.network.netId, null);
    427             mConnectivityServiceHandler.sendMessage(message);
    428         }
    429     }
    430 
    431     // Being in the EvaluatingState State indicates the Network is being evaluated for internet
    432     // connectivity.
    433     private class EvaluatingState extends State {
    434         private int mAttempt;
    435 
    436         @Override
    437         public void enter() {
    438             mAttempt = 1;
    439             sendMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
    440             if (mUidResponsibleForReeval != INVALID_UID) {
    441                 TrafficStats.setThreadStatsUid(mUidResponsibleForReeval);
    442                 mUidResponsibleForReeval = INVALID_UID;
    443             }
    444         }
    445 
    446         @Override
    447         public boolean processMessage(Message message) {
    448             if (DBG) log(getName() + message.toString());
    449             switch (message.what) {
    450                 case CMD_REEVALUATE:
    451                     if (message.arg1 != mReevaluateToken)
    452                         return HANDLED;
    453                     // Don't bother validating networks that don't satisify the default request.
    454                     // This includes:
    455                     //  - VPNs which can be considered explicitly desired by the user and the
    456                     //    user's desire trumps whether the network validates.
    457                     //  - Networks that don't provide internet access.  It's unclear how to
    458                     //    validate such networks.
    459                     //  - Untrusted networks.  It's unsafe to prompt the user to sign-in to
    460                     //    such networks and the user didn't express interest in connecting to
    461                     //    such networks (an app did) so the user may be unhappily surprised when
    462                     //    asked to sign-in to a network they didn't want to connect to in the
    463                     //    first place.  Validation could be done to adjust the network scores
    464                     //    however these networks are app-requested and may not be intended for
    465                     //    general usage, in which case general validation may not be an accurate
    466                     //    measure of the network's quality.  Only the app knows how to evaluate
    467                     //    the network so don't bother validating here.  Furthermore sending HTTP
    468                     //    packets over the network may be undesirable, for example an extremely
    469                     //    expensive metered network, or unwanted leaking of the User Agent string.
    470                     if (!mDefaultRequest.networkCapabilities.satisfiedByNetworkCapabilities(
    471                             mNetworkAgentInfo.networkCapabilities)) {
    472                         transitionTo(mValidatedState);
    473                         return HANDLED;
    474                     }
    475                     // Note: This call to isCaptivePortal() could take up to a minute. Resolving the
    476                     // server's IP addresses could hit the DNS timeout, and attempting connections
    477                     // to each of the server's several IP addresses (currently one IPv4 and one
    478                     // IPv6) could each take SOCKET_TIMEOUT_MS.  During this time this StateMachine
    479                     // will be unresponsive. isCaptivePortal() could be executed on another Thread
    480                     // if this is found to cause problems.
    481                     int httpResponseCode = isCaptivePortal();
    482                     if (httpResponseCode == 204) {
    483                         transitionTo(mValidatedState);
    484                     } else if (httpResponseCode >= 200 && httpResponseCode <= 399) {
    485                         transitionTo(mCaptivePortalState);
    486                     } else if (++mAttempt > mMaxAttempts) {
    487                         transitionTo(mOfflineState);
    488                     } else if (mReevaluateDelayMs >= 0) {
    489                         Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
    490                         sendMessageDelayed(msg, mReevaluateDelayMs);
    491                     }
    492                     return HANDLED;
    493                 case CMD_FORCE_REEVALUATION:
    494                     // Ignore duplicate requests.
    495                     return HANDLED;
    496                 default:
    497                     return NOT_HANDLED;
    498             }
    499         }
    500 
    501         @Override
    502         public void exit() {
    503             TrafficStats.clearThreadStatsUid();
    504         }
    505     }
    506 
    507     // BroadcastReceiver that waits for a particular Intent and then posts a message.
    508     private class CustomIntentReceiver extends BroadcastReceiver {
    509         private final int mToken;
    510         private final int mWhat;
    511         private final String mAction;
    512         CustomIntentReceiver(String action, int token, int what) {
    513             mToken = token;
    514             mWhat = what;
    515             mAction = action + "_" + mNetworkAgentInfo.network.netId + "_" + token;
    516             mContext.registerReceiver(this, new IntentFilter(mAction));
    517         }
    518         public PendingIntent getPendingIntent() {
    519             return PendingIntent.getBroadcast(mContext, 0, new Intent(mAction), 0);
    520         }
    521         @Override
    522         public void onReceive(Context context, Intent intent) {
    523             if (intent.getAction().equals(mAction)) sendMessage(obtainMessage(mWhat, mToken));
    524         }
    525     }
    526 
    527     private class CaptivePortalLoggedInBroadcastReceiver extends BroadcastReceiver {
    528         @Override
    529         public void onReceive(Context context, Intent intent) {
    530             if (Integer.parseInt(intent.getStringExtra(Intent.EXTRA_TEXT)) ==
    531                     mNetworkAgentInfo.network.netId &&
    532                     mCaptivePortalLoggedInResponseToken.equals(
    533                             intent.getStringExtra(RESPONSE_TOKEN))) {
    534                 sendMessage(obtainMessage(CMD_CAPTIVE_PORTAL_APP_FINISHED,
    535                         Integer.parseInt(intent.getStringExtra(LOGGED_IN_RESULT)), 0));
    536             }
    537         }
    538     }
    539 
    540     // Being in the CaptivePortalState State indicates a captive portal was detected and the user
    541     // has been shown a notification to sign-in.
    542     private class CaptivePortalState extends State {
    543         @Override
    544         public void enter() {
    545             mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED,
    546                     NETWORK_TEST_RESULT_INVALID, 0, mNetworkAgentInfo));
    547 
    548             // Assemble Intent to launch captive portal sign-in app.
    549             final Intent intent = new Intent(Intent.ACTION_SEND);
    550             // Intent cannot use extras because PendingIntent.getActivity will merge matching
    551             // Intents erasing extras.  Use data instead of extras to encode NetID.
    552             intent.setData(Uri.fromParts("netid", Integer.toString(mNetworkAgentInfo.network.netId),
    553                     mCaptivePortalLoggedInResponseToken));
    554             intent.setComponent(new ComponentName("com.android.captiveportallogin",
    555                     "com.android.captiveportallogin.CaptivePortalLoginActivity"));
    556             intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
    557 
    558             if (mCaptivePortalLoggedInBroadcastReceiver == null) {
    559                 // Wait for result.
    560                 mCaptivePortalLoggedInBroadcastReceiver =
    561                         new CaptivePortalLoggedInBroadcastReceiver();
    562                 final IntentFilter filter = new IntentFilter(ACTION_CAPTIVE_PORTAL_LOGGED_IN);
    563                 mContext.registerReceiver(mCaptivePortalLoggedInBroadcastReceiver, filter);
    564             }
    565             // Initiate notification to sign-in.
    566             Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 1,
    567                     mNetworkAgentInfo.network.netId,
    568                     PendingIntent.getActivity(mContext, 0, intent, 0));
    569             mConnectivityServiceHandler.sendMessage(message);
    570         }
    571 
    572         @Override
    573         public boolean processMessage(Message message) {
    574             if (DBG) log(getName() + message.toString());
    575             return NOT_HANDLED;
    576         }
    577     }
    578 
    579     // Being in the LingeringState State indicates a Network's validated bit is true and it once
    580     // was the highest scoring Network satisfying a particular NetworkRequest, but since then
    581     // another Network satsified the NetworkRequest with a higher score and hence this Network
    582     // is "lingered" for a fixed period of time before it is disconnected.  This period of time
    583     // allows apps to wrap up communication and allows for seamless reactivation if the other
    584     // higher scoring Network happens to disconnect.
    585     private class LingeringState extends State {
    586         private static final String ACTION_LINGER_EXPIRED = "android.net.netmon.lingerExpired";
    587 
    588         private CustomIntentReceiver mBroadcastReceiver;
    589         private PendingIntent mIntent;
    590 
    591         @Override
    592         public void enter() {
    593             mLingerToken = new Random().nextInt();
    594             mBroadcastReceiver = new CustomIntentReceiver(ACTION_LINGER_EXPIRED, mLingerToken,
    595                     CMD_LINGER_EXPIRED);
    596             mIntent = mBroadcastReceiver.getPendingIntent();
    597             long wakeupTime = SystemClock.elapsedRealtime() + mLingerDelayMs;
    598             mAlarmManager.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP, wakeupTime,
    599                     // Give a specific window so we aren't subject to unknown inexactitude.
    600                     mLingerDelayMs / 6, mIntent);
    601         }
    602 
    603         @Override
    604         public boolean processMessage(Message message) {
    605             if (DBG) log(getName() + message.toString());
    606             switch (message.what) {
    607                 case CMD_NETWORK_CONNECTED:
    608                     // Go straight to active as we've already evaluated.
    609                     transitionTo(mValidatedState);
    610                     return HANDLED;
    611                 case CMD_LINGER_EXPIRED:
    612                     if (message.arg1 != mLingerToken)
    613                         return HANDLED;
    614                     mConnectivityServiceHandler.sendMessage(
    615                             obtainMessage(EVENT_NETWORK_LINGER_COMPLETE, mNetworkAgentInfo));
    616                     return HANDLED;
    617                 case CMD_FORCE_REEVALUATION:
    618                     // Ignore reevaluation attempts when lingering.  A reevaluation could result
    619                     // in a transition to the validated state which would abort the linger
    620                     // timeout.  Lingering is the result of score assessment; validity is
    621                     // irrelevant.
    622                     return HANDLED;
    623                 case CMD_CAPTIVE_PORTAL_APP_FINISHED:
    624                     // Ignore user network determination as this could abort linger timeout.
    625                     // Networks are only lingered once validated because:
    626                     // - Unvalidated networks are never lingered (see rematchNetworkAndRequests).
    627                     // - Once validated, a Network's validated bit is never cleared.
    628                     // Since networks are only lingered after being validated a user's
    629                     // determination will not change the death sentence that lingering entails:
    630                     // - If the user wants to use the network or bypasses the captive portal,
    631                     //   the network's score will not be increased beyond its current value
    632                     //   because it is already validated.  Without a score increase there is no
    633                     //   chance of reactivation (i.e. aborting linger timeout).
    634                     // - If the user does not want the network, lingering will disconnect the
    635                     //   network anyhow.
    636                     return HANDLED;
    637                 default:
    638                     return NOT_HANDLED;
    639             }
    640         }
    641 
    642         @Override
    643         public void exit() {
    644             mAlarmManager.cancel(mIntent);
    645             mContext.unregisterReceiver(mBroadcastReceiver);
    646         }
    647     }
    648 
    649     /**
    650      * Do a URL fetch on a known server to see if we get the data we expect.
    651      * Returns HTTP response code.
    652      */
    653     private int isCaptivePortal() {
    654         if (!mIsCaptivePortalCheckEnabled) return 204;
    655 
    656         HttpURLConnection urlConnection = null;
    657         int httpResponseCode = 599;
    658         try {
    659             URL url = new URL("http", mServer, "/generate_204");
    660             // On networks with a PAC instead of fetching a URL that should result in a 204
    661             // reponse, we instead simply fetch the PAC script.  This is done for a few reasons:
    662             // 1. At present our PAC code does not yet handle multiple PACs on multiple networks
    663             //    until something like https://android-review.googlesource.com/#/c/115180/ lands.
    664             //    Network.openConnection() will ignore network-specific PACs and instead fetch
    665             //    using NO_PROXY.  If a PAC is in place, the only fetch we know will succeed with
    666             //    NO_PROXY is the fetch of the PAC itself.
    667             // 2. To proxy the generate_204 fetch through a PAC would require a number of things
    668             //    happen before the fetch can commence, namely:
    669             //        a) the PAC script be fetched
    670             //        b) a PAC script resolver service be fired up and resolve mServer
    671             //    Network validation could be delayed until these prerequisities are satisifed or
    672             //    could simply be left to race them.  Neither is an optimal solution.
    673             // 3. PAC scripts are sometimes used to block or restrict Internet access and may in
    674             //    fact block fetching of the generate_204 URL which would lead to false negative
    675             //    results for network validation.
    676             boolean fetchPac = false;
    677             {
    678                 final ProxyInfo proxyInfo = mNetworkAgentInfo.linkProperties.getHttpProxy();
    679                 if (proxyInfo != null && !Uri.EMPTY.equals(proxyInfo.getPacFileUrl())) {
    680                     url = new URL(proxyInfo.getPacFileUrl().toString());
    681                     fetchPac = true;
    682                 }
    683             }
    684             if (DBG) {
    685                 log("Checking " + url.toString() + " on " +
    686                         mNetworkAgentInfo.networkInfo.getExtraInfo());
    687             }
    688             urlConnection = (HttpURLConnection) mNetworkAgentInfo.network.openConnection(url);
    689             urlConnection.setInstanceFollowRedirects(fetchPac);
    690             urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
    691             urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
    692             urlConnection.setUseCaches(false);
    693 
    694             // Time how long it takes to get a response to our request
    695             long requestTimestamp = SystemClock.elapsedRealtime();
    696 
    697             urlConnection.getInputStream();
    698 
    699             // Time how long it takes to get a response to our request
    700             long responseTimestamp = SystemClock.elapsedRealtime();
    701 
    702             httpResponseCode = urlConnection.getResponseCode();
    703             if (DBG) {
    704                 log("isCaptivePortal: ret=" + httpResponseCode +
    705                         " headers=" + urlConnection.getHeaderFields());
    706             }
    707             // NOTE: We may want to consider an "HTTP/1.0 204" response to be a captive
    708             // portal.  The only example of this seen so far was a captive portal.  For
    709             // the time being go with prior behavior of assuming it's not a captive
    710             // portal.  If it is considered a captive portal, a different sign-in URL
    711             // is needed (i.e. can't browse a 204).  This could be the result of an HTTP
    712             // proxy server.
    713 
    714             // Consider 200 response with "Content-length=0" to not be a captive portal.
    715             // There's no point in considering this a captive portal as the user cannot
    716             // sign-in to an empty page.  Probably the result of a broken transparent proxy.
    717             // See http://b/9972012.
    718             if (httpResponseCode == 200 && urlConnection.getContentLength() == 0) {
    719                 if (DBG) log("Empty 200 response interpreted as 204 response.");
    720                 httpResponseCode = 204;
    721             }
    722 
    723             if (httpResponseCode == 200 && fetchPac) {
    724                 if (DBG) log("PAC fetch 200 response interpreted as 204 response.");
    725                 httpResponseCode = 204;
    726             }
    727 
    728             sendNetworkConditionsBroadcast(true /* response received */,
    729                     httpResponseCode != 204 /* isCaptivePortal */,
    730                     requestTimestamp, responseTimestamp);
    731         } catch (IOException e) {
    732             if (DBG) log("Probably not a portal: exception " + e);
    733             if (httpResponseCode == 599) {
    734                 // TODO: Ping gateway and DNS server and log results.
    735             }
    736         } finally {
    737             if (urlConnection != null) {
    738                 urlConnection.disconnect();
    739             }
    740         }
    741         return httpResponseCode;
    742     }
    743 
    744     /**
    745      * @param responseReceived - whether or not we received a valid HTTP response to our request.
    746      * If false, isCaptivePortal and responseTimestampMs are ignored
    747      * TODO: This should be moved to the transports.  The latency could be passed to the transports
    748      * along with the captive portal result.  Currently the TYPE_MOBILE broadcasts appear unused so
    749      * perhaps this could just be added to the WiFi transport only.
    750      */
    751     private void sendNetworkConditionsBroadcast(boolean responseReceived, boolean isCaptivePortal,
    752             long requestTimestampMs, long responseTimestampMs) {
    753         if (Settings.Global.getInt(mContext.getContentResolver(),
    754                 Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 0) {
    755             if (DBG) log("Don't send network conditions - lacking user consent.");
    756             return;
    757         }
    758 
    759         if (systemReady == false) return;
    760 
    761         Intent latencyBroadcast = new Intent(ACTION_NETWORK_CONDITIONS_MEASURED);
    762         switch (mNetworkAgentInfo.networkInfo.getType()) {
    763             case ConnectivityManager.TYPE_WIFI:
    764                 WifiInfo currentWifiInfo = mWifiManager.getConnectionInfo();
    765                 if (currentWifiInfo != null) {
    766                     // NOTE: getSSID()'s behavior changed in API 17; before that, SSIDs were not
    767                     // surrounded by double quotation marks (thus violating the Javadoc), but this
    768                     // was changed to match the Javadoc in API 17. Since clients may have started
    769                     // sanitizing the output of this method since API 17 was released, we should
    770                     // not change it here as it would become impossible to tell whether the SSID is
    771                     // simply being surrounded by quotes due to the API, or whether those quotes
    772                     // are actually part of the SSID.
    773                     latencyBroadcast.putExtra(EXTRA_SSID, currentWifiInfo.getSSID());
    774                     latencyBroadcast.putExtra(EXTRA_BSSID, currentWifiInfo.getBSSID());
    775                 } else {
    776                     if (DBG) logw("network info is TYPE_WIFI but no ConnectionInfo found");
    777                     return;
    778                 }
    779                 break;
    780             case ConnectivityManager.TYPE_MOBILE:
    781                 latencyBroadcast.putExtra(EXTRA_NETWORK_TYPE, mTelephonyManager.getNetworkType());
    782                 List<CellInfo> info = mTelephonyManager.getAllCellInfo();
    783                 if (info == null) return;
    784                 int numRegisteredCellInfo = 0;
    785                 for (CellInfo cellInfo : info) {
    786                     if (cellInfo.isRegistered()) {
    787                         numRegisteredCellInfo++;
    788                         if (numRegisteredCellInfo > 1) {
    789                             if (DBG) log("more than one registered CellInfo.  Can't " +
    790                                     "tell which is active.  Bailing.");
    791                             return;
    792                         }
    793                         if (cellInfo instanceof CellInfoCdma) {
    794                             CellIdentityCdma cellId = ((CellInfoCdma) cellInfo).getCellIdentity();
    795                             latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId);
    796                         } else if (cellInfo instanceof CellInfoGsm) {
    797                             CellIdentityGsm cellId = ((CellInfoGsm) cellInfo).getCellIdentity();
    798                             latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId);
    799                         } else if (cellInfo instanceof CellInfoLte) {
    800                             CellIdentityLte cellId = ((CellInfoLte) cellInfo).getCellIdentity();
    801                             latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId);
    802                         } else if (cellInfo instanceof CellInfoWcdma) {
    803                             CellIdentityWcdma cellId = ((CellInfoWcdma) cellInfo).getCellIdentity();
    804                             latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId);
    805                         } else {
    806                             if (DBG) logw("Registered cellinfo is unrecognized");
    807                             return;
    808                         }
    809                     }
    810                 }
    811                 break;
    812             default:
    813                 return;
    814         }
    815         latencyBroadcast.putExtra(EXTRA_CONNECTIVITY_TYPE, mNetworkAgentInfo.networkInfo.getType());
    816         latencyBroadcast.putExtra(EXTRA_RESPONSE_RECEIVED, responseReceived);
    817         latencyBroadcast.putExtra(EXTRA_REQUEST_TIMESTAMP_MS, requestTimestampMs);
    818 
    819         if (responseReceived) {
    820             latencyBroadcast.putExtra(EXTRA_IS_CAPTIVE_PORTAL, isCaptivePortal);
    821             latencyBroadcast.putExtra(EXTRA_RESPONSE_TIMESTAMP_MS, responseTimestampMs);
    822         }
    823         mContext.sendBroadcastAsUser(latencyBroadcast, UserHandle.CURRENT,
    824                 PERMISSION_ACCESS_NETWORK_CONDITIONS);
    825     }
    826 }
    827