CaptivePortalTracker.java revision b8aad91f059527e04abaf8a83ed1ce6b5f09c55d
1da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff/*
2da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff * Copyright (C) 2012 The Android Open Source Project
3da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff *
4da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff * Licensed under the Apache License, Version 2.0 (the "License");
5da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff * you may not use this file except in compliance with the License.
6da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff * You may obtain a copy of the License at
7da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff *
8da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff *      http://www.apache.org/licenses/LICENSE-2.0
9da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff *
10da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff * Unless required by applicable law or agreed to in writing, software
11da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff * distributed under the License is distributed on an "AS IS" BASIS,
12da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff * See the License for the specific language governing permissions and
14da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff * limitations under the License.
15da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff */
16da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff
17da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriffpackage android.net;
18da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff
199538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriffimport android.app.Activity;
20da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriffimport android.app.Notification;
21da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriffimport android.app.NotificationManager;
22da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriffimport android.app.PendingIntent;
23da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriffimport android.content.BroadcastReceiver;
24da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriffimport android.content.Context;
25da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriffimport android.content.Intent;
26da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriffimport android.content.IntentFilter;
27da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriffimport android.content.res.Resources;
28da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriffimport android.net.ConnectivityManager;
29da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriffimport android.net.IConnectivityManager;
309538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriffimport android.os.UserHandle;
31da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriffimport android.os.Message;
32da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriffimport android.os.RemoteException;
33da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriffimport android.provider.Settings;
34b8aad91f059527e04abaf8a83ed1ce6b5f09c55dIrfan Sheriffimport android.telephony.TelephonyManager;
35da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriffimport android.util.Log;
36da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff
379538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriffimport com.android.internal.util.State;
389538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriffimport com.android.internal.util.StateMachine;
399538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff
40da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriffimport java.io.IOException;
41da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriffimport java.net.HttpURLConnection;
42da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriffimport java.net.InetAddress;
43da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriffimport java.net.Inet4Address;
44da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriffimport java.net.URL;
45da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriffimport java.net.UnknownHostException;
46da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff
47da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriffimport com.android.internal.R;
48da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff
49da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff/**
509538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff * This class allows captive portal detection on a network.
51da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff * @hide
52da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff */
539538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriffpublic class CaptivePortalTracker extends StateMachine {
549538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff    private static final boolean DBG = false;
55da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff    private static final String TAG = "CaptivePortalTracker";
56da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff
57da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff    private static final String DEFAULT_SERVER = "clients3.google.com";
58da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff    private static final String NOTIFICATION_ID = "CaptivePortal.Notification";
59da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff
60da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff    private static final int SOCKET_TIMEOUT_MS = 10000;
61da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff
62da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff    private String mServer;
63da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff    private String mUrl;
64da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff    private boolean mNotificationShown = false;
65da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff    private boolean mIsCaptivePortalCheckEnabled = false;
66da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff    private IConnectivityManager mConnService;
67b8aad91f059527e04abaf8a83ed1ce6b5f09c55dIrfan Sheriff    private TelephonyManager mTelephonyManager;
68da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff    private Context mContext;
69da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff    private NetworkInfo mNetworkInfo;
70da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff
719538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff    private static final int CMD_DETECT_PORTAL          = 0;
729538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff    private static final int CMD_CONNECTIVITY_CHANGE    = 1;
739538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff    private static final int CMD_DELAYED_CAPTIVE_CHECK  = 2;
74da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff
759538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff    /* This delay happens every time before we do a captive check on a network */
769538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff    private static final int DELAYED_CHECK_INTERVAL_MS = 10000;
779538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff    private int mDelayedCheckToken = 0;
789538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff
799538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff    private State mDefaultState = new DefaultState();
809538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff    private State mNoActiveNetworkState = new NoActiveNetworkState();
819538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff    private State mActiveNetworkState = new ActiveNetworkState();
829538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff    private State mDelayedCaptiveCheckState = new DelayedCaptiveCheckState();
839538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff
849538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff    private CaptivePortalTracker(Context context, IConnectivityManager cs) {
859538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff        super(TAG);
86da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff
87da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        mContext = context;
88da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        mConnService = cs;
89b8aad91f059527e04abaf8a83ed1ce6b5f09c55dIrfan Sheriff        mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
90da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff
91da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        IntentFilter filter = new IntentFilter();
92da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
93da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        mContext.registerReceiver(mReceiver, filter);
94da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff
95625239a05401bbf18b04d9874cea3f82da7c29a1Jeff Sharkey        mServer = Settings.Global.getString(mContext.getContentResolver(),
96625239a05401bbf18b04d9874cea3f82da7c29a1Jeff Sharkey                Settings.Global.CAPTIVE_PORTAL_SERVER);
97da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        if (mServer == null) mServer = DEFAULT_SERVER;
98da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff
99625239a05401bbf18b04d9874cea3f82da7c29a1Jeff Sharkey        mIsCaptivePortalCheckEnabled = Settings.Global.getInt(mContext.getContentResolver(),
100625239a05401bbf18b04d9874cea3f82da7c29a1Jeff Sharkey                Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED, 1) == 1;
1019538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff
1029538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff        addState(mDefaultState);
1039538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            addState(mNoActiveNetworkState, mDefaultState);
1049538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            addState(mActiveNetworkState, mDefaultState);
1059538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                addState(mDelayedCaptiveCheckState, mActiveNetworkState);
1069538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff        setInitialState(mNoActiveNetworkState);
107da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff    }
108da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff
109da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
110da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        @Override
111da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        public void onReceive(Context context, Intent intent) {
112da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff            String action = intent.getAction();
1139538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
114da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff                NetworkInfo info = intent.getParcelableExtra(
115da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff                        ConnectivityManager.EXTRA_NETWORK_INFO);
1169538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                sendMessage(obtainMessage(CMD_CONNECTIVITY_CHANGE, info));
117da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff            }
118da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        }
119da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff    };
120da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff
1219538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff    public static CaptivePortalTracker makeCaptivePortalTracker(Context context,
122da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff            IConnectivityManager cs) {
1239538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff        CaptivePortalTracker captivePortal = new CaptivePortalTracker(context, cs);
1249538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff        captivePortal.start();
125da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        return captivePortal;
126da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff    }
127da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff
1289538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff    public void detectCaptivePortal(NetworkInfo info) {
1299538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff        sendMessage(obtainMessage(CMD_DETECT_PORTAL, info));
1309538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff    }
1319538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff
1329538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff    private class DefaultState extends State {
1339538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff        @Override
1349538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff        public void enter() {
1359538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            if (DBG) log(getName() + "\n");
136da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        }
137da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff
138da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        @Override
1399538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff        public boolean processMessage(Message message) {
1409538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            if (DBG) log(getName() + message.toString() + "\n");
1419538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            switch (message.what) {
1429538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                case CMD_DETECT_PORTAL:
1439538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                    NetworkInfo info = (NetworkInfo) message.obj;
1449538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                    // Checking on a secondary connection is not supported
1459538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                    // yet
1469538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                    notifyPortalCheckComplete(info);
147da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff                    break;
1489538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                case CMD_CONNECTIVITY_CHANGE:
1499538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                case CMD_DELAYED_CAPTIVE_CHECK:
1509538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                    break;
1519538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                default:
1529538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                    loge("Ignoring " + message);
1539538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                    break;
1549538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            }
1559538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            return HANDLED;
1569538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff        }
1579538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff    }
158da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff
1599538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff    private class NoActiveNetworkState extends State {
1609538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff        @Override
1619538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff        public void enter() {
1629538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            if (DBG) log(getName() + "\n");
1639538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            mNetworkInfo = null;
1649538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            /* Clear any previous notification */
1659538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            setNotificationVisible(false);
1669538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff        }
167da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff
1689538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff        @Override
1699538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff        public boolean processMessage(Message message) {
1709538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            if (DBG) log(getName() + message.toString() + "\n");
1719538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            InetAddress server;
1729538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            NetworkInfo info;
1739538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            switch (message.what) {
1749538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                case CMD_CONNECTIVITY_CHANGE:
1759538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                    info = (NetworkInfo) message.obj;
1769538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                    if (info.isConnected() && isActiveNetwork(info)) {
1779538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                        mNetworkInfo = info;
1789538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                        transitionTo(mDelayedCaptiveCheckState);
179da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff                    }
180da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff                    break;
181da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff                default:
1829538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                    return NOT_HANDLED;
183da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff            }
1849538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            return HANDLED;
1859538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff        }
1869538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff    }
1879538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff
1889538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff    private class ActiveNetworkState extends State {
1899538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff        @Override
1909538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff        public void enter() {
1919538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            if (DBG) log(getName() + "\n");
192da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        }
193da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff
1949538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff        @Override
1959538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff        public boolean processMessage(Message message) {
1969538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            NetworkInfo info;
1979538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            switch (message.what) {
1989538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff               case CMD_CONNECTIVITY_CHANGE:
1999538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                    info = (NetworkInfo) message.obj;
2009538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                    if (!info.isConnected()
2019538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                            && info.getType() == mNetworkInfo.getType()) {
2029538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                        if (DBG) log("Disconnected from active network " + info);
2039538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                        transitionTo(mNoActiveNetworkState);
2049538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                    } else if (info.getType() != mNetworkInfo.getType() &&
2059538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                            info.isConnected() &&
2069538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                            isActiveNetwork(info)) {
2079538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                        if (DBG) log("Active network switched " + info);
2089538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                        deferMessage(message);
2099538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                        transitionTo(mNoActiveNetworkState);
2109538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                    }
2119538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                    break;
2129538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                default:
2139538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                    return NOT_HANDLED;
2149538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            }
2159538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            return HANDLED;
216da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        }
217da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff    }
218da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff
2199538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff
2209538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff
2219538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff    private class DelayedCaptiveCheckState extends State {
2229538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff        @Override
2239538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff        public void enter() {
2249538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            if (DBG) log(getName() + "\n");
2259538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            sendMessageDelayed(obtainMessage(CMD_DELAYED_CAPTIVE_CHECK,
2269538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                        ++mDelayedCheckToken, 0), DELAYED_CHECK_INTERVAL_MS);
2279538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff        }
2289538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff
2299538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff        @Override
2309538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff        public boolean processMessage(Message message) {
2319538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            if (DBG) log(getName() + message.toString() + "\n");
2329538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            switch (message.what) {
2339538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                case CMD_DELAYED_CAPTIVE_CHECK:
2349538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                    if (message.arg1 == mDelayedCheckToken) {
2359538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                        InetAddress server = lookupHost(mServer);
2369538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                        if (server != null) {
2379538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                            if (isCaptivePortal(server)) {
2389538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                                if (DBG) log("Captive network " + mNetworkInfo);
2399538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                                setNotificationVisible(true);
2409538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                            }
2419538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                        }
2429538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                        if (DBG) log("Not captive network " + mNetworkInfo);
2439538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                        transitionTo(mActiveNetworkState);
2449538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                    }
2459538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                    break;
2469538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                default:
2479538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                    return NOT_HANDLED;
2489538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            }
2499538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            return HANDLED;
2509538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff        }
251da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff    }
252da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff
2539538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff    private void notifyPortalCheckComplete(NetworkInfo info) {
2549538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff        if (info == null) {
2559538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            loge("notifyPortalCheckComplete on null");
2569538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            return;
2579538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff        }
258da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        try {
2599538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            mConnService.captivePortalCheckComplete(info);
260da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        } catch(RemoteException e) {
261da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff            e.printStackTrace();
262da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        }
263da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff    }
264da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff
2659538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff    private boolean isActiveNetwork(NetworkInfo info) {
266da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        try {
2679538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            NetworkInfo active = mConnService.getActiveNetworkInfo();
2689538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            if (active != null && active.getType() == info.getType()) {
2699538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                return true;
2709538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            }
271da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        } catch (RemoteException e) {
272da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff            e.printStackTrace();
273da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        }
2749538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff        return false;
275da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff    }
276da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff
277da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff    /**
278da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff     * Do a URL fetch on a known server to see if we get the data we expect
279da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff     */
280da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff    private boolean isCaptivePortal(InetAddress server) {
281da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        HttpURLConnection urlConnection = null;
282da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        if (!mIsCaptivePortalCheckEnabled) return false;
283da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff
284da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        mUrl = "http://" + server.getHostAddress() + "/generate_204";
2859538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff        if (DBG) log("Checking " + mUrl);
286da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        try {
287da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff            URL url = new URL(mUrl);
288da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff            urlConnection = (HttpURLConnection) url.openConnection();
289da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff            urlConnection.setInstanceFollowRedirects(false);
290da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff            urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
291da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff            urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
292da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff            urlConnection.setUseCaches(false);
293da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff            urlConnection.getInputStream();
294da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff            // we got a valid response, but not from the real google
295da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff            return urlConnection.getResponseCode() != 204;
296da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        } catch (IOException e) {
297da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff            if (DBG) log("Probably not a portal: exception " + e);
298da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff            return false;
299da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        } finally {
300da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff            if (urlConnection != null) {
301da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff                urlConnection.disconnect();
302da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff            }
303da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        }
304da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff    }
305da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff
306da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff    private InetAddress lookupHost(String hostname) {
307da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        InetAddress inetAddress[];
308da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        try {
309da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff            inetAddress = InetAddress.getAllByName(hostname);
310da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        } catch (UnknownHostException e) {
311da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff            return null;
312da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        }
313da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff
314da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        for (InetAddress a : inetAddress) {
315da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff            if (a instanceof Inet4Address) return a;
316da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        }
317da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        return null;
318da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff    }
319da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff
320da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff    private void setNotificationVisible(boolean visible) {
321da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        // if it should be hidden and it is already hidden, then noop
322da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        if (!visible && !mNotificationShown) {
323da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff            return;
324da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        }
325da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff
326da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        Resources r = Resources.getSystem();
327da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        NotificationManager notificationManager = (NotificationManager) mContext
328da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff            .getSystemService(Context.NOTIFICATION_SERVICE);
329da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff
330da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        if (visible) {
3319538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            CharSequence title;
332b8aad91f059527e04abaf8a83ed1ce6b5f09c55dIrfan Sheriff            CharSequence details;
333b8aad91f059527e04abaf8a83ed1ce6b5f09c55dIrfan Sheriff            switch (mNetworkInfo.getType()) {
334b8aad91f059527e04abaf8a83ed1ce6b5f09c55dIrfan Sheriff                case ConnectivityManager.TYPE_WIFI:
335b8aad91f059527e04abaf8a83ed1ce6b5f09c55dIrfan Sheriff                    title = r.getString(R.string.wifi_available_sign_in, 0);
336b8aad91f059527e04abaf8a83ed1ce6b5f09c55dIrfan Sheriff                    details = r.getString(R.string.network_available_sign_in_detailed,
337b8aad91f059527e04abaf8a83ed1ce6b5f09c55dIrfan Sheriff                            mNetworkInfo.getExtraInfo());
338b8aad91f059527e04abaf8a83ed1ce6b5f09c55dIrfan Sheriff                    break;
339b8aad91f059527e04abaf8a83ed1ce6b5f09c55dIrfan Sheriff                case ConnectivityManager.TYPE_MOBILE:
340b8aad91f059527e04abaf8a83ed1ce6b5f09c55dIrfan Sheriff                    title = r.getString(R.string.network_available_sign_in, 0);
341b8aad91f059527e04abaf8a83ed1ce6b5f09c55dIrfan Sheriff                    // TODO: Change this to pull from NetworkInfo once a printable
342b8aad91f059527e04abaf8a83ed1ce6b5f09c55dIrfan Sheriff                    // name has been added to it
343b8aad91f059527e04abaf8a83ed1ce6b5f09c55dIrfan Sheriff                    details = mTelephonyManager.getNetworkOperatorName();
344b8aad91f059527e04abaf8a83ed1ce6b5f09c55dIrfan Sheriff                    break;
345b8aad91f059527e04abaf8a83ed1ce6b5f09c55dIrfan Sheriff                default:
346b8aad91f059527e04abaf8a83ed1ce6b5f09c55dIrfan Sheriff                    title = r.getString(R.string.network_available_sign_in, 0);
347b8aad91f059527e04abaf8a83ed1ce6b5f09c55dIrfan Sheriff                    details = r.getString(R.string.network_available_sign_in_detailed,
348b8aad91f059527e04abaf8a83ed1ce6b5f09c55dIrfan Sheriff                            mNetworkInfo.getExtraInfo());
349b8aad91f059527e04abaf8a83ed1ce6b5f09c55dIrfan Sheriff                    break;
3509538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            }
351da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff
352da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff            Notification notification = new Notification();
353da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff            notification.when = 0;
354da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff            notification.icon = com.android.internal.R.drawable.stat_notify_wifi_in_range;
355da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff            notification.flags = Notification.FLAG_AUTO_CANCEL;
3569538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(mUrl));
3579538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
3589538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff                    Intent.FLAG_ACTIVITY_NEW_TASK);
3599538bdd3c77968c7673719c580ae653ede4654d6Irfan Sheriff            notification.contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
360da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff            notification.tickerText = title;
361da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff            notification.setLatestEventInfo(mContext, title, details, notification.contentIntent);
362da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff
363da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff            notificationManager.notify(NOTIFICATION_ID, 1, notification);
364da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        } else {
365da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff            notificationManager.cancel(NOTIFICATION_ID, 1);
366da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        }
367da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        mNotificationShown = visible;
368da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff    }
369da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff
370da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff    private static void log(String s) {
371da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        Log.d(TAG, s);
372da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff    }
373da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff
374da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff    private static void loge(String s) {
375da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff        Log.e(TAG, s);
376da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff    }
377da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff
378da6da0907b28d4704aabbdb1bbeb4300954670d1Irfan Sheriff}
379