/core/java/android/net/CaptivePortalTracker.java
Java | 382 lines | 307 code | 45 blank | 30 comment | 51 complexity | 88cec8559a9c9abfdd260413c27eb91d MD5 | raw file
- /*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package android.net;
- import android.app.Activity;
- import android.app.Notification;
- import android.app.NotificationManager;
- import android.app.PendingIntent;
- import android.content.BroadcastReceiver;
- import android.content.Context;
- import android.content.Intent;
- import android.content.IntentFilter;
- import android.content.res.Resources;
- import android.net.ConnectivityManager;
- import android.net.IConnectivityManager;
- import android.os.UserHandle;
- import android.os.Message;
- import android.os.RemoteException;
- import android.provider.Settings;
- import android.telephony.TelephonyManager;
- 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 com.android.internal.R;
- /**
- * This class allows captive portal detection on a network.
- * @hide
- */
- 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";
- private static final String NOTIFICATION_ID = "CaptivePortal.Notification";
- private static final int SOCKET_TIMEOUT_MS = 10000;
- private String mServer;
- private String mUrl;
- private boolean mNotificationShown = false;
- private boolean mIsCaptivePortalCheckEnabled = false;
- private IConnectivityManager mConnService;
- private TelephonyManager mTelephonyManager;
- private Context mContext;
- private NetworkInfo mNetworkInfo;
- 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;
- /* 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);
- mContext = context;
- mConnService = cs;
- mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
- IntentFilter filter = new IntentFilter();
- filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
- mContext.registerReceiver(mReceiver, filter);
- mServer = Settings.Global.getString(mContext.getContentResolver(),
- Settings.Global.CAPTIVE_PORTAL_SERVER);
- if (mServer == null) mServer = DEFAULT_SERVER;
- mIsCaptivePortalCheckEnabled = Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.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(ConnectivityManager.CONNECTIVITY_ACTION)) {
- NetworkInfo info = intent.getParcelableExtra(
- ConnectivityManager.EXTRA_NETWORK_INFO);
- sendMessage(obtainMessage(CMD_CONNECTIVITY_CHANGE, info));
- }
- }
- };
- public static CaptivePortalTracker makeCaptivePortalTracker(Context context,
- IConnectivityManager cs) {
- CaptivePortalTracker captivePortal = new CaptivePortalTracker(context, cs);
- captivePortal.start();
- return captivePortal;
- }
- public void detectCaptivePortal(NetworkInfo info) {
- sendMessage(obtainMessage(CMD_DETECT_PORTAL, info));
- }
- private class DefaultState extends State {
- @Override
- 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;
- }
- }
- private class NoActiveNetworkState extends State {
- @Override
- public void enter() {
- if (DBG) log(getName() + "\n");
- mNetworkInfo = null;
- /* Clear any previous notification */
- setNotificationVisible(false);
- }
- @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:
- return NOT_HANDLED;
- }
- return HANDLED;
- }
- }
- 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 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(NetworkInfo info) {
- if (info == null) {
- loge("notifyPortalCheckComplete on null");
- return;
- }
- try {
- mConnService.captivePortalCheckComplete(info);
- } catch(RemoteException e) {
- e.printStackTrace();
- }
- }
- private boolean isActiveNetwork(NetworkInfo info) {
- try {
- NetworkInfo active = mConnService.getActiveNetworkInfo();
- if (active != null && active.getType() == info.getType()) {
- return true;
- }
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- return false;
- }
- /**
- * Do a URL fetch on a known server to see if we get the data we expect
- */
- private boolean isCaptivePortal(InetAddress server) {
- HttpURLConnection urlConnection = null;
- 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();
- urlConnection.setInstanceFollowRedirects(false);
- urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
- urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
- urlConnection.setUseCaches(false);
- urlConnection.getInputStream();
- // we got a valid response, but not from the real google
- return urlConnection.getResponseCode() != 204;
- } catch (IOException e) {
- if (DBG) log("Probably not a portal: exception " + e);
- return false;
- } finally {
- if (urlConnection != null) {
- urlConnection.disconnect();
- }
- }
- }
- private InetAddress lookupHost(String hostname) {
- InetAddress inetAddress[];
- try {
- inetAddress = InetAddress.getAllByName(hostname);
- } catch (UnknownHostException e) {
- return null;
- }
- for (InetAddress a : inetAddress) {
- if (a instanceof Inet4Address) return a;
- }
- return null;
- }
- private void setNotificationVisible(boolean visible) {
- // if it should be hidden and it is already hidden, then noop
- if (!visible && !mNotificationShown) {
- return;
- }
- Resources r = Resources.getSystem();
- NotificationManager notificationManager = (NotificationManager) mContext
- .getSystemService(Context.NOTIFICATION_SERVICE);
- if (visible) {
- CharSequence title;
- CharSequence details;
- int icon;
- switch (mNetworkInfo.getType()) {
- case ConnectivityManager.TYPE_WIFI:
- title = r.getString(R.string.wifi_available_sign_in, 0);
- details = r.getString(R.string.network_available_sign_in_detailed,
- mNetworkInfo.getExtraInfo());
- icon = R.drawable.stat_notify_wifi_in_range;
- break;
- case ConnectivityManager.TYPE_MOBILE:
- title = r.getString(R.string.network_available_sign_in, 0);
- // TODO: Change this to pull from NetworkInfo once a printable
- // name has been added to it
- details = mTelephonyManager.getNetworkOperatorName();
- icon = R.drawable.stat_notify_rssi_in_range;
- break;
- default:
- title = r.getString(R.string.network_available_sign_in, 0);
- details = r.getString(R.string.network_available_sign_in_detailed,
- mNetworkInfo.getExtraInfo());
- icon = R.drawable.stat_notify_rssi_in_range;
- break;
- }
- Notification notification = new Notification();
- notification.when = 0;
- notification.icon = icon;
- notification.flags = Notification.FLAG_AUTO_CANCEL;
- 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);
- notificationManager.notify(NOTIFICATION_ID, 1, notification);
- } else {
- notificationManager.cancel(NOTIFICATION_ID, 1);
- }
- mNotificationShown = visible;
- }
- private static void log(String s) {
- Log.d(TAG, s);
- }
- private static void loge(String s) {
- Log.e(TAG, s);
- }
- }