Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 314488b7 authored by Irfan Sheriff's avatar Irfan Sheriff Committed by Android (Google) Code Review
Browse files

Merge "Captive check for both mobile and wifi" into jb-mr1-dev

parents d86077c3 9538bdd3
Loading
Loading
Loading
Loading
+164 −83
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.net;

import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -24,33 +25,32 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.net.ConnectivityManager;
import android.net.IConnectivityManager;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.UserHandle;
import android.os.Message;
import android.os.RemoteException;
import android.provider.Settings;
import android.util.Log;

import com.android.internal.util.State;
import com.android.internal.util.StateMachine;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.Inet4Address;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.concurrent.atomic.AtomicBoolean;

import com.android.internal.R;

/**
 * This class allows captive portal detection
 * This class allows captive portal detection on a network.
 * @hide
 */
public class CaptivePortalTracker {
    private static final boolean DBG = true;
public class CaptivePortalTracker extends StateMachine {
    private static final boolean DBG = false;
    private static final String TAG = "CaptivePortalTracker";

    private static final String DEFAULT_SERVER = "clients3.google.com";
@@ -62,37 +62,31 @@ public class CaptivePortalTracker {
    private String mUrl;
    private boolean mNotificationShown = false;
    private boolean mIsCaptivePortalCheckEnabled = false;
    private InternalHandler mHandler;
    private IConnectivityManager mConnService;
    private Context mContext;
    private NetworkInfo mNetworkInfo;
    private boolean mIsCaptivePortal = false;

    private static final int DETECT_PORTAL = 0;
    private static final int HANDLE_CONNECT = 1;
    private static final int CMD_DETECT_PORTAL          = 0;
    private static final int CMD_CONNECTIVITY_CHANGE    = 1;
    private static final int CMD_DELAYED_CAPTIVE_CHECK  = 2;

    /**
     * Activity Action: Switch to the captive portal network
     * <p>Input: Nothing.
     * <p>Output: Nothing.
     */
    public static final String ACTION_SWITCH_TO_CAPTIVE_PORTAL
            = "android.net.SWITCH_TO_CAPTIVE_PORTAL";
    /* This delay happens every time before we do a captive check on a network */
    private static final int DELAYED_CHECK_INTERVAL_MS = 10000;
    private int mDelayedCheckToken = 0;

    private State mDefaultState = new DefaultState();
    private State mNoActiveNetworkState = new NoActiveNetworkState();
    private State mActiveNetworkState = new ActiveNetworkState();
    private State mDelayedCaptiveCheckState = new DelayedCaptiveCheckState();

    private CaptivePortalTracker(Context context, IConnectivityManager cs) {
        super(TAG);

    private CaptivePortalTracker(Context context, NetworkInfo info, IConnectivityManager cs) {
        mContext = context;
        mNetworkInfo = info;
        mConnService = cs;

        HandlerThread handlerThread = new HandlerThread("CaptivePortalThread");
        handlerThread.start();
        mHandler = new InternalHandler(handlerThread.getLooper());
        mHandler.obtainMessage(DETECT_PORTAL).sendToTarget();

        IntentFilter filter = new IntentFilter();
        filter.addAction(ACTION_SWITCH_TO_CAPTIVE_PORTAL);
        filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);

        mContext.registerReceiver(mReceiver, filter);

        mServer = Settings.Secure.getString(mContext.getContentResolver(),
@@ -101,100 +95,180 @@ public class CaptivePortalTracker {

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

        addState(mDefaultState);
            addState(mNoActiveNetworkState, mDefaultState);
            addState(mActiveNetworkState, mDefaultState);
                addState(mDelayedCaptiveCheckState, mActiveNetworkState);
        setInitialState(mNoActiveNetworkState);
    }

    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (action.equals(ACTION_SWITCH_TO_CAPTIVE_PORTAL)) {
                notifyPortalCheckComplete();
            } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
            if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
                NetworkInfo info = intent.getParcelableExtra(
                        ConnectivityManager.EXTRA_NETWORK_INFO);
                mHandler.obtainMessage(HANDLE_CONNECT, info).sendToTarget();
                sendMessage(obtainMessage(CMD_CONNECTIVITY_CHANGE, info));
            }
        }
    };

    public static CaptivePortalTracker detect(Context context, NetworkInfo info,
    public static CaptivePortalTracker makeCaptivePortalTracker(Context context,
            IConnectivityManager cs) {
        CaptivePortalTracker captivePortal = new CaptivePortalTracker(context, info, cs);
        CaptivePortalTracker captivePortal = new CaptivePortalTracker(context, cs);
        captivePortal.start();
        return captivePortal;
    }

    private class InternalHandler extends Handler {
        public InternalHandler(Looper looper) {
            super(looper);
    public void detectCaptivePortal(NetworkInfo info) {
        sendMessage(obtainMessage(CMD_DETECT_PORTAL, info));
    }

    private class DefaultState extends State {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case DETECT_PORTAL:
                    InetAddress server = lookupHost(mServer);
                    if (server != null) {
                        requestRouteToHost(server);
                        if (isCaptivePortal(server)) {
                            if (DBG) log("Captive portal " + mNetworkInfo);
                            setNotificationVisible(true);
                            mIsCaptivePortal = true;
        public void enter() {
            if (DBG) log(getName() + "\n");
        }

        @Override
        public boolean processMessage(Message message) {
            if (DBG) log(getName() + message.toString() + "\n");
            switch (message.what) {
                case CMD_DETECT_PORTAL:
                    NetworkInfo info = (NetworkInfo) message.obj;
                    // Checking on a secondary connection is not supported
                    // yet
                    notifyPortalCheckComplete(info);
                    break;
                case CMD_CONNECTIVITY_CHANGE:
                case CMD_DELAYED_CAPTIVE_CHECK:
                    break;
                default:
                    loge("Ignoring " + message);
                    break;
            }
            return HANDLED;
        }
    }
                    notifyPortalCheckComplete();
                    quit();
                    break;
                case HANDLE_CONNECT:
                    NetworkInfo info = (NetworkInfo) msg.obj;
                    if (info.getType() != mNetworkInfo.getType()) break;

                    if (info.getState() == NetworkInfo.State.CONNECTED ||
                            info.getState() == NetworkInfo.State.DISCONNECTED) {
    private class NoActiveNetworkState extends State {
        @Override
        public void enter() {
            if (DBG) log(getName() + "\n");
            mNetworkInfo = null;
            /* Clear any previous notification */
            setNotificationVisible(false);
        }

                    /* Connected to a captive portal */
                    if (info.getState() == NetworkInfo.State.CONNECTED &&
                            mIsCaptivePortal) {
                        launchBrowser();
                        quit();
        @Override
        public boolean processMessage(Message message) {
            if (DBG) log(getName() + message.toString() + "\n");
            InetAddress server;
            NetworkInfo info;
            switch (message.what) {
                case CMD_CONNECTIVITY_CHANGE:
                    info = (NetworkInfo) message.obj;
                    if (info.isConnected() && isActiveNetwork(info)) {
                        mNetworkInfo = info;
                        transitionTo(mDelayedCaptiveCheckState);
                    }
                    break;
                default:
                    loge("Unhandled message " + msg);
                    break;
                    return NOT_HANDLED;
            }
            return HANDLED;
        }
    }

        private void quit() {
            mIsCaptivePortal = false;
            getLooper().quit();
            mContext.unregisterReceiver(mReceiver);
    private class ActiveNetworkState extends State {
        @Override
        public void enter() {
            if (DBG) log(getName() + "\n");
        }

        @Override
        public boolean processMessage(Message message) {
            NetworkInfo info;
            switch (message.what) {
               case CMD_CONNECTIVITY_CHANGE:
                    info = (NetworkInfo) message.obj;
                    if (!info.isConnected()
                            && info.getType() == mNetworkInfo.getType()) {
                        if (DBG) log("Disconnected from active network " + info);
                        transitionTo(mNoActiveNetworkState);
                    } else if (info.getType() != mNetworkInfo.getType() &&
                            info.isConnected() &&
                            isActiveNetwork(info)) {
                        if (DBG) log("Active network switched " + info);
                        deferMessage(message);
                        transitionTo(mNoActiveNetworkState);
                    }
                    break;
                default:
                    return NOT_HANDLED;
            }
            return HANDLED;
        }
    }

    private void launchBrowser() {
        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(mUrl));
        intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
        mContext.startActivity(intent);


    private class DelayedCaptiveCheckState extends State {
        @Override
        public void enter() {
            if (DBG) log(getName() + "\n");
            sendMessageDelayed(obtainMessage(CMD_DELAYED_CAPTIVE_CHECK,
                        ++mDelayedCheckToken, 0), DELAYED_CHECK_INTERVAL_MS);
        }

        @Override
        public boolean processMessage(Message message) {
            if (DBG) log(getName() + message.toString() + "\n");
            switch (message.what) {
                case CMD_DELAYED_CAPTIVE_CHECK:
                    if (message.arg1 == mDelayedCheckToken) {
                        InetAddress server = lookupHost(mServer);
                        if (server != null) {
                            if (isCaptivePortal(server)) {
                                if (DBG) log("Captive network " + mNetworkInfo);
                                setNotificationVisible(true);
                            }
                        }
                        if (DBG) log("Not captive network " + mNetworkInfo);
                        transitionTo(mActiveNetworkState);
                    }
                    break;
                default:
                    return NOT_HANDLED;
            }
            return HANDLED;
        }
    }

    private void notifyPortalCheckComplete() {
    private void notifyPortalCheckComplete(NetworkInfo info) {
        if (info == null) {
            loge("notifyPortalCheckComplete on null");
            return;
        }
        try {
            mConnService.captivePortalCheckComplete(mNetworkInfo);
            mConnService.captivePortalCheckComplete(info);
        } catch(RemoteException e) {
            e.printStackTrace();
        }
    }

    private void requestRouteToHost(InetAddress server) {
    private boolean isActiveNetwork(NetworkInfo info) {
        try {
            mConnService.requestRouteToHostAddress(mNetworkInfo.getType(),
                    server.getAddress());
            NetworkInfo active = mConnService.getActiveNetworkInfo();
            if (active != null && active.getType() == info.getType()) {
                return true;
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
@@ -205,6 +279,7 @@ public class CaptivePortalTracker {
        if (!mIsCaptivePortalCheckEnabled) return false;

        mUrl = "http://" + server.getHostAddress() + "/generate_204";
        if (DBG) log("Checking " + mUrl);
        try {
            URL url = new URL(mUrl);
            urlConnection = (HttpURLConnection) url.openConnection();
@@ -250,7 +325,12 @@ public class CaptivePortalTracker {
            .getSystemService(Context.NOTIFICATION_SERVICE);

        if (visible) {
            CharSequence title = r.getString(R.string.wifi_available_sign_in, 0);
            CharSequence title;
            if (mNetworkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
                title = r.getString(R.string.wifi_available_sign_in, 0);
            } else {
                title = r.getString(R.string.network_available_sign_in, 0);
            }
            CharSequence details = r.getString(R.string.network_available_sign_in_detailed,
                    mNetworkInfo.getExtraInfo());

@@ -258,9 +338,10 @@ public class CaptivePortalTracker {
            notification.when = 0;
            notification.icon = com.android.internal.R.drawable.stat_notify_wifi_in_range;
            notification.flags = Notification.FLAG_AUTO_CANCEL;
            notification.contentIntent = PendingIntent.getBroadcast(mContext, 0,
                    new Intent(CaptivePortalTracker.ACTION_SWITCH_TO_CAPTIVE_PORTAL), 0);

            Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(mUrl));
            intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
                    Intent.FLAG_ACTIVITY_NEW_TASK);
            notification.contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
            notification.tickerText = title;
            notification.setLatestEventInfo(mContext, title, details, notification.contentIntent);

+6 −6
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import static android.net.ConnectivityManager.isNetworkTypeValid;
import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;

import android.app.Activity;
import android.bluetooth.BluetoothTetheringDataTracker;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
@@ -548,6 +549,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
        mSettingsObserver = new SettingsObserver(mHandler, EVENT_APPLY_GLOBAL_HTTP_PROXY);
        mSettingsObserver.observe(mContext);

        mCaptivePortalTracker = CaptivePortalTracker.makeCaptivePortalTracker(mContext, this);
        loadGlobalProxy();
    }

@@ -1694,7 +1696,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
        }

        Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION);
        intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info);
        intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, new NetworkInfo(info));
        intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType());
        if (info.isFailover()) {
            intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true);
@@ -1825,7 +1827,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
        }

        Intent intent = new Intent(bcastType);
        intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info);
        intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, new NetworkInfo(info));
        intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType());
        if (info.isFailover()) {
            intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true);
@@ -1882,7 +1884,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
        loge("Attempt to connect to " + info.getTypeName() + " failed" + reasonText);

        Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION);
        intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info);
        intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, new NetworkInfo(info));
        intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType());
        if (getActiveNetworkInfo() == null) {
            intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true);
@@ -2075,8 +2077,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
            if (mActiveDefaultNetwork != -1 && mActiveDefaultNetwork != type) {
                if (isNewNetTypePreferredOverCurrentNetType(type)) {
                    if (DBG) log("Captive check on " + info.getTypeName());
                    mCaptivePortalTracker = CaptivePortalTracker.detect(mContext, info,
                            ConnectivityService.this);
                    mCaptivePortalTracker.detectCaptivePortal(new NetworkInfo(info));
                    return;
                } else {
                    if (DBG) log("Tear down low priority net " + info.getTypeName());
@@ -2092,7 +2093,6 @@ public class ConnectivityService extends IConnectivityManager.Stub {
    /** @hide */
    public void captivePortalCheckComplete(NetworkInfo info) {
        mNetTrackers[info.getType()].captivePortalCheckComplete();
        mCaptivePortalTracker = null;
    }

    /**