summaryrefslogtreecommitdiffstats
path: root/core/java/android
diff options
context:
space:
mode:
authorIrfan Sheriff <isheriff@google.com>2012-08-16 12:49:23 -0700
committerIrfan Sheriff <isheriff@google.com>2012-08-27 22:27:06 -0700
commitda6da0907b28d4704aabbdb1bbeb4300954670d1 (patch)
tree58a2b58cf777d02d0d89cc8f54b0ce5d29a9c5b2 /core/java/android
parent10a0df8459d22b29fb9163071e8cbc2bb7194393 (diff)
downloadframeworks_base-da6da0907b28d4704aabbdb1bbeb4300954670d1.zip
frameworks_base-da6da0907b28d4704aabbdb1bbeb4300954670d1.tar.gz
frameworks_base-da6da0907b28d4704aabbdb1bbeb4300954670d1.tar.bz2
Captive portal handling
We now notify the user of a captive portal before switching to the network as default. This allows background applications to continue to work until the user confirms he wants to sign in to the captive portal. Also, moved out captive portal handling out of wifi as a seperate component. Change-Id: I7c7507481967e33a1afad0b4961688bd192f0d31
Diffstat (limited to 'core/java/android')
-rw-r--r--core/java/android/bluetooth/BluetoothTetheringDataTracker.java5
-rw-r--r--core/java/android/net/CaptivePortalTracker.java282
-rw-r--r--core/java/android/net/ConnectivityManager.java11
-rw-r--r--core/java/android/net/DummyDataStateTracker.java4
-rw-r--r--core/java/android/net/EthernetDataTracker.java5
-rw-r--r--core/java/android/net/IConnectivityManager.aidl2
-rw-r--r--core/java/android/net/MobileDataStateTracker.java5
-rw-r--r--core/java/android/net/NetworkInfo.java5
-rw-r--r--core/java/android/net/NetworkStateTracker.java5
-rw-r--r--core/java/android/provider/Settings.java39
10 files changed, 338 insertions, 25 deletions
diff --git a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
index b2b5d81..30406e9 100644
--- a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
+++ b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
@@ -133,6 +133,11 @@ public class BluetoothTetheringDataTracker implements NetworkStateTracker {
return true;
}
+ @Override
+ public void captivePortalCheckComplete() {
+ // not implemented
+ }
+
/**
* Re-enable connectivity to a network after a {@link #teardown()}.
*/
diff --git a/core/java/android/net/CaptivePortalTracker.java b/core/java/android/net/CaptivePortalTracker.java
new file mode 100644
index 0000000..aa392d0
--- /dev/null
+++ b/core/java/android/net/CaptivePortalTracker.java
@@ -0,0 +1,282 @@
+/*
+ * 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.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.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.Message;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.util.Log;
+
+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
+ * @hide
+ */
+public class CaptivePortalTracker {
+ private static final boolean DBG = true;
+ 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 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;
+
+ /**
+ * 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";
+
+ 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(),
+ Settings.Secure.CAPTIVE_PORTAL_SERVER);
+ if (mServer == null) mServer = DEFAULT_SERVER;
+
+ mIsCaptivePortalCheckEnabled = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.CAPTIVE_PORTAL_DETECTION_ENABLED, 1) == 1;
+ }
+
+ 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)) {
+ NetworkInfo info = intent.getParcelableExtra(
+ ConnectivityManager.EXTRA_NETWORK_INFO);
+ mHandler.obtainMessage(HANDLE_CONNECT, info).sendToTarget();
+ }
+ }
+ };
+
+ public static CaptivePortalTracker detect(Context context, NetworkInfo info,
+ IConnectivityManager cs) {
+ CaptivePortalTracker captivePortal = new CaptivePortalTracker(context, info, cs);
+ return captivePortal;
+ }
+
+ private class InternalHandler extends Handler {
+ public InternalHandler(Looper looper) {
+ super(looper);
+ }
+
+ @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;
+ break;
+ }
+ }
+ 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) {
+ setNotificationVisible(false);
+ }
+
+ /* Connected to a captive portal */
+ if (info.getState() == NetworkInfo.State.CONNECTED &&
+ mIsCaptivePortal) {
+ launchBrowser();
+ quit();
+ }
+ break;
+ default:
+ loge("Unhandled message " + msg);
+ break;
+ }
+ }
+
+ private void quit() {
+ mIsCaptivePortal = false;
+ getLooper().quit();
+ mContext.unregisterReceiver(mReceiver);
+ }
+ }
+
+ 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 void notifyPortalCheckComplete() {
+ try {
+ mConnService.captivePortalCheckComplete(mNetworkInfo);
+ } catch(RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void requestRouteToHost(InetAddress server) {
+ try {
+ mConnService.requestRouteToHostAddress(mNetworkInfo.getType(),
+ server.getAddress());
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 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";
+ 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 = r.getString(R.string.wifi_available_sign_in, 0);
+ CharSequence details = r.getString(R.string.wifi_available_sign_in_detailed,
+ mNetworkInfo.getExtraInfo());
+
+ Notification notification = new Notification();
+ 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);
+
+ 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);
+ }
+
+}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 60bf4d6..a570473 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -921,4 +921,15 @@ public class ConnectivityManager {
return false;
}
}
+
+ /**
+ * {@hide}
+ */
+ public void captivePortalCheckComplete(NetworkInfo info) {
+ try {
+ mService.captivePortalCheckComplete(info);
+ } catch (RemoteException e) {
+ }
+ }
+
}
diff --git a/core/java/android/net/DummyDataStateTracker.java b/core/java/android/net/DummyDataStateTracker.java
index ccd96ff..39440c2 100644
--- a/core/java/android/net/DummyDataStateTracker.java
+++ b/core/java/android/net/DummyDataStateTracker.java
@@ -119,6 +119,10 @@ public class DummyDataStateTracker implements NetworkStateTracker {
return true;
}
+ public void captivePortalCheckComplete() {
+ // not implemented
+ }
+
/**
* Record the detailed state of a network, and if it is a
* change from the previous state, send a notification to
diff --git a/core/java/android/net/EthernetDataTracker.java b/core/java/android/net/EthernetDataTracker.java
index c690430..c52aa9e 100644
--- a/core/java/android/net/EthernetDataTracker.java
+++ b/core/java/android/net/EthernetDataTracker.java
@@ -274,6 +274,11 @@ public class EthernetDataTracker implements NetworkStateTracker {
return mLinkUp;
}
+ @Override
+ public void captivePortalCheckComplete() {
+ // not implemented
+ }
+
/**
* Turn the wireless radio off for a network.
* @param turnOn {@code true} to turn the radio on, {@code false}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 3614045..056fa03 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -124,4 +124,6 @@ interface IConnectivityManager
LegacyVpnInfo getLegacyVpnInfo();
boolean updateLockdownVpn();
+
+ void captivePortalCheckComplete(in NetworkInfo info);
}
diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java
index d59fa6a..b35d61c 100644
--- a/core/java/android/net/MobileDataStateTracker.java
+++ b/core/java/android/net/MobileDataStateTracker.java
@@ -381,6 +381,11 @@ public class MobileDataStateTracker implements NetworkStateTracker {
return (setEnableApn(mApnType, false) != PhoneConstants.APN_REQUEST_FAILED);
}
+ @Override
+ public void captivePortalCheckComplete() {
+ // not implemented
+ }
+
/**
* Record the detailed state of a network, and if it is a
* change from the previous state, send a notification to
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
index 0bc6b58..0b23cb7 100644
--- a/core/java/android/net/NetworkInfo.java
+++ b/core/java/android/net/NetworkInfo.java
@@ -79,7 +79,9 @@ public class NetworkInfo implements Parcelable {
/** Access to this network is blocked. */
BLOCKED,
/** Link has poor connectivity. */
- VERIFYING_POOR_LINK
+ VERIFYING_POOR_LINK,
+ /** Checking if network is a captive portal */
+ CAPTIVE_PORTAL_CHECK,
}
/**
@@ -97,6 +99,7 @@ public class NetworkInfo implements Parcelable {
stateMap.put(DetailedState.AUTHENTICATING, State.CONNECTING);
stateMap.put(DetailedState.OBTAINING_IPADDR, State.CONNECTING);
stateMap.put(DetailedState.VERIFYING_POOR_LINK, State.CONNECTING);
+ stateMap.put(DetailedState.CAPTIVE_PORTAL_CHECK, State.CONNECTING);
stateMap.put(DetailedState.CONNECTED, State.CONNECTED);
stateMap.put(DetailedState.SUSPENDED, State.SUSPENDED);
stateMap.put(DetailedState.DISCONNECTING, State.DISCONNECTING);
diff --git a/core/java/android/net/NetworkStateTracker.java b/core/java/android/net/NetworkStateTracker.java
index eae89f1..0a0c1e0 100644
--- a/core/java/android/net/NetworkStateTracker.java
+++ b/core/java/android/net/NetworkStateTracker.java
@@ -123,6 +123,11 @@ public interface NetworkStateTracker {
public boolean reconnect();
/**
+ * Ready to switch on to the network after captive portal check
+ */
+ public void captivePortalCheckComplete();
+
+ /**
* Turn the wireless radio off for a network.
* @param turnOn {@code true} to turn the radio on, {@code false}
*/
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 1f6f0dd..6f43891 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3279,13 +3279,6 @@ public final class Settings {
/**
- * ms delay before rechecking a connect SSID for walled garden with a http download.
- * @hide
- */
- public static final String WIFI_WATCHDOG_WALLED_GARDEN_INTERVAL_MS =
- "wifi_watchdog_walled_garden_interval_ms";
-
- /**
* Number of ARP pings per check.
* @hide
*/
@@ -3322,23 +3315,6 @@ public final class Settings {
"wifi_suspend_optimizations_enabled";
/**
- * Setting to turn off walled garden test on Wi-Fi. Feature is enabled by default and
- * the setting needs to be set to 0 to disable it.
- * @hide
- */
- public static final String WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED =
- "wifi_watchdog_walled_garden_test_enabled";
-
- /**
- * The URL used for walled garden check upon a new conection. WifiWatchdogService
- * fetches the URL and checks to see if {@link #WIFI_WATCHDOG_WALLED_GARDEN_PATTERN}
- * is not part of the title string to notify the user on the presence of a walled garden.
- * @hide
- */
- public static final String WIFI_WATCHDOG_WALLED_GARDEN_URL =
- "wifi_watchdog_walled_garden_url";
-
- /**
* The maximum number of times we will retry a connection to an access
* point for which we have failed in acquiring an IP address from DHCP.
* A value of N means that we will make N+1 connection attempts in all.
@@ -3362,6 +3338,21 @@ public final class Settings {
public static final String WIFI_P2P_DEVICE_NAME = "wifi_p2p_device_name";
/**
+ * Setting to turn off captive portal detection. Feature is enabled by default and
+ * the setting needs to be set to 0 to disable it.
+ * @hide
+ */
+ public static final String CAPTIVE_PORTAL_DETECTION_ENABLED =
+ "captive_portal_detection_enabled";
+
+ /**
+ * The server used for captive portal detection upon a new conection. A 204 response
+ * code from the server is used for validation.
+ * @hide
+ */
+ public static final String CAPTIVE_PORTAL_SERVER = "captive_portal_server";
+
+ /**
* Maximum amount of time in milliseconds to hold a wakelock while waiting for mobile
* data connectivity to be established after a disconnect from Wi-Fi.
*/