diff --git a/api/current.txt b/api/current.txt index ac174599b9a90..bc70390961e23 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 b2b5d81fe3ae0..30406e98710bb 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 0000000000000..aa392d0482b23 --- /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 + *
Input: Nothing. + *
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 60bf4d6869fc1..a57047363e697 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 ccd96ff3c0c16..39440c2a730f5 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 c690430c09f0a..c52aa9eda7e03 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 3614045f42d02..056fa030e97d9 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 d59fa6a917121..b35d61ca42c97 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 0bc6b58aef33a..0b23cb72a7caa 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 eae89f1cb8507..0a0c1e06de2a7 100644
--- a/core/java/android/net/NetworkStateTracker.java
+++ b/core/java/android/net/NetworkStateTracker.java
@@ -122,6 +122,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 1f6f0dd36ee5a..6f43891a88b68 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3278,13 +3278,6 @@ public final class Settings {
"wifi_watchdog_rssi_fetch_interval_ms";
- /**
- * 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
@@ -3321,23 +3314,6 @@ public final class Settings {
public static final String WIFI_SUSPEND_OPTIMIZATIONS_ENABLED =
"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.
@@ -3361,6 +3337,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 06d6236ac8d26..8a1ac109f3c97 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,34 +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 NetworkStateTracker thisNet = mNetTrackers[type];
+ final int newNetType = info.getType();
- setupDataActivityTracking(type);
-
- setupDataActivityTracking(type);
+ setupDataActivityTracking(newNetType);
// snapshot isFailover, because sendConnectedBroadcast() resets it
boolean isFailover = info.isFailover();
+ 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];
@@ -2006,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) {
@@ -2019,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
@@ -2031,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.
*
@@ -2632,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 f483576f8fccf..84d670e3e1632 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 6b0807414b913..55de065e90893 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 284bee8d8e03d..aa591582dfca7 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 a5322fa7533db..b4456b9d4b078 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 bfb91e2d4661f..a5a24691a1294 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;
@@ -112,6 +113,14 @@ public class WifiStateTracker implements NetworkStateTracker {
return true;
}
+ /**
+ * 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 29a53b6c07822..7fa6aaca826c7 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