Merge commit '949dcad8' into manualmerge

Conflicts:
	services/java/com/android/server/ConnectivityService.java

Change-Id: I02fee6839c2a8879fb6e885d8fc8483f17d655c2
This commit is contained in:
Irfan Sheriff
2012-08-27 23:03:50 -07:00
18 changed files with 472 additions and 234 deletions

View File

@@ -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;

View File

@@ -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()}.
*/

View File

@@ -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);
}
}

View File

@@ -921,4 +921,15 @@ public class ConnectivityManager {
return false;
}
}
/**
* {@hide}
*/
public void captivePortalCheckComplete(NetworkInfo info) {
try {
mService.captivePortalCheckComplete(info);
} catch (RemoteException e) {
}
}
}

View File

@@ -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

View File

@@ -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}

View File

@@ -124,4 +124,6 @@ interface IConnectivityManager
LegacyVpnInfo getLegacyVpnInfo();
boolean updateLockdownVpn();
void captivePortalCheckComplete(in NetworkInfo info);
}

View File

@@ -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

View File

@@ -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);

View File

@@ -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}

View File

@@ -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.

View File

@@ -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) {

View File

@@ -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}
*

View File

@@ -106,5 +106,7 @@ interface IWifiManager
Messenger getWifiStateMachineMessenger();
String getConfigFile();
void captivePortalCheckComplete();
}

View File

@@ -1971,4 +1971,11 @@ public class WifiManager {
return false;
}
}
/** @hide */
public void captivePortalCheckComplete() {
try {
mService.captivePortalCheckComplete();
} catch (RemoteException e) {}
}
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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,
@@ -457,45 +417,9 @@ 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;
}
}
@@ -833,38 +707,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.
*/
@@ -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"));