summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/current.txt1
-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
-rw-r--r--services/java/com/android/server/ConnectivityService.java84
-rw-r--r--services/java/com/android/server/WifiService.java19
-rw-r--r--wifi/java/android/net/wifi/IWifiManager.aidl2
-rw-r--r--wifi/java/android/net/wifi/WifiManager.java7
-rw-r--r--wifi/java/android/net/wifi/WifiStateMachine.java32
-rw-r--r--wifi/java/android/net/wifi/WifiStateTracker.java14
-rw-r--r--wifi/java/android/net/wifi/WifiWatchdogStateMachine.java182
18 files changed, 472 insertions, 232 deletions
diff --git a/api/current.txt b/api/current.txt
index 512a311..4a5f071 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -12606,6 +12606,7 @@ package android.net {
method public static final android.net.NetworkInfo.DetailedState[] values();
enum_constant public static final android.net.NetworkInfo.DetailedState AUTHENTICATING;
enum_constant public static final android.net.NetworkInfo.DetailedState BLOCKED;
+ enum_constant public static final android.net.NetworkInfo.DetailedState CAPTIVE_PORTAL_CHECK;
enum_constant public static final android.net.NetworkInfo.DetailedState CONNECTED;
enum_constant public static final android.net.NetworkInfo.DetailedState CONNECTING;
enum_constant public static final android.net.NetworkInfo.DetailedState DISCONNECTED;
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.
*/
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 776a1a4..8a1ac10 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -42,6 +42,7 @@ import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.ContentObserver;
+import android.net.CaptivePortalTracker;
import android.net.ConnectivityManager;
import android.net.DummyDataStateTracker;
import android.net.EthernetDataTracker;
@@ -166,6 +167,9 @@ public class ConnectivityService extends IConnectivityManager.Stub {
*/
private NetworkStateTracker mNetTrackers[];
+ /* Handles captive portal check on a network */
+ private CaptivePortalTracker mCaptivePortalTracker;
+
/**
* The link properties that define the current links
*/
@@ -1363,8 +1367,10 @@ public class ConnectivityService extends IConnectivityManager.Stub {
return false;
}
NetworkStateTracker tracker = mNetTrackers[networkType];
+ DetailedState netState = tracker.getNetworkInfo().getDetailedState();
- if (tracker == null || !tracker.getNetworkInfo().isConnected() ||
+ if (tracker == null || (netState != DetailedState.CONNECTED &&
+ netState != DetailedState.CAPTIVE_PORTAL_CHECK) ||
tracker.isTeardownRequested()) {
if (VDBG) {
log("requestRouteToHostAddress on down network " +
@@ -1966,32 +1972,29 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
};
+ private boolean isNewNetTypePreferredOverCurrentNetType(int type) {
+ if ((type != mNetworkPreference &&
+ mNetConfigs[mActiveDefaultNetwork].priority >
+ mNetConfigs[type].priority) ||
+ mNetworkPreference == mActiveDefaultNetwork) return false;
+ return true;
+ }
+
private void handleConnect(NetworkInfo info) {
- final int type = info.getType();
+ final int newNetType = info.getType();
- setupDataActivityTracking(type);
+ setupDataActivityTracking(newNetType);
// snapshot isFailover, because sendConnectedBroadcast() resets it
boolean isFailover = info.isFailover();
- final NetworkStateTracker thisNet = mNetTrackers[type];
+ final NetworkStateTracker thisNet = mNetTrackers[newNetType];
final String thisIface = thisNet.getLinkProperties().getInterfaceName();
// if this is a default net and other default is running
// kill the one not preferred
- if (mNetConfigs[type].isDefault()) {
- if (mActiveDefaultNetwork != -1 && mActiveDefaultNetwork != type) {
- if ((type != mNetworkPreference &&
- mNetConfigs[mActiveDefaultNetwork].priority >
- mNetConfigs[type].priority) ||
- mNetworkPreference == mActiveDefaultNetwork) {
- // don't accept this one
- if (VDBG) {
- log("Not broadcasting CONNECT_ACTION " +
- "to torn down network " + info.getTypeName());
- }
- teardown(thisNet);
- return;
- } else {
+ if (mNetConfigs[newNetType].isDefault()) {
+ if (mActiveDefaultNetwork != -1 && mActiveDefaultNetwork != newNetType) {
+ if (isNewNetTypePreferredOverCurrentNetType(newNetType)) {
// tear down the other
NetworkStateTracker otherNet =
mNetTrackers[mActiveDefaultNetwork];
@@ -2004,6 +2007,14 @@ public class ConnectivityService extends IConnectivityManager.Stub {
teardown(thisNet);
return;
}
+ } else {
+ // don't accept this one
+ if (VDBG) {
+ log("Not broadcasting CONNECT_ACTION " +
+ "to torn down network " + info.getTypeName());
+ }
+ teardown(thisNet);
+ return;
}
}
synchronized (ConnectivityService.this) {
@@ -2017,7 +2028,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
1000);
}
}
- mActiveDefaultNetwork = type;
+ mActiveDefaultNetwork = newNetType;
// this will cause us to come up initially as unconnected and switching
// to connected after our normal pause unless somebody reports us as reall
// disconnected
@@ -2029,19 +2040,47 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
thisNet.setTeardownRequested(false);
updateNetworkSettings(thisNet);
- handleConnectivityChange(type, false);
+ handleConnectivityChange(newNetType, false);
sendConnectedBroadcastDelayed(info, getConnectivityChangeDelay());
// notify battery stats service about this network
if (thisIface != null) {
try {
- BatteryStatsService.getService().noteNetworkInterfaceType(thisIface, type);
+ BatteryStatsService.getService().noteNetworkInterfaceType(thisIface, newNetType);
} catch (RemoteException e) {
// ignored; service lives in system_server
}
}
}
+ private void handleCaptivePortalTrackerCheck(NetworkInfo info) {
+ if (DBG) log("Captive portal check " + info);
+ int type = info.getType();
+ final NetworkStateTracker thisNet = mNetTrackers[type];
+ if (mNetConfigs[type].isDefault()) {
+ if (mActiveDefaultNetwork != -1 && mActiveDefaultNetwork != type) {
+ if (isNewNetTypePreferredOverCurrentNetType(type)) {
+ if (DBG) log("Captive check on " + info.getTypeName());
+ mCaptivePortalTracker = CaptivePortalTracker.detect(mContext, info,
+ ConnectivityService.this);
+ return;
+ } else {
+ if (DBG) log("Tear down low priority net " + info.getTypeName());
+ teardown(thisNet);
+ return;
+ }
+ }
+ }
+
+ thisNet.captivePortalCheckComplete();
+ }
+
+ /** @hide */
+ public void captivePortalCheckComplete(NetworkInfo info) {
+ mNetTrackers[info.getType()].captivePortalCheckComplete();
+ mCaptivePortalTracker = null;
+ }
+
/**
* Setup data activity tracking for the given network interface.
*
@@ -2630,6 +2669,9 @@ public class ConnectivityService extends IConnectivityManager.Stub {
if (info.getDetailedState() ==
NetworkInfo.DetailedState.FAILED) {
handleConnectionFailure(info);
+ } else if (info.getDetailedState() ==
+ DetailedState.CAPTIVE_PORTAL_CHECK) {
+ handleCaptivePortalTrackerCheck(info);
} else if (state == NetworkInfo.State.DISCONNECTED) {
handleDisconnect(info);
} else if (state == NetworkInfo.State.SUSPENDED) {
diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java
index f483576..84d670e 100644
--- a/services/java/com/android/server/WifiService.java
+++ b/services/java/com/android/server/WifiService.java
@@ -412,6 +412,7 @@ public class WifiService extends IWifiManager.Stub {
switch(mNetworkInfo.getDetailedState()) {
case CONNECTED:
case DISCONNECTED:
+ case CAPTIVE_PORTAL_CHECK:
evaluateTrafficStatsPolling();
resetNotification();
break;
@@ -606,6 +607,12 @@ public class WifiService extends IWifiManager.Stub {
"WifiService");
}
+ private void enforceConnectivityInternalPermission() {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CONNECTIVITY_INTERNAL,
+ "ConnectivityService");
+ }
+
/**
* see {@link android.net.wifi.WifiManager#setWifiEnabled(boolean)}
* @param enable {@code true} to enable, {@code false} to disable.
@@ -910,7 +917,7 @@ public class WifiService extends IWifiManager.Stub {
*
*/
public void startWifi() {
- enforceChangePermission();
+ enforceConnectivityInternalPermission();
/* TODO: may be add permissions for access only to connectivity service
* TODO: if a start issued, keep wifi alive until a stop issued irrespective
* of WifiLock & device idle status unless wifi enabled status is toggled
@@ -920,20 +927,24 @@ public class WifiService extends IWifiManager.Stub {
mWifiStateMachine.reconnectCommand();
}
+ public void captivePortalCheckComplete() {
+ enforceConnectivityInternalPermission();
+ mWifiStateMachine.captivePortalCheckComplete();
+ }
+
/**
* see {@link android.net.wifi.WifiManager#stopWifi}
*
*/
public void stopWifi() {
- enforceChangePermission();
- /* TODO: may be add permissions for access only to connectivity service
+ enforceConnectivityInternalPermission();
+ /*
* TODO: if a stop is issued, wifi is brought up only by startWifi
* unless wifi enabled status is toggled
*/
mWifiStateMachine.setDriverStart(false, mEmergencyCallbackMode);
}
-
/**
* see {@link android.net.wifi.WifiManager#addToBlacklist}
*
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 6b08074..55de065 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -106,5 +106,7 @@ interface IWifiManager
Messenger getWifiStateMachineMessenger();
String getConfigFile();
+
+ void captivePortalCheckComplete();
}
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 284bee8..aa59158 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1971,4 +1971,11 @@ public class WifiManager {
return false;
}
}
+
+ /** @hide */
+ public void captivePortalCheckComplete() {
+ try {
+ mService.captivePortalCheckComplete();
+ } catch (RemoteException e) {}
+ }
}
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index a5322fa..b4456b9 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -256,6 +256,8 @@ public class WifiStateMachine extends StateMachine {
static final int CMD_DELAYED_STOP_DRIVER = BASE + 18;
/* A delayed message sent to start driver when it fail to come up */
static final int CMD_DRIVER_START_TIMED_OUT = BASE + 19;
+ /* Ready to switch to network as default */
+ static final int CMD_CAPTIVE_CHECK_COMPLETE = BASE + 20;
/* Start the soft access point */
static final int CMD_START_AP = BASE + 21;
@@ -459,6 +461,8 @@ public class WifiStateMachine extends StateMachine {
private State mObtainingIpState = new ObtainingIpState();
/* Waiting for link quality verification to be complete */
private State mVerifyingLinkState = new VerifyingLinkState();
+ /* Waiting for captive portal check to be complete */
+ private State mCaptivePortalCheckState = new CaptivePortalCheckState();
/* Connected with IP addr */
private State mConnectedState = new ConnectedState();
/* disconnect issued, waiting for network disconnect confirmation */
@@ -695,6 +699,7 @@ public class WifiStateMachine extends StateMachine {
addState(mL2ConnectedState, mConnectModeState);
addState(mObtainingIpState, mL2ConnectedState);
addState(mVerifyingLinkState, mL2ConnectedState);
+ addState(mCaptivePortalCheckState, mL2ConnectedState);
addState(mConnectedState, mL2ConnectedState);
addState(mDisconnectingState, mConnectModeState);
addState(mDisconnectedState, mConnectModeState);
@@ -865,6 +870,10 @@ public class WifiStateMachine extends StateMachine {
}
}
+ public void captivePortalCheckComplete() {
+ sendMessage(obtainMessage(CMD_CAPTIVE_CHECK_COMPLETE));
+ }
+
/**
* TODO: doc
*/
@@ -1616,7 +1625,7 @@ public class WifiStateMachine extends StateMachine {
}
if (state != mNetworkInfo.getDetailedState()) {
- mNetworkInfo.setDetailedState(state, null, null);
+ mNetworkInfo.setDetailedState(state, null, mWifiInfo.getSSID());
}
}
@@ -3253,6 +3262,26 @@ public class WifiStateMachine extends StateMachine {
//stay here
break;
case WifiWatchdogStateMachine.GOOD_LINK_DETECTED:
+ transitionTo(mCaptivePortalCheckState);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class CaptivePortalCheckState extends State {
+ @Override
+ public void enter() {
+ setNetworkDetailedState(DetailedState.CAPTIVE_PORTAL_CHECK);
+ mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.CAPTIVE_PORTAL_CHECK);
+ sendNetworkStateChangeBroadcast(mLastBssid);
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case CMD_CAPTIVE_CHECK_COMPLETE:
try {
mNwService.enableIpv6(mInterfaceName);
} catch (RemoteException re) {
@@ -3260,7 +3289,6 @@ public class WifiStateMachine extends StateMachine {
} catch (IllegalStateException e) {
loge("Failed to enable IPv6: " + e);
}
-
setNetworkDetailedState(DetailedState.CONNECTED);
mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.CONNECTED);
sendNetworkStateChangeBroadcast(mLastBssid);
diff --git a/wifi/java/android/net/wifi/WifiStateTracker.java b/wifi/java/android/net/wifi/WifiStateTracker.java
index bfb91e2..a5a2469 100644
--- a/wifi/java/android/net/wifi/WifiStateTracker.java
+++ b/wifi/java/android/net/wifi/WifiStateTracker.java
@@ -23,6 +23,7 @@ import android.content.IntentFilter;
import android.net.LinkCapabilities;
import android.net.LinkProperties;
import android.net.NetworkInfo;
+import android.net.NetworkInfo.DetailedState;
import android.net.NetworkStateTracker;
import android.net.wifi.p2p.WifiP2pManager;
import android.os.Handler;
@@ -113,6 +114,14 @@ public class WifiStateTracker implements NetworkStateTracker {
}
/**
+ * Captive check is complete, switch to network
+ */
+ @Override
+ public void captivePortalCheckComplete() {
+ mWifiManager.captivePortalCheckComplete();
+ }
+
+ /**
* Turn the wireless radio off for a network.
* @param turnOn {@code true} to turn the radio on, {@code false}
*/
@@ -235,9 +244,10 @@ public class WifiStateTracker implements NetworkStateTracker {
mLinkCapabilities = new LinkCapabilities();
}
// don't want to send redundent state messages
- // TODO can this be fixed in WifiStateMachine?
+ // but send portal check detailed state notice
NetworkInfo.State state = mNetworkInfo.getState();
- if (mLastState == state) {
+ if (mLastState == state &&
+ mNetworkInfo.getDetailedState() != DetailedState.CAPTIVE_PORTAL_CHECK) {
return;
} else {
mLastState = state;
diff --git a/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java b/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java
index 29a53b6..7fa6aac 100644
--- a/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java
@@ -16,20 +16,15 @@
package android.net.wifi;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
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.LinkProperties;
import android.net.NetworkInfo;
-import android.net.Uri;
import android.net.wifi.RssiPacketCountInfo;
import android.os.Message;
import android.os.SystemClock;
@@ -44,10 +39,7 @@ import com.android.internal.util.Protocol;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
-import java.io.IOException;
import java.io.PrintWriter;
-import java.net.HttpURLConnection;
-import java.net.URL;
import java.text.DecimalFormat;
/**
@@ -100,8 +92,7 @@ public class WifiWatchdogStateMachine extends StateMachine {
private static final int EVENT_SCREEN_OFF = BASE + 9;
/* Internal messages */
- private static final int CMD_DELAYED_WALLED_GARDEN_CHECK = BASE + 11;
- private static final int CMD_RSSI_FETCH = BASE + 12;
+ private static final int CMD_RSSI_FETCH = BASE + 11;
/* Notifications from/to WifiStateMachine */
static final int POOR_LINK_DETECTED = BASE + 21;
@@ -266,27 +257,6 @@ public class WifiWatchdogStateMachine extends StateMachine {
new MaxAvoidTime( 0 * 60000, -55 ),
};
-
- private static final String WALLED_GARDEN_NOTIFICATION_ID = "WifiWatchdog.walledgarden";
-
- private static final long DEFAULT_WALLED_GARDEN_INTERVAL_MS = 30 * 60 * 1000;
-
- /**
- * See http://go/clientsdns for usage approval
- */
- private static final String DEFAULT_WALLED_GARDEN_URL =
- "http://clients3.google.com/generate_204";
- private static final int WALLED_GARDEN_SOCKET_TIMEOUT_MS = 10000;
-
- /**
- * Some carrier apps might have support captive portal handling. Add some
- * delay to allow app authentication to be done before our test. TODO: This
- * should go away once we provide an API to apps to disable walled garden
- * test for certain SSIDs
- */
- private static final int WALLED_GARDEN_START_DELAY_MS = 3000;
-
-
/* Framework related */
private Context mContext;
private ContentResolver mContentResolver;
@@ -300,13 +270,6 @@ public class WifiWatchdogStateMachine extends StateMachine {
/* System settingss related */
private static boolean sWifiOnly = false;
private boolean mPoorNetworkDetectionEnabled;
- private long mWalledGardenIntervalMs;
- private boolean mWalledGardenTestEnabled;
- private String mWalledGardenUrl;
-
- /* Wall garden detection related */
- private long mLastWalledGardenCheckTime = 0;
- private boolean mWalledGardenNotificationShown;
/* Poor link detection related */
private LruCache<String, BssidStatistics> mBssidCache =
@@ -325,7 +288,6 @@ public class WifiWatchdogStateMachine extends StateMachine {
private NotConnectedState mNotConnectedState = new NotConnectedState();
private VerifyingLinkState mVerifyingLinkState = new VerifyingLinkState();
private ConnectedState mConnectedState = new ConnectedState();
- private WalledGardenCheckState mWalledGardenCheckState = new WalledGardenCheckState();
private OnlineWatchState mOnlineWatchState = new OnlineWatchState();
private LinkMonitoringState mLinkMonitoringState = new LinkMonitoringState();
private OnlineState mOnlineState = new OnlineState();
@@ -359,7 +321,6 @@ public class WifiWatchdogStateMachine extends StateMachine {
addState(mNotConnectedState, mWatchdogEnabledState);
addState(mVerifyingLinkState, mWatchdogEnabledState);
addState(mConnectedState, mWatchdogEnabledState);
- addState(mWalledGardenCheckState, mConnectedState);
addState(mOnlineWatchState, mConnectedState);
addState(mLinkMonitoringState, mConnectedState);
addState(mOnlineState, mConnectedState);
@@ -379,13 +340,12 @@ public class WifiWatchdogStateMachine extends StateMachine {
Context.CONNECTIVITY_SERVICE);
sWifiOnly = (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false);
- // Watchdog is always enabled. Poor network detection & walled garden detection
- // can individually be turned on/off
+ // Watchdog is always enabled. Poor network detection can be seperately turned on/off
// TODO: Remove this setting & clean up state machine since we always have
// watchdog in an enabled state
putSettingsBoolean(contentResolver, Settings.Secure.WIFI_WATCHDOG_ON, true);
- // disable poor network avoidance, but keep watchdog active for walled garden detection
+ // disable poor network avoidance
if (sWifiOnly) {
logd("Disabling poor network avoidance for wi-fi only device");
putSettingsBoolean(contentResolver,
@@ -458,44 +418,8 @@ public class WifiWatchdogStateMachine extends StateMachine {
};
mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_INTERVAL_MS),
- false, contentObserver);
- mContext.getContentResolver().registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED),
false, contentObserver);
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED),
- false, contentObserver);
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_URL),
- false, contentObserver);
- }
-
- /**
- * DNS based detection techniques do not work at all hotspots. The one sure
- * way to check a walled garden is to see if a URL fetch on a known address
- * fetches the data we expect
- */
- private boolean isWalledGardenConnection() {
- HttpURLConnection urlConnection = null;
- try {
- URL url = new URL(mWalledGardenUrl);
- urlConnection = (HttpURLConnection) url.openConnection();
- urlConnection.setInstanceFollowRedirects(false);
- urlConnection.setConnectTimeout(WALLED_GARDEN_SOCKET_TIMEOUT_MS);
- urlConnection.setReadTimeout(WALLED_GARDEN_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) logd("Walled garden check - probably not a portal: exception " + e);
- return false;
- } finally {
- if (urlConnection != null) {
- urlConnection.disconnect();
- }
- }
}
public void dump(PrintWriter pw) {
@@ -504,10 +428,7 @@ public class WifiWatchdogStateMachine extends StateMachine {
pw.println("mWifiInfo: [" + mWifiInfo + "]");
pw.println("mLinkProperties: [" + mLinkProperties + "]");
pw.println("mCurrentSignalLevel: [" + mCurrentSignalLevel + "]");
- pw.println("mWalledGardenIntervalMs: [" + mWalledGardenIntervalMs + "]");
pw.println("mPoorNetworkDetectionEnabled: [" + mPoorNetworkDetectionEnabled + "]");
- pw.println("mWalledGardenTestEnabled: [" + mWalledGardenTestEnabled + "]");
- pw.println("mWalledGardenUrl: [" + mWalledGardenUrl + "]");
}
private boolean isWatchdogEnabled() {
@@ -521,47 +442,6 @@ public class WifiWatchdogStateMachine extends StateMachine {
mPoorNetworkDetectionEnabled = getSettingsBoolean(mContentResolver,
Settings.Secure.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED, true);
- mWalledGardenTestEnabled = getSettingsBoolean(mContentResolver,
- Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED, true);
- mWalledGardenUrl = getSettingsStr(mContentResolver,
- Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_URL,
- DEFAULT_WALLED_GARDEN_URL);
- mWalledGardenIntervalMs = Secure.getLong(mContentResolver,
- Secure.WIFI_WATCHDOG_WALLED_GARDEN_INTERVAL_MS,
- DEFAULT_WALLED_GARDEN_INTERVAL_MS);
- }
-
- private void setWalledGardenNotificationVisible(boolean visible) {
- // if it should be hidden and it is already hidden, then noop
- if (!visible && !mWalledGardenNotificationShown) {
- return;
- }
-
- Resources r = Resources.getSystem();
- NotificationManager notificationManager = (NotificationManager) mContext
- .getSystemService(Context.NOTIFICATION_SERVICE);
-
- if (visible) {
- Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(mWalledGardenUrl));
- intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
-
- CharSequence title = r.getString(R.string.wifi_available_sign_in, 0);
- CharSequence details = r.getString(R.string.wifi_available_sign_in_detailed,
- mWifiInfo.getSSID());
-
- 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.getActivity(mContext, 0, intent, 0);
- notification.tickerText = title;
- notification.setLatestEventInfo(mContext, title, details, notification.contentIntent);
-
- notificationManager.notify(WALLED_GARDEN_NOTIFICATION_ID, 1, notification);
- } else {
- notificationManager.cancel(WALLED_GARDEN_NOTIFICATION_ID, 1);
- }
- mWalledGardenNotificationShown = visible;
}
/**
@@ -587,7 +467,6 @@ public class WifiWatchdogStateMachine extends StateMachine {
case EVENT_NETWORK_STATE_CHANGE:
case EVENT_SUPPLICANT_STATE_CHANGE:
case EVENT_BSSID_CHANGE:
- case CMD_DELAYED_WALLED_GARDEN_CHECK:
case CMD_RSSI_FETCH:
case WifiManager.RSSI_PKTCNT_FETCH_SUCCEEDED:
case WifiManager.RSSI_PKTCNT_FETCH_FAILED:
@@ -685,11 +564,7 @@ public class WifiWatchdogStateMachine extends StateMachine {
}
break;
case CONNECTED:
- if (shouldCheckWalledGarden()) {
- transitionTo(mWalledGardenCheckState);
- } else {
- transitionTo(mOnlineWatchState);
- }
+ transitionTo(mOnlineWatchState);
break;
default:
transitionTo(mNotConnectedState);
@@ -716,7 +591,6 @@ public class WifiWatchdogStateMachine extends StateMachine {
return NOT_HANDLED;
}
- setWalledGardenNotificationVisible(false);
return HANDLED;
}
}
@@ -834,38 +708,6 @@ public class WifiWatchdogStateMachine extends StateMachine {
}
/**
- * Checking for wall garden.
- */
- class WalledGardenCheckState extends State {
- private int mWalledGardenToken = 0;
- @Override
- public void enter() {
- if (DBG) logd(getName());
- sendMessageDelayed(obtainMessage(CMD_DELAYED_WALLED_GARDEN_CHECK,
- ++mWalledGardenToken, 0), WALLED_GARDEN_START_DELAY_MS);
- }
-
- @Override
- public boolean processMessage(Message msg) {
- switch (msg.what) {
- case CMD_DELAYED_WALLED_GARDEN_CHECK:
- if (msg.arg1 == mWalledGardenToken) {
- mLastWalledGardenCheckTime = SystemClock.elapsedRealtime();
- if (isWalledGardenConnection()) {
- if (DBG) logd("Walled garden detected");
- setWalledGardenNotificationVisible(true);
- }
- transitionTo(mOnlineWatchState);
- }
- break;
- default:
- return NOT_HANDLED;
- }
- return HANDLED;
- }
- }
-
- /**
* RSSI is high enough and don't need link monitoring.
*/
class OnlineWatchState extends State {
@@ -1037,22 +879,6 @@ public class WifiWatchdogStateMachine extends StateMachine {
}
}
- private boolean shouldCheckWalledGarden() {
- if (!mWalledGardenTestEnabled) {
- if (DBG) logd("Skipping walled garden check - disabled");
- return false;
- }
-
- long waitTime = (mWalledGardenIntervalMs + mLastWalledGardenCheckTime)
- - SystemClock.elapsedRealtime();
-
- if (mLastWalledGardenCheckTime != 0 && waitTime > 0) {
- if (DBG) logd("Skipping walled garden check - wait " + waitTime + " ms.");
- return false;
- }
- return true;
- }
-
private void updateCurrentBssid(String bssid) {
if (DBG) logd("Update current BSSID to " + (bssid != null ? bssid : "null"));