diff options
author | 2012-02-29 19:33:06 -0800 | |
---|---|---|
committer | 2012-02-29 19:33:06 -0800 | |
commit | 43d8a95fa8dfd26ba8c56ac7489a8bc77c77034c (patch) | |
tree | 8aed3c6b7190073512833b461e4e28aa2a9fe657 | |
parent | 7084e75282d6c800a1b889eff66794a8cd62e0c5 (diff) | |
parent | 07573b32494acbabd21979d8b9584c1ed3f7a6ad (diff) |
Merge "Improve Wi-Fi hand-off"
-rw-r--r-- | api/current.txt | 1 | ||||
-rw-r--r-- | core/java/android/net/NetworkInfo.java | 5 | ||||
-rw-r--r-- | core/java/android/net/arp/ArpPeer.java | 132 | ||||
-rw-r--r-- | core/java/android/provider/Settings.java | 55 | ||||
-rw-r--r-- | packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java | 15 | ||||
-rw-r--r-- | services/java/com/android/server/WifiService.java | 9 | ||||
-rw-r--r-- | wifi/java/android/net/wifi/IWifiManager.aidl | 4 | ||||
-rw-r--r-- | wifi/java/android/net/wifi/SupplicantState.java | 4 | ||||
-rw-r--r-- | wifi/java/android/net/wifi/WifiManager.java | 21 | ||||
-rw-r--r-- | wifi/java/android/net/wifi/WifiStateMachine.java | 291 | ||||
-rw-r--r-- | wifi/java/android/net/wifi/WifiWatchdogStateMachine.java | 993 |
11 files changed, 693 insertions, 837 deletions
diff --git a/api/current.txt b/api/current.txt index 5babf232f709..cce28a48366f 100644 --- a/api/current.txt +++ b/api/current.txt @@ -11815,6 +11815,7 @@ package android.net { enum_constant public static final android.net.NetworkInfo.DetailedState OBTAINING_IPADDR; enum_constant public static final android.net.NetworkInfo.DetailedState SCANNING; enum_constant public static final android.net.NetworkInfo.DetailedState SUSPENDED; + enum_constant public static final android.net.NetworkInfo.DetailedState VERIFYING_POOR_LINK; } public static final class NetworkInfo.State extends java.lang.Enum { diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java index 2f43cb87f6fc..0bc6b58aef33 100644 --- a/core/java/android/net/NetworkInfo.java +++ b/core/java/android/net/NetworkInfo.java @@ -77,7 +77,9 @@ public class NetworkInfo implements Parcelable { /** Attempt to connect failed. */ FAILED, /** Access to this network is blocked. */ - BLOCKED + BLOCKED, + /** Link has poor connectivity. */ + VERIFYING_POOR_LINK } /** @@ -94,6 +96,7 @@ public class NetworkInfo implements Parcelable { stateMap.put(DetailedState.CONNECTING, State.CONNECTING); stateMap.put(DetailedState.AUTHENTICATING, State.CONNECTING); stateMap.put(DetailedState.OBTAINING_IPADDR, State.CONNECTING); + stateMap.put(DetailedState.VERIFYING_POOR_LINK, 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/arp/ArpPeer.java b/core/java/android/net/arp/ArpPeer.java new file mode 100644 index 000000000000..8e666bc275da --- /dev/null +++ b/core/java/android/net/arp/ArpPeer.java @@ -0,0 +1,132 @@ +/* + * 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.arp; + +import android.os.SystemClock; +import android.util.Log; +import java.io.IOException; +import java.net.InetAddress; +import java.net.Inet6Address; +import java.net.SocketException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; + +import libcore.net.RawSocket; + +/** + * This class allows simple ARP exchanges over an uninitialized network + * interface. + * + * @hide + */ +public class ArpPeer { + private String mInterfaceName; + private final InetAddress mMyAddr; + private final byte[] mMyMac = new byte[6]; + private final InetAddress mPeer; + private final RawSocket mSocket; + private final byte[] L2_BROADCAST; // TODO: refactor from DhcpClient.java + private static final int MAX_LENGTH = 1500; // refactor from DhcpPacket.java + private static final int ETHERNET_TYPE = 1; + private static final int ARP_LENGTH = 28; + private static final int MAC_ADDR_LENGTH = 6; + private static final int IPV4_LENGTH = 4; + private static final String TAG = "ArpPeer"; + + public ArpPeer(String interfaceName, InetAddress myAddr, String mac, + InetAddress peer) throws SocketException { + mInterfaceName = interfaceName; + mMyAddr = myAddr; + + for (int i = 0; i < MAC_ADDR_LENGTH; i++) { + mMyMac[i] = (byte) Integer.parseInt(mac.substring( + i*3, (i*3) + 2), 16); + } + + if (myAddr instanceof Inet6Address || peer instanceof Inet6Address) { + throw new IllegalArgumentException("IPv6 unsupported"); + } + + mPeer = peer; + L2_BROADCAST = new byte[MAC_ADDR_LENGTH]; + Arrays.fill(L2_BROADCAST, (byte) 0xFF); + + mSocket = new RawSocket(mInterfaceName, RawSocket.ETH_P_ARP); + } + + /** + * Returns the MAC address (or null if timeout) for the requested + * peer. + */ + public byte[] doArp(int timeoutMillis) { + ByteBuffer buf = ByteBuffer.allocate(MAX_LENGTH); + byte[] desiredIp = mPeer.getAddress(); + long timeout = SystemClock.elapsedRealtime() + timeoutMillis; + + // construct ARP request packet, using a ByteBuffer as a + // convenient container + buf.clear(); + buf.order(ByteOrder.BIG_ENDIAN); + + buf.putShort((short) ETHERNET_TYPE); // Ethernet type, 16 bits + buf.putShort(RawSocket.ETH_P_IP); // Protocol type IP, 16 bits + buf.put((byte)MAC_ADDR_LENGTH); // MAC address length, 6 bytes + buf.put((byte)IPV4_LENGTH); // IPv4 protocol size + buf.putShort((short) 1); // ARP opcode 1: 'request' + buf.put(mMyMac); // six bytes: sender MAC + buf.put(mMyAddr.getAddress()); // four bytes: sender IP address + buf.put(new byte[MAC_ADDR_LENGTH]); // target MAC address: unknown + buf.put(desiredIp); // target IP address, 4 bytes + buf.flip(); + mSocket.write(L2_BROADCAST, buf.array(), 0, buf.limit()); + + byte[] recvBuf = new byte[MAX_LENGTH]; + + while (SystemClock.elapsedRealtime() < timeout) { + long duration = (long) timeout - SystemClock.elapsedRealtime(); + int readLen = mSocket.read(recvBuf, 0, recvBuf.length, -1, + (int) duration); + + // Verify packet details. see RFC 826 + if ((readLen >= ARP_LENGTH) // trailing bytes at times + && (recvBuf[0] == 0) && (recvBuf[1] == ETHERNET_TYPE) // type Ethernet + && (recvBuf[2] == 8) && (recvBuf[3] == 0) // protocol IP + && (recvBuf[4] == MAC_ADDR_LENGTH) // mac length + && (recvBuf[5] == IPV4_LENGTH) // IPv4 protocol size + && (recvBuf[6] == 0) && (recvBuf[7] == 2) // ARP reply + // verify desired IP address + && (recvBuf[14] == desiredIp[0]) && (recvBuf[15] == desiredIp[1]) + && (recvBuf[16] == desiredIp[2]) && (recvBuf[17] == desiredIp[3])) + { + // looks good. copy out the MAC + byte[] result = new byte[MAC_ADDR_LENGTH]; + System.arraycopy(recvBuf, 8, result, 0, MAC_ADDR_LENGTH); + return result; + } + } + + return null; + } + + public void close() { + try { + mSocket.close(); + } catch (IOException ex) { + } + } +} diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 0aad64a61be9..b42417add694 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -3125,15 +3125,8 @@ public final class Settings { * ms delay before rechecking an 'online' wifi connection when it is thought to be unstable. * @hide */ - public static final String WIFI_WATCHDOG_DNS_CHECK_SHORT_INTERVAL_MS = - "wifi_watchdog_dns_check_short_interval_ms"; - - /** - * ms delay before rechecking an 'online' wifi connection when it is thought to be stable. - * @hide - */ - public static final String WIFI_WATCHDOG_DNS_CHECK_LONG_INTERVAL_MS = - "wifi_watchdog_dns_check_long_interval_ms"; + public static final String WIFI_WATCHDOG_ARP_CHECK_INTERVAL_MS = + "wifi_watchdog_arp_interval_ms"; /** * ms delay before rechecking a connect SSID for walled garden with a http download. @@ -3143,44 +3136,28 @@ public final class Settings { "wifi_watchdog_walled_garden_interval_ms"; /** - * max blacklist calls on an SSID before full dns check failures disable the network. + * Number of ARP pings per check. * @hide */ - public static final String WIFI_WATCHDOG_MAX_SSID_BLACKLISTS = - "wifi_watchdog_max_ssid_blacklists"; + public static final String WIFI_WATCHDOG_NUM_ARP_PINGS = "wifi_watchdog_num_arp_pings"; /** - * Number of dns pings per check. + * Minimum number of responses to the arp pings to consider the test 'successful'. * @hide */ - public static final String WIFI_WATCHDOG_NUM_DNS_PINGS = "wifi_watchdog_num_dns_pings"; + public static final String WIFI_WATCHDOG_MIN_ARP_RESPONSES = + "wifi_watchdog_min_arp_responses"; /** - * Minimum number of responses to the dns pings to consider the test 'successful'. + * Timeout on ARP pings * @hide */ - public static final String WIFI_WATCHDOG_MIN_DNS_RESPONSES = - "wifi_watchdog_min_dns_responses"; + public static final String WIFI_WATCHDOG_ARP_PING_TIMEOUT_MS = + "wifi_watchdog_arp_ping_timeout_ms"; /** - * Timeout on dns pings - * @hide - */ - public static final String WIFI_WATCHDOG_DNS_PING_TIMEOUT_MS = - "wifi_watchdog_dns_ping_timeout_ms"; - - /** - * We consider action from a 'blacklist' call to have finished by the end of - * this interval. If we are connected to the same AP with no network connection, - * we are likely stuck on an SSID with no external connectivity. - * @hide - */ - public static final String WIFI_WATCHDOG_BLACKLIST_FOLLOWUP_INTERVAL_MS = - "wifi_watchdog_blacklist_followup_interval_ms"; - - /** - * Setting to turn off poor network avoidance on Wi-Fi. Feature is disabled by default and - * the setting needs to be set to 1 to enable it. + * Setting to turn off poor network avoidance 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_POOR_NETWORK_TEST_ENABLED = @@ -3204,14 +3181,6 @@ public final class Settings { "wifi_watchdog_walled_garden_url"; /** - * Boolean to determine whether to notify on disabling a network. Secure setting used - * to notify user only once. - * @hide - */ - public static final String WIFI_WATCHDOG_SHOW_DISABLED_NETWORK_POPUP = - "wifi_watchdog_show_disabled_network_popup"; - - /** * 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. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java index c59290cc2453..95704f676f14 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java @@ -203,7 +203,7 @@ public class NetworkController extends BroadcastReceiver { mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); Handler handler = new WifiHandler(); mWifiChannel = new AsyncChannel(); - Messenger wifiMessenger = mWifiManager.getMessenger(); + Messenger wifiMessenger = mWifiManager.getWifiServiceMessenger(); if (wifiMessenger != null) { mWifiChannel.connect(mContext, handler, wifiMessenger); } @@ -767,17 +767,10 @@ public class NetworkController extends BroadcastReceiver { } else if (!mWifiConnected) { mWifiSsid = null; } - // Apparently the wifi level is not stable at this point even if we've just connected to - // the network; we need to wait for an RSSI_CHANGED_ACTION for that. So let's just set - // it to 0 for now - mWifiLevel = 0; - mWifiRssi = -200; } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) { - if (mWifiConnected) { - mWifiRssi = intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200); - mWifiLevel = WifiManager.calculateSignalLevel( - mWifiRssi, WifiIcons.WIFI_LEVEL_COUNT); - } + mWifiRssi = intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200); + mWifiLevel = WifiManager.calculateSignalLevel( + mWifiRssi, WifiIcons.WIFI_LEVEL_COUNT); } updateWifiIcons(); diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java index 52087854c07b..f09c43f51a84 100644 --- a/services/java/com/android/server/WifiService.java +++ b/services/java/com/android/server/WifiService.java @@ -914,7 +914,7 @@ public class WifiService extends IWifiManager.Stub { * Get a reference to handler. This is used by a client to establish * an AsyncChannel communication with WifiService */ - public Messenger getMessenger() { + public Messenger getWifiServiceMessenger() { /* Enforce the highest permissions TODO: when we consider exposing the asynchronous API, think about how to provide both access and change permissions seperately @@ -924,6 +924,13 @@ public class WifiService extends IWifiManager.Stub { return new Messenger(mAsyncServiceHandler); } + /** Get a reference to WifiStateMachine handler for AsyncChannel communication */ + public Messenger getWifiStateMachineMessenger() { + enforceAccessPermission(); + enforceChangePermission(); + return mWifiStateMachine.getMessenger(); + } + /** * Get the IP and proxy configuration file */ diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index 61dfebf41344..6b0807414b91 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -101,7 +101,9 @@ interface IWifiManager void clearBlacklist(); - Messenger getMessenger(); + Messenger getWifiServiceMessenger(); + + Messenger getWifiStateMachineMessenger(); String getConfigFile(); } diff --git a/wifi/java/android/net/wifi/SupplicantState.java b/wifi/java/android/net/wifi/SupplicantState.java index 509b02ca82d8..4a2037d1237d 100644 --- a/wifi/java/android/net/wifi/SupplicantState.java +++ b/wifi/java/android/net/wifi/SupplicantState.java @@ -171,8 +171,8 @@ public enum SupplicantState implements Parcelable { } - /* Supplicant associating or authenticating is considered a handshake state */ - static boolean isHandshakeState(SupplicantState state) { + /** Supplicant associating or authenticating is considered a handshake state {@hide} */ + public static boolean isHandshakeState(SupplicantState state) { switch(state) { case AUTHENTICATING: case ASSOCIATING: diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 1a0e0dad5f85..1acfd3a5ec21 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -1096,7 +1096,7 @@ public class WifiManager { * @hide */ public void asyncConnect(Context srcContext, Handler srcHandler) { - mAsyncChannel.connect(srcContext, srcHandler, getMessenger()); + mAsyncChannel.connect(srcContext, srcHandler, getWifiServiceMessenger()); } /** @@ -1197,15 +1197,30 @@ public class WifiManager { * @return Messenger pointing to the WifiService handler * @hide */ - public Messenger getMessenger() { + public Messenger getWifiServiceMessenger() { try { - return mService.getMessenger(); + return mService.getWifiServiceMessenger(); } catch (RemoteException e) { return null; } } /** + * Get a reference to WifiStateMachine handler. + * @return Messenger pointing to the WifiService handler + * @hide + */ + public Messenger getWifiStateMachineMessenger() { + try { + return mService.getWifiStateMachineMessenger(); + } catch (RemoteException e) { + return null; + } + } + + + + /** * Returns the file in which IP and proxy configuration data is stored * @hide */ diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java index 1b64f3e3a2f5..e140d80a3263 100644 --- a/wifi/java/android/net/wifi/WifiStateMachine.java +++ b/wifi/java/android/net/wifi/WifiStateMachine.java @@ -463,8 +463,12 @@ public class WifiStateMachine extends StateMachine { private State mScanModeState = new ScanModeState(); /* Connecting to an access point */ private State mConnectModeState = new ConnectModeState(); - /* Fetching IP after network connection (assoc+auth complete) */ - private State mConnectingState = new ConnectingState(); + /* Connected at 802.11 (L2) level */ + private State mL2ConnectedState = new L2ConnectedState(); + /* fetching IP after connection to access point (assoc+auth complete) */ + private State mObtainingIpState = new ObtainingIpState(); + /* Waiting for link quality verification to be complete */ + private State mVerifyingLinkState = new VerifyingLinkState(); /* Connected with IP addr */ private State mConnectedState = new ConnectedState(); /* disconnect issued, waiting for network disconnect confirmation */ @@ -629,8 +633,10 @@ public class WifiStateMachine extends StateMachine { addState(mDriverStartedState, mSupplicantStartedState); addState(mScanModeState, mDriverStartedState); addState(mConnectModeState, mDriverStartedState); - addState(mConnectingState, mConnectModeState); - addState(mConnectedState, mConnectModeState); + addState(mL2ConnectedState, mConnectModeState); + addState(mObtainingIpState, mL2ConnectedState); + addState(mVerifyingLinkState, mL2ConnectedState); + addState(mConnectedState, mL2ConnectedState); addState(mDisconnectingState, mConnectModeState); addState(mDisconnectedState, mConnectModeState); addState(mWaitForWpsCompletionState, mConnectModeState); @@ -655,6 +661,9 @@ public class WifiStateMachine extends StateMachine { * Methods exposed for public use ********************************************************/ + public Messenger getMessenger() { + return new Messenger(getHandler()); + } /** * TODO: doc */ @@ -1543,12 +1552,14 @@ public class WifiStateMachine extends StateMachine { Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | Intent.FLAG_RECEIVER_REPLACE_PENDING); - intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, mNetworkInfo); + intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, new NetworkInfo(mNetworkInfo)); intent.putExtra(WifiManager.EXTRA_LINK_PROPERTIES, new LinkProperties (mLinkProperties)); if (bssid != null) intent.putExtra(WifiManager.EXTRA_BSSID, bssid); - if (mNetworkInfo.getState() == NetworkInfo.State.CONNECTED) + if (mNetworkInfo.getDetailedState() == DetailedState.VERIFYING_POOR_LINK || + mNetworkInfo.getDetailedState() == DetailedState.CONNECTED) { intent.putExtra(WifiManager.EXTRA_WIFI_INFO, new WifiInfo(mWifiInfo)); + } mContext.sendStickyBroadcast(intent); } @@ -1740,9 +1751,6 @@ public class WifiStateMachine extends StateMachine { } } else { configureLinkProperties(); - setNetworkDetailedState(DetailedState.CONNECTED); - mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.CONNECTED); - sendNetworkStateChangeBroadcast(mLastBssid); } } @@ -1890,6 +1898,8 @@ public class WifiStateMachine extends StateMachine { case CMD_SET_AP_CONFIG_COMPLETED: case CMD_REQUEST_AP_CONFIG: case CMD_RESPONSE_AP_CONFIG: + case WifiWatchdogStateMachine.POOR_LINK_DETECTED: + case WifiWatchdogStateMachine.GOOD_LINK_DETECTED: break; case WifiMonitor.DRIVER_HUNG_EVENT: setWifiEnabled(false); @@ -2885,7 +2895,7 @@ public class WifiStateMachine extends StateMachine { /* send event to CM & network change broadcast */ setNetworkDetailedState(DetailedState.OBTAINING_IPADDR); sendNetworkStateChangeBroadcast(mLastBssid); - transitionTo(mConnectingState); + transitionTo(mObtainingIpState); break; case WifiMonitor.NETWORK_DISCONNECTION_EVENT: if (DBG) log("Network connection lost"); @@ -2900,122 +2910,18 @@ public class WifiStateMachine extends StateMachine { } } - class ConnectingState extends State { - - @Override - public void enter() { - if (DBG) log(getName() + "\n"); - EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); - - try { - mNwService.enableIpv6(mInterfaceName); - } catch (RemoteException re) { - loge("Failed to enable IPv6: " + re); - } catch (IllegalStateException e) { - loge("Failed to enable IPv6: " + e); - } - - if (!mWifiConfigStore.isUsingStaticIp(mLastNetworkId)) { - //start DHCP - mDhcpStateMachine = DhcpStateMachine.makeDhcpStateMachine( - mContext, WifiStateMachine.this, mInterfaceName); - mDhcpStateMachine.registerForPreDhcpNotification(); - mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_START_DHCP); - } else { - DhcpInfoInternal dhcpInfoInternal = mWifiConfigStore.getIpConfiguration( - mLastNetworkId); - InterfaceConfiguration ifcg = new InterfaceConfiguration(); - ifcg.setLinkAddress(dhcpInfoInternal.makeLinkAddress()); - ifcg.setInterfaceUp(); - try { - mNwService.setInterfaceConfig(mInterfaceName, ifcg); - if (DBG) log("Static IP configuration succeeded"); - sendMessage(CMD_STATIC_IP_SUCCESS, dhcpInfoInternal); - } catch (RemoteException re) { - loge("Static IP configuration failed: " + re); - sendMessage(CMD_STATIC_IP_FAILURE); - } catch (IllegalStateException e) { - loge("Static IP configuration failed: " + e); - sendMessage(CMD_STATIC_IP_FAILURE); - } - } - } - @Override - public boolean processMessage(Message message) { - if (DBG) log(getName() + message.toString() + "\n"); - - switch(message.what) { - case DhcpStateMachine.CMD_PRE_DHCP_ACTION: - handlePreDhcpSetup(); - mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_PRE_DHCP_ACTION_COMPLETE); - break; - case DhcpStateMachine.CMD_POST_DHCP_ACTION: - handlePostDhcpSetup(); - if (message.arg1 == DhcpStateMachine.DHCP_SUCCESS) { - handleSuccessfulIpConfiguration((DhcpInfoInternal) message.obj); - transitionTo(mConnectedState); - } else if (message.arg1 == DhcpStateMachine.DHCP_FAILURE) { - handleFailedIpConfiguration(); - transitionTo(mDisconnectingState); - } - break; - case CMD_STATIC_IP_SUCCESS: - handleSuccessfulIpConfiguration((DhcpInfoInternal) message.obj); - transitionTo(mConnectedState); - break; - case CMD_STATIC_IP_FAILURE: - handleFailedIpConfiguration(); - transitionTo(mDisconnectingState); - break; - case CMD_DISCONNECT: - mWifiNative.disconnect(); - transitionTo(mDisconnectingState); - break; - /* Ignore connection to same network */ - case CMD_CONNECT_NETWORK: - int netId = message.arg1; - if (mWifiInfo.getNetworkId() == netId) { - break; - } - return NOT_HANDLED; - case CMD_SAVE_NETWORK: - deferMessage(message); - break; - /* Ignore */ - case WifiMonitor.NETWORK_CONNECTION_EVENT: - break; - case CMD_SET_SCAN_MODE: - if (message.arg1 == SCAN_ONLY_MODE) { - sendMessage(CMD_DISCONNECT); - deferMessage(message); - } - break; - /* Defer scan when IP is being fetched */ - case CMD_START_SCAN: - deferMessage(message); - break; - /* Defer any power mode changes since we must keep active power mode at DHCP */ - case CMD_SET_HIGH_PERF_MODE: - deferMessage(message); - break; - default: - return NOT_HANDLED; - } - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); - return HANDLED; - } - } - class ConnectedState extends State { + class L2ConnectedState extends State { @Override public void enter() { if (DBG) log(getName() + "\n"); EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); mRssiPollToken++; if (mEnableRssiPolling) { - sendMessage(obtainMessage(WifiStateMachine.CMD_RSSI_POLL, mRssiPollToken, 0)); + sendMessage(obtainMessage(CMD_RSSI_POLL, mRssiPollToken, 0)); } } + @Override public boolean processMessage(Message message) { if (DBG) log(getName() + message.toString() + "\n"); @@ -3028,8 +2934,11 @@ public class WifiStateMachine extends StateMachine { case DhcpStateMachine.CMD_POST_DHCP_ACTION: handlePostDhcpSetup(); if (message.arg1 == DhcpStateMachine.DHCP_SUCCESS) { + if (DBG) log("DHCP successful"); handleSuccessfulIpConfiguration((DhcpInfoInternal) message.obj); + transitionTo(mVerifyingLinkState); } else if (message.arg1 == DhcpStateMachine.DHCP_FAILURE) { + if (DBG) log("DHCP failed"); handleFailedIpConfiguration(); transitionTo(mDisconnectingState); } @@ -3067,7 +2976,7 @@ public class WifiStateMachine extends StateMachine { if (mWifiInfo.getNetworkId() == result.getNetworkId()) { if (result.hasIpChanged()) { log("Reconfiguring IP on connection"); - transitionTo(mConnectingState); + transitionTo(mObtainingIpState); } if (result.hasProxyChanged()) { log("Reconfiguring proxy on connection"); @@ -3084,7 +2993,7 @@ public class WifiStateMachine extends StateMachine { if (message.arg1 == mRssiPollToken) { // Get Info and continue polling fetchRssiAndLinkSpeedNative(); - sendMessageDelayed(obtainMessage(WifiStateMachine.CMD_RSSI_POLL, + sendMessageDelayed(obtainMessage(CMD_RSSI_POLL, mRssiPollToken, 0), POLL_RSSI_INTERVAL_MSECS); } else { // Polling has completed @@ -3096,25 +3005,22 @@ public class WifiStateMachine extends StateMachine { if (mEnableRssiPolling) { // first poll fetchRssiAndLinkSpeedNative(); - sendMessageDelayed(obtainMessage(WifiStateMachine.CMD_RSSI_POLL, + sendMessageDelayed(obtainMessage(CMD_RSSI_POLL, mRssiPollToken, 0), POLL_RSSI_INTERVAL_MSECS); } break; default: return NOT_HANDLED; } + if (eventLoggingEnabled) { EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); } return HANDLED; } + @Override public void exit() { - - /* Request a CS wakelock during transition to mobile */ - checkAndSetConnectivityInstance(); - mCm.requestNetworkTransitionWakelock(TAG); - /* If a scan result is pending in connected state, the supplicant * is in SCAN_ONLY_MODE. Restore CONNECT_MODE on exit */ @@ -3124,6 +3030,141 @@ public class WifiStateMachine extends StateMachine { } } + class ObtainingIpState extends State { + @Override + public void enter() { + if (DBG) log(getName() + "\n"); + EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); + + if (!mWifiConfigStore.isUsingStaticIp(mLastNetworkId)) { + //start DHCP + mDhcpStateMachine = DhcpStateMachine.makeDhcpStateMachine( + mContext, WifiStateMachine.this, mInterfaceName); + mDhcpStateMachine.registerForPreDhcpNotification(); + mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_START_DHCP); + } else { + DhcpInfoInternal dhcpInfoInternal = mWifiConfigStore.getIpConfiguration( + mLastNetworkId); + InterfaceConfiguration ifcg = new InterfaceConfiguration(); + ifcg.setLinkAddress(dhcpInfoInternal.makeLinkAddress()); + ifcg.setInterfaceUp(); + try { + mNwService.setInterfaceConfig(mInterfaceName, ifcg); + if (DBG) log("Static IP configuration succeeded"); + sendMessage(CMD_STATIC_IP_SUCCESS, dhcpInfoInternal); + } catch (RemoteException re) { + loge("Static IP configuration failed: " + re); + sendMessage(CMD_STATIC_IP_FAILURE); + } catch (IllegalStateException e) { + loge("Static IP configuration failed: " + e); + sendMessage(CMD_STATIC_IP_FAILURE); + } + } + } + @Override + public boolean processMessage(Message message) { + if (DBG) log(getName() + message.toString() + "\n"); + switch(message.what) { + case CMD_STATIC_IP_SUCCESS: + handleSuccessfulIpConfiguration((DhcpInfoInternal) message.obj); + transitionTo(mVerifyingLinkState); + break; + case CMD_STATIC_IP_FAILURE: + handleFailedIpConfiguration(); + transitionTo(mDisconnectingState); + break; + case CMD_SAVE_NETWORK: + deferMessage(message); + break; + /* Defer any power mode changes since we must keep active power mode at DHCP */ + case CMD_SET_HIGH_PERF_MODE: + deferMessage(message); + break; + default: + return NOT_HANDLED; + } + EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); + return HANDLED; + } + } + + class VerifyingLinkState extends State { + @Override + public void enter() { + if (DBG) log(getName() + "\n"); + EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); + setNetworkDetailedState(DetailedState.VERIFYING_POOR_LINK); + mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.VERIFYING_POOR_LINK); + sendNetworkStateChangeBroadcast(mLastBssid); + } + @Override + public boolean processMessage(Message message) { + switch (message.what) { + case WifiWatchdogStateMachine.POOR_LINK_DETECTED: + //stay here + break; + case WifiWatchdogStateMachine.GOOD_LINK_DETECTED: + try { + mNwService.enableIpv6(mInterfaceName); + } catch (RemoteException re) { + loge("Failed to enable IPv6: " + re); + } catch (IllegalStateException e) { + loge("Failed to enable IPv6: " + e); + } + + setNetworkDetailedState(DetailedState.CONNECTED); + mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.CONNECTED); + sendNetworkStateChangeBroadcast(mLastBssid); + transitionTo(mConnectedState); + break; + default: + return NOT_HANDLED; + } + EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); + return HANDLED; + } + } + + class ConnectedState extends State { + @Override + public void enter() { + if (DBG) log(getName() + "\n"); + EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); + } + @Override + public boolean processMessage(Message message) { + if (DBG) log(getName() + message.toString() + "\n"); + switch (message.what) { + case WifiWatchdogStateMachine.POOR_LINK_DETECTED: + if (DBG) log("Watchdog reports poor link"); + try { + mNwService.disableIpv6(mInterfaceName); + } catch (RemoteException re) { + loge("Failed to disable IPv6: " + re); + } catch (IllegalStateException e) { + loge("Failed to disable IPv6: " + e); + } + /* Report a disconnect */ + setNetworkDetailedState(DetailedState.DISCONNECTED); + mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.DISCONNECTED); + sendNetworkStateChangeBroadcast(mLastBssid); + + transitionTo(mVerifyingLinkState); + break; + default: + return NOT_HANDLED; + } + EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); + return HANDLED; + } + @Override + public void exit() { + /* Request a CS wakelock during transition to mobile */ + checkAndSetConnectivityInstance(); + mCm.requestNetworkTransitionWakelock(TAG); + } + } + class DisconnectingState extends State { @Override public void enter() { diff --git a/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java b/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java index 0ca3852a0bdc..a2f63438c4fa 100644 --- a/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java +++ b/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java @@ -26,9 +26,12 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources; import android.database.ContentObserver; +import android.net.arp.ArpPeer; import android.net.ConnectivityManager; -import android.net.DnsPinger; +import android.net.LinkAddress; +import android.net.LinkProperties; import android.net.NetworkInfo; +import android.net.RouteInfo; import android.net.Uri; import android.os.Message; import android.os.SystemClock; @@ -38,6 +41,7 @@ import android.provider.Settings.Secure; import android.util.Log; import com.android.internal.R; +import com.android.internal.util.AsyncChannel; import com.android.internal.util.Protocol; import com.android.internal.util.State; import com.android.internal.util.StateMachine; @@ -46,49 +50,66 @@ import java.io.IOException; import java.io.PrintWriter; import java.net.HttpURLConnection; import java.net.InetAddress; +import java.net.SocketException; import java.net.URL; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; /** - * {@link WifiWatchdogStateMachine} monitors the initial connection to a Wi-Fi - * network with multiple access points. After the framework successfully - * connects to an access point, the watchdog verifies connectivity by 'pinging' - * the configured DNS server using {@link DnsPinger}. - * <p> - * On DNS check failure, the BSSID is blacklisted if it is reasonably likely - * that another AP might have internet access; otherwise the SSID is disabled. - * <p> - * On DNS success, the WatchdogService initiates a walled garden check via an - * http get. A browser window is activated if a walled garden is detected. + * WifiWatchdogStateMachine monitors the connection to a Wi-Fi + * network. After the framework notifies that it has connected to an + * acccess point and is waiting for link to be verified, the watchdog + * takes over and verifies if the link is good by doing ARP pings to + * the gateway using {@link ArpPeer}. + * + * Upon successful verification, the watchdog notifies and continues + * to monitor the link afterwards when the RSSI level falls below + * a certain threshold. + + * When Wi-fi connects at L2 layer, the beacons from access point reach + * the device and it can maintain a connection, but the application + * connectivity can be flaky (due to bigger packet size exchange). + * + * We now monitor the quality of the last hop on + * Wi-Fi using signal strength and ARP connectivity as indicators + * to decide if the link is good enough to switch to Wi-Fi as the uplink. + * + * ARP pings are useful for link validation but can still get through + * when the application traffic fails to go through and are thus not + * the best indicator of real packet loss since they are tiny packets + * (28 bytes) and have a much low chance of packet corruption than the + * regular data packets. + * + * When signal strength and ARP are used together, it ends up working well in tests. + * The goal is to switch to Wi-Fi after validating ARP transfer + * and RSSI and then switching out of Wi-Fi when we hit a low + * signal strength threshold and then waiting until the signal strength + * improves and validating ARP transfer. * * @hide */ public class WifiWatchdogStateMachine extends StateMachine { - private static final boolean DBG = false; + /* STOPSHIP: Keep this configurable for debugging until ship */ + private static boolean DBG = false; private static final String TAG = "WifiWatchdogStateMachine"; - private static final String DISABLED_NETWORK_NOTIFICATION_ID = "WifiWatchdog.networkdisabled"; private static final String WALLED_GARDEN_NOTIFICATION_ID = "WifiWatchdog.walledgarden"; - private static final int WIFI_SIGNAL_LEVELS = 4; - /** - * Low signal is defined as less than or equal to cut off - */ - private static final int LOW_SIGNAL_CUTOFF = 0; + /* Wi-fi connection is considered poor below this + RSSI level threshold and the watchdog report it + to the WifiStateMachine */ + private static final int RSSI_LEVEL_CUTOFF = 1; + /* Wi-fi connection is monitored actively below this + threshold */ + private static final int RSSI_LEVEL_MONITOR = 2; - private static final long DEFAULT_DNS_CHECK_SHORT_INTERVAL_MS = 2 * 60 * 1000; - private static final long DEFAULT_DNS_CHECK_LONG_INTERVAL_MS = 60 * 60 * 1000; - private static final long DEFAULT_WALLED_GARDEN_INTERVAL_MS = 30 * 60 * 1000; + private int mCurrentSignalLevel; - private static final int DEFAULT_MAX_SSID_BLACKLISTS = 7; - private static final int DEFAULT_NUM_DNS_PINGS = 5; // Multiple pings to detect setup issues - private static final int DEFAULT_MIN_DNS_RESPONSES = 1; + private static final long DEFAULT_ARP_CHECK_INTERVAL_MS = 2 * 60 * 1000; + private static final long DEFAULT_WALLED_GARDEN_INTERVAL_MS = 30 * 60 * 1000; - private static final int DEFAULT_DNS_PING_TIMEOUT_MS = 2000; + private static final int DEFAULT_NUM_ARP_PINGS = 5; + private static final int DEFAULT_MIN_ARP_RESPONSES = 1; - private static final long DEFAULT_BLACKLIST_FOLLOWUP_INTERVAL_MS = 15 * 1000; + private static final int DEFAULT_ARP_PING_TIMEOUT_MS = 100; // See http://go/clientsdns for usage approval private static final String DEFAULT_WALLED_GARDEN_URL = @@ -102,10 +123,6 @@ public class WifiWatchdogStateMachine extends StateMachine { */ private static final int WALLED_GARDEN_START_DELAY_MS = 3000; - private static final int DNS_INTRATEST_PING_INTERVAL_MS = 200; - /* With some router setups, it takes a few hunder milli-seconds before connection is active */ - private static final int DNS_START_DELAY_MS = 1000; - private static final int BASE = Protocol.BASE_WIFI_WATCHDOG; /** @@ -118,99 +135,76 @@ public class WifiWatchdogStateMachine extends StateMachine { * which has a non-null networkInfo object */ private static final int EVENT_NETWORK_STATE_CHANGE = BASE + 2; - /** - * Indicates the signal has changed. Passed with arg1 - * {@link #mNetEventCounter} and arg2 [raw signal strength] - */ + /* Passed with RSSI information */ private static final int EVENT_RSSI_CHANGE = BASE + 3; - private static final int EVENT_SCAN_RESULTS_AVAILABLE = BASE + 4; private static final int EVENT_WIFI_RADIO_STATE_CHANGE = BASE + 5; private static final int EVENT_WATCHDOG_SETTINGS_CHANGE = BASE + 6; - private static final int MESSAGE_HANDLE_WALLED_GARDEN = BASE + 100; - private static final int MESSAGE_HANDLE_BAD_AP = BASE + 101; - /** - * arg1 == mOnlineWatchState.checkCount - */ - private static final int MESSAGE_SINGLE_DNS_CHECK = BASE + 102; - private static final int MESSAGE_NETWORK_FOLLOWUP = BASE + 103; - private static final int MESSAGE_DELAYED_WALLED_GARDEN_CHECK = BASE + 104; + /* Internal messages */ + private static final int CMD_ARP_CHECK = BASE + 11; + private static final int CMD_DELAYED_WALLED_GARDEN_CHECK = BASE + 12; + + /* Notifications to WifiStateMachine */ + static final int POOR_LINK_DETECTED = BASE + 21; + static final int GOOD_LINK_DETECTED = BASE + 22; + + private static final int SINGLE_ARP_CHECK = 0; + private static final int FULL_ARP_CHECK = 1; private Context mContext; private ContentResolver mContentResolver; private WifiManager mWifiManager; - private DnsPinger mDnsPinger; private IntentFilter mIntentFilter; private BroadcastReceiver mBroadcastReceiver; + private AsyncChannel mWsmChannel = new AsyncChannel();; private DefaultState mDefaultState = new DefaultState(); private WatchdogDisabledState mWatchdogDisabledState = new WatchdogDisabledState(); private WatchdogEnabledState mWatchdogEnabledState = new WatchdogEnabledState(); private NotConnectedState mNotConnectedState = new NotConnectedState(); + private VerifyingLinkState mVerifyingLinkState = new VerifyingLinkState(); private ConnectedState mConnectedState = new ConnectedState(); - private DnsCheckingState mDnsCheckingState = new DnsCheckingState(); + private WalledGardenCheckState mWalledGardenCheckState = new WalledGardenCheckState(); + /* Online and watching link connectivity */ private OnlineWatchState mOnlineWatchState = new OnlineWatchState(); + /* Online and doing nothing */ private OnlineState mOnlineState = new OnlineState(); - private DnsCheckFailureState mDnsCheckFailureState = new DnsCheckFailureState(); - private DelayWalledGardenState mDelayWalledGardenState = new DelayWalledGardenState(); - private WalledGardenState mWalledGardenState = new WalledGardenState(); - private BlacklistedApState mBlacklistedApState = new BlacklistedApState(); - private long mDnsCheckShortIntervalMs; - private long mDnsCheckLongIntervalMs; + private int mArpToken = 0; + private long mArpCheckIntervalMs; private long mWalledGardenIntervalMs; - private int mMaxSsidBlacklists; - private int mNumDnsPings; - private int mMinDnsResponses; - private int mDnsPingTimeoutMs; - private long mBlacklistFollowupIntervalMs; + private int mNumArpPings; + private int mMinArpResponses; + private int mArpPingTimeoutMs; private boolean mPoorNetworkDetectionEnabled; private boolean mWalledGardenTestEnabled; private String mWalledGardenUrl; - private boolean mShowDisabledNotification; - /** - * The {@link WifiInfo} object passed to WWSM on network broadcasts - */ - private WifiInfo mConnectionInfo; - private int mNetEventCounter = 0; - - /** - * Currently maintained but not used, TODO - */ - private HashSet<String> mBssids = new HashSet<String>(); - private int mNumCheckFailures = 0; + private WifiInfo mWifiInfo; + private LinkProperties mLinkProperties; - private Long mLastWalledGardenCheckTime = null; + private long mLastWalledGardenCheckTime = 0; - /** - * This is set by the blacklisted state and reset when connected to a new AP. - * It triggers a disableNetwork call if a DNS check fails. - */ - public boolean mDisableAPNextFailure = false; private static boolean sWifiOnly = false; - private boolean mDisabledNotificationShown; private boolean mWalledGardenNotificationShown; - public boolean mHasConnectedWifiManager = false; /** * STATE MAP * Default * / \ - * Disabled Enabled - * / \ - * NotConnected Connected - * /---------\ - * (all other states) + * Disabled Enabled + * / \ \ + * NotConnected Verifying Connected + * /---------\ + * (all other states) */ private WifiWatchdogStateMachine(Context context) { super(TAG); mContext = context; mContentResolver = context.getContentResolver(); mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); - mDnsPinger = new DnsPinger(mContext, "WifiWatchdogStateMachine.DnsPinger", - this.getHandler().getLooper(), this.getHandler(), - ConnectivityManager.TYPE_WIFI); + mWsmChannel.connectSync(mContext, getHandler(), + mWifiManager.getWifiStateMachineMessenger()); setupNetworkReceiver(); @@ -221,16 +215,17 @@ public class WifiWatchdogStateMachine extends StateMachine { addState(mWatchdogDisabledState, mDefaultState); addState(mWatchdogEnabledState, mDefaultState); addState(mNotConnectedState, mWatchdogEnabledState); + addState(mVerifyingLinkState, mWatchdogEnabledState); addState(mConnectedState, mWatchdogEnabledState); - addState(mDnsCheckingState, mConnectedState); - addState(mDnsCheckFailureState, mConnectedState); - addState(mDelayWalledGardenState, mConnectedState); - addState(mWalledGardenState, mConnectedState); - addState(mBlacklistedApState, mConnectedState); + addState(mWalledGardenCheckState, mConnectedState); addState(mOnlineWatchState, mConnectedState); addState(mOnlineState, mConnectedState); - setInitialState(mWatchdogDisabledState); + if (isWatchdogEnabled()) { + setInitialState(mNotConnectedState); + } else { + setInitialState(mWatchdogDisabledState); + } updateSettings(); } @@ -242,19 +237,15 @@ public class WifiWatchdogStateMachine extends StateMachine { sWifiOnly = (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false); // Disable for wifi only devices. - if (Settings.Secure.getString(contentResolver, Settings.Secure.WIFI_WATCHDOG_ON) == null && - sWifiOnly) { + if (Settings.Secure.getString(contentResolver, Settings.Secure.WIFI_WATCHDOG_ON) == null + && sWifiOnly) { putSettingsBoolean(contentResolver, Settings.Secure.WIFI_WATCHDOG_ON, false); } WifiWatchdogStateMachine wwsm = new WifiWatchdogStateMachine(context); wwsm.start(); - wwsm.sendMessage(EVENT_WATCHDOG_TOGGLED); return wwsm; } - /** - * - */ private void setupNetworkReceiver() { mBroadcastReceiver = new BroadcastReceiver() { @Override @@ -263,10 +254,8 @@ public class WifiWatchdogStateMachine extends StateMachine { if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { sendMessage(EVENT_NETWORK_STATE_CHANGE, intent); } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) { - obtainMessage(EVENT_RSSI_CHANGE, mNetEventCounter, - intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200)).sendToTarget(); - } else if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) { - sendMessage(EVENT_SCAN_RESULTS_AVAILABLE); + obtainMessage(EVENT_RSSI_CHANGE, + intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200), 0).sendToTarget(); } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { sendMessage(EVENT_WIFI_RADIO_STATE_CHANGE, intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, @@ -279,7 +268,7 @@ public class WifiWatchdogStateMachine extends StateMachine { mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); mIntentFilter.addAction(WifiManager.RSSI_CHANGED_ACTION); - mIntentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); + mContext.registerReceiver(mBroadcastReceiver, mIntentFilter); } /** @@ -311,39 +300,29 @@ public class WifiWatchdogStateMachine extends StateMachine { mContext.getContentResolver().registerContentObserver( Settings.Secure.getUriFor( - Settings.Secure.WIFI_WATCHDOG_DNS_CHECK_SHORT_INTERVAL_MS), + Settings.Secure.WIFI_WATCHDOG_ARP_CHECK_INTERVAL_MS), false, contentObserver); mContext.getContentResolver().registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_DNS_CHECK_LONG_INTERVAL_MS), - false, contentObserver); - 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_MAX_SSID_BLACKLISTS), + Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_NUM_ARP_PINGS), false, contentObserver); mContext.getContentResolver().registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_NUM_DNS_PINGS), + Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_MIN_ARP_RESPONSES), false, contentObserver); mContext.getContentResolver().registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_MIN_DNS_RESPONSES), + Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_ARP_PING_TIMEOUT_MS), false, contentObserver); mContext.getContentResolver().registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_DNS_PING_TIMEOUT_MS), + Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED), false, contentObserver); mContext.getContentResolver().registerContentObserver( - Settings.Secure.getUriFor( - Settings.Secure.WIFI_WATCHDOG_BLACKLIST_FOLLOWUP_INTERVAL_MS), - 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); - mContext.getContentResolver().registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_SHOW_DISABLED_NETWORK_POPUP) - , false, contentObserver); } /** @@ -375,17 +354,20 @@ public class WifiWatchdogStateMachine extends StateMachine { } } - private boolean rssiStrengthAboveCutoff(int rssi) { - return WifiManager.calculateSignalLevel(rssi, WIFI_SIGNAL_LEVELS) > LOW_SIGNAL_CUTOFF; - } - public void dump(PrintWriter pw) { pw.print("WatchdogStatus: "); - pw.print("State " + getCurrentState()); - pw.println(", network [" + mConnectionInfo + "]"); - pw.print("checkFailures " + mNumCheckFailures); - pw.println(", bssids: " + mBssids); - pw.println("lastSingleCheck: " + mOnlineWatchState.lastCheckTime); + pw.print("State: " + getCurrentState()); + pw.println("mWifiInfo: [" + mWifiInfo + "]"); + pw.println("mLinkProperties: [" + mLinkProperties + "]"); + pw.println("mCurrentSignalLevel: [" + mCurrentSignalLevel + "]"); + pw.println("mArpCheckIntervalMs: [" + mArpCheckIntervalMs+ "]"); + pw.println("mWalledGardenIntervalMs: [" + mWalledGardenIntervalMs + "]"); + pw.println("mNumArpPings: [" + mNumArpPings + "]"); + pw.println("mMinArpResponses: [" + mMinArpResponses + "]"); + pw.println("mArpPingTimeoutMs: [" + mArpPingTimeoutMs + "]"); + pw.println("mPoorNetworkDetectionEnabled: [" + mPoorNetworkDetectionEnabled + "]"); + pw.println("mWalledGardenTestEnabled: [" + mWalledGardenTestEnabled + "]"); + pw.println("mWalledGardenUrl: [" + mWalledGardenUrl + "]"); } private boolean isWatchdogEnabled() { @@ -393,31 +375,22 @@ public class WifiWatchdogStateMachine extends StateMachine { } private void updateSettings() { - mDnsCheckShortIntervalMs = Secure.getLong(mContentResolver, - Secure.WIFI_WATCHDOG_DNS_CHECK_SHORT_INTERVAL_MS, - DEFAULT_DNS_CHECK_SHORT_INTERVAL_MS); - mDnsCheckLongIntervalMs = Secure.getLong(mContentResolver, - Secure.WIFI_WATCHDOG_DNS_CHECK_LONG_INTERVAL_MS, - DEFAULT_DNS_CHECK_LONG_INTERVAL_MS); - mMaxSsidBlacklists = Secure.getInt(mContentResolver, - Secure.WIFI_WATCHDOG_MAX_SSID_BLACKLISTS, - DEFAULT_MAX_SSID_BLACKLISTS); - mNumDnsPings = Secure.getInt(mContentResolver, - Secure.WIFI_WATCHDOG_NUM_DNS_PINGS, - DEFAULT_NUM_DNS_PINGS); - mMinDnsResponses = Secure.getInt(mContentResolver, - Secure.WIFI_WATCHDOG_MIN_DNS_RESPONSES, - DEFAULT_MIN_DNS_RESPONSES); - mDnsPingTimeoutMs = Secure.getInt(mContentResolver, - Secure.WIFI_WATCHDOG_DNS_PING_TIMEOUT_MS, - DEFAULT_DNS_PING_TIMEOUT_MS); - mBlacklistFollowupIntervalMs = Secure.getLong(mContentResolver, - Settings.Secure.WIFI_WATCHDOG_BLACKLIST_FOLLOWUP_INTERVAL_MS, - DEFAULT_BLACKLIST_FOLLOWUP_INTERVAL_MS); - //TODO: enable this by default after changing watchdog behavior - //Also, update settings description + if (DBG) log("Updating secure settings"); + + mArpCheckIntervalMs = Secure.getLong(mContentResolver, + Secure.WIFI_WATCHDOG_ARP_CHECK_INTERVAL_MS, + DEFAULT_ARP_CHECK_INTERVAL_MS); + mNumArpPings = Secure.getInt(mContentResolver, + Secure.WIFI_WATCHDOG_NUM_ARP_PINGS, + DEFAULT_NUM_ARP_PINGS); + mMinArpResponses = Secure.getInt(mContentResolver, + Secure.WIFI_WATCHDOG_MIN_ARP_RESPONSES, + DEFAULT_MIN_ARP_RESPONSES); + mArpPingTimeoutMs = Secure.getInt(mContentResolver, + Secure.WIFI_WATCHDOG_ARP_PING_TIMEOUT_MS, + DEFAULT_ARP_PING_TIMEOUT_MS); mPoorNetworkDetectionEnabled = getSettingsBoolean(mContentResolver, - Settings.Secure.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED, false); + Settings.Secure.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED, true); mWalledGardenTestEnabled = getSettingsBoolean(mContentResolver, Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED, true); mWalledGardenUrl = getSettingsStr(mContentResolver, @@ -426,69 +399,6 @@ public class WifiWatchdogStateMachine extends StateMachine { mWalledGardenIntervalMs = Secure.getLong(mContentResolver, Secure.WIFI_WATCHDOG_WALLED_GARDEN_INTERVAL_MS, DEFAULT_WALLED_GARDEN_INTERVAL_MS); - mShowDisabledNotification = getSettingsBoolean(mContentResolver, - Settings.Secure.WIFI_WATCHDOG_SHOW_DISABLED_NETWORK_POPUP, true); - } - - /** - * Helper to return wait time left given a min interval and last run - * - * @param interval minimum wait interval - * @param lastTime last time action was performed in - * SystemClock.elapsedRealtime(). Null if never. - * @return non negative time to wait - */ - private static long waitTime(long interval, Long lastTime) { - if (lastTime == null) - return 0; - long wait = interval + lastTime - SystemClock.elapsedRealtime(); - return wait > 0 ? wait : 0; - } - - private static String wifiInfoToStr(WifiInfo wifiInfo) { - if (wifiInfo == null) - return "null"; - return "(" + wifiInfo.getSSID() + ", " + wifiInfo.getBSSID() + ")"; - } - - /** - * Uses {@link #mConnectionInfo}. - */ - private void updateBssids() { - String curSsid = mConnectionInfo.getSSID(); - List<ScanResult> results = mWifiManager.getScanResults(); - int oldNumBssids = mBssids.size(); - - if (results == null) { - if (DBG) { - log("updateBssids: Got null scan results!"); - } - return; - } - - for (ScanResult result : results) { - if (result == null || result.SSID == null) { - if (DBG) { - log("Received invalid scan result: " + result); - } - continue; - } - if (curSsid.equals(result.SSID)) - mBssids.add(result.BSSID); - } - } - - private void resetWatchdogState() { - if (DBG) { - log("Resetting watchdog state..."); - } - mConnectionInfo = null; - mDisableAPNextFailure = false; - mLastWalledGardenCheckTime = null; - mNumCheckFailures = 0; - mBssids.clear(); - setDisabledNetworkNotificationVisible(false); - setWalledGardenNotificationVisible(false); } private void setWalledGardenNotificationVisible(boolean visible) { @@ -507,7 +417,7 @@ public class WifiWatchdogStateMachine extends StateMachine { CharSequence title = r.getString(R.string.wifi_available_sign_in, 0); CharSequence details = r.getString(R.string.wifi_available_sign_in_detailed, - mConnectionInfo.getSSID()); + mWifiInfo.getSSID()); Notification notification = new Notification(); notification.when = 0; @@ -524,41 +434,6 @@ public class WifiWatchdogStateMachine extends StateMachine { mWalledGardenNotificationShown = visible; } - private void setDisabledNetworkNotificationVisible(boolean visible) { - // If it should be hidden and it is already hidden, then noop - if (!visible && !mDisabledNotificationShown) { - return; - } - - Resources r = Resources.getSystem(); - NotificationManager notificationManager = (NotificationManager) mContext - .getSystemService(Context.NOTIFICATION_SERVICE); - - if (visible) { - CharSequence title = r.getText(R.string.wifi_watchdog_network_disabled); - String msg = mConnectionInfo.getSSID() + - r.getText(R.string.wifi_watchdog_network_disabled_detailed); - - Notification wifiDisabledWarning = new Notification.Builder(mContext) - .setSmallIcon(R.drawable.stat_sys_warning) - .setDefaults(Notification.DEFAULT_ALL) - .setTicker(title) - .setContentTitle(title) - .setContentText(msg) - .setContentIntent(PendingIntent.getActivity(mContext, 0, - new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0)) - .setWhen(System.currentTimeMillis()) - .setAutoCancel(true) - .getNotification(); - - notificationManager.notify(DISABLED_NETWORK_NOTIFICATION_ID, 1, wifiDisabledWarning); - } else { - notificationManager.cancel(DISABLED_NETWORK_NOTIFICATION_ID, 1); - } - mDisabledNotificationShown = visible; - } - class DefaultState extends State { @Override public boolean processMessage(Message msg) { @@ -568,11 +443,20 @@ public class WifiWatchdogStateMachine extends StateMachine { if (DBG) { log("Updating wifi-watchdog secure settings"); } - return HANDLED; - } - if (DBG) { - log("Caught message " + msg.what + " in state " + - getCurrentState().getName()); + break; + case EVENT_RSSI_CHANGE: + mCurrentSignalLevel = WifiManager.calculateSignalLevel(msg.arg1, + WifiManager.RSSI_LEVELS); + break; + case EVENT_WIFI_RADIO_STATE_CHANGE: + case EVENT_NETWORK_STATE_CHANGE: + case CMD_ARP_CHECK: + case CMD_DELAYED_WALLED_GARDEN_CHECK: + //ignore + break; + default: + log("Unhandled message " + msg + " in state " + getCurrentState().getName()); + break; } return HANDLED; } @@ -586,6 +470,20 @@ public class WifiWatchdogStateMachine extends StateMachine { if (isWatchdogEnabled()) transitionTo(mNotConnectedState); return HANDLED; + case EVENT_NETWORK_STATE_CHANGE: + Intent intent = (Intent) msg.obj; + NetworkInfo networkInfo = (NetworkInfo) + intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); + + switch (networkInfo.getDetailedState()) { + case VERIFYING_POOR_LINK: + if (DBG) log("Watchdog disabled, verify link"); + mWsmChannel.sendMessage(GOOD_LINK_DETECTED); + break; + default: + break; + } + break; } return NOT_HANDLED; } @@ -594,10 +492,8 @@ public class WifiWatchdogStateMachine extends StateMachine { class WatchdogEnabledState extends State { @Override public void enter() { - resetWatchdogState(); - mContext.registerReceiver(mBroadcastReceiver, mIntentFilter); if (DBG) log("WifiWatchdogService enabled"); - } + } @Override public boolean processMessage(Message msg) { @@ -605,77 +501,57 @@ public class WifiWatchdogStateMachine extends StateMachine { case EVENT_WATCHDOG_TOGGLED: if (!isWatchdogEnabled()) transitionTo(mWatchdogDisabledState); - return HANDLED; + break; case EVENT_NETWORK_STATE_CHANGE: - Intent stateChangeIntent = (Intent) msg.obj; + Intent intent = (Intent) msg.obj; NetworkInfo networkInfo = (NetworkInfo) - stateChangeIntent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); - - setDisabledNetworkNotificationVisible(false); - setWalledGardenNotificationVisible(false); - switch (networkInfo.getState()) { - case CONNECTED: - WifiInfo wifiInfo = (WifiInfo) - stateChangeIntent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO); - if (wifiInfo == null) { - loge("Connected --> WifiInfo object null!"); - return HANDLED; - } - - if (wifiInfo.getSSID() == null || wifiInfo.getBSSID() == null) { - loge("Received wifiInfo object with null elts: " - + wifiInfoToStr(wifiInfo)); - return HANDLED; - } - - initConnection(wifiInfo); - mConnectionInfo = wifiInfo; - mNetEventCounter++; + intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); + + switch (networkInfo.getDetailedState()) { + case VERIFYING_POOR_LINK: + mLinkProperties = (LinkProperties) intent.getParcelableExtra( + WifiManager.EXTRA_LINK_PROPERTIES); + mWifiInfo = (WifiInfo) intent.getParcelableExtra( + WifiManager.EXTRA_WIFI_INFO); if (mPoorNetworkDetectionEnabled) { - updateBssids(); - transitionTo(mDnsCheckingState); + if (mWifiInfo == null) { + log("Ignoring link verification, mWifiInfo is NULL"); + mWsmChannel.sendMessage(GOOD_LINK_DETECTED); + } else { + transitionTo(mVerifyingLinkState); + } } else { - transitionTo(mDelayWalledGardenState); + mWsmChannel.sendMessage(GOOD_LINK_DETECTED); + } + break; + case CONNECTED: + if (shouldCheckWalledGarden()) { + transitionTo(mWalledGardenCheckState); + } else { + transitionTo(mOnlineWatchState); } break; default: - mNetEventCounter++; transitionTo(mNotConnectedState); break; } - return HANDLED; + break; case EVENT_WIFI_RADIO_STATE_CHANGE: if ((Integer) msg.obj == WifiManager.WIFI_STATE_DISABLING) { if (DBG) log("WifiStateDisabling -- Resetting WatchdogState"); - resetWatchdogState(); - mNetEventCounter++; transitionTo(mNotConnectedState); } - return HANDLED; - } - - return NOT_HANDLED; - } - - /** - * @param wifiInfo Info object with non-null ssid and bssid - */ - private void initConnection(WifiInfo wifiInfo) { - if (DBG) { - log("Connected:: old " + wifiInfoToStr(mConnectionInfo) + - " ==> new " + wifiInfoToStr(wifiInfo)); + break; + default: + return NOT_HANDLED; } - if (mConnectionInfo == null || !wifiInfo.getSSID().equals(mConnectionInfo.getSSID())) { - resetWatchdogState(); - } else if (!wifiInfo.getBSSID().equals(mConnectionInfo.getBSSID())) { - mDisableAPNextFailure = false; - } + setWalledGardenNotificationVisible(false); + return HANDLED; } @Override public void exit() { - mContext.unregisterReceiver(mBroadcastReceiver); if (DBG) log("WifiWatchdogService disabled"); } } @@ -683,423 +559,240 @@ public class WifiWatchdogStateMachine extends StateMachine { class NotConnectedState extends State { } - class ConnectedState extends State { + class VerifyingLinkState extends State { + @Override + public void enter() { + if (DBG) log(getName() + "\n"); + //Treat entry as an rssi change + handleRssiChange(); + } + + private void handleRssiChange() { + if (mCurrentSignalLevel <= RSSI_LEVEL_CUTOFF) { + //stay here + if (DBG) log("enter VerifyingLinkState, stay level: " + mCurrentSignalLevel); + } else { + if (DBG) log("enter VerifyingLinkState, arp check level: " + mCurrentSignalLevel); + sendMessage(obtainMessage(CMD_ARP_CHECK, ++mArpToken, 0)); + } + } + @Override public boolean processMessage(Message msg) { switch (msg.what) { - case EVENT_SCAN_RESULTS_AVAILABLE: - if (mPoorNetworkDetectionEnabled) { - updateBssids(); - } - return HANDLED; case EVENT_WATCHDOG_SETTINGS_CHANGE: updateSettings(); - if (mPoorNetworkDetectionEnabled) { - transitionTo(mOnlineWatchState); - } else { - transitionTo(mOnlineState); + if (!mPoorNetworkDetectionEnabled) { + mWsmChannel.sendMessage(GOOD_LINK_DETECTED); } - return HANDLED; + break; + case EVENT_RSSI_CHANGE: + int signalLevel = WifiManager.calculateSignalLevel(msg.arg1, + WifiManager.RSSI_LEVELS); + if (DBG) log("RSSI change old: " + mCurrentSignalLevel + "new: " + signalLevel); + mCurrentSignalLevel = signalLevel; + + handleRssiChange(); + break; + case CMD_ARP_CHECK: + if (msg.arg1 == mArpToken) { + if (doArpTest(FULL_ARP_CHECK) == true) { + if (DBG) log("Notify link is good " + mCurrentSignalLevel); + mWsmChannel.sendMessage(GOOD_LINK_DETECTED); + } else { + if (DBG) log("Continue ARP check, rssi level: " + mCurrentSignalLevel); + sendMessageDelayed(obtainMessage(CMD_ARP_CHECK, ++mArpToken, 0), + mArpCheckIntervalMs); + } + } + break; + default: + return NOT_HANDLED; } - return NOT_HANDLED; + return HANDLED; } } - class DnsCheckingState extends State { - List<InetAddress> mDnsList; - int[] dnsCheckSuccesses; - String dnsCheckLogStr; - String[] dnsResponseStrs; - /** Keeps track of active dns pings. Map is from pingID to index in mDnsList */ - HashMap<Integer, Integer> idDnsMap = new HashMap<Integer, Integer>(); - + class ConnectedState extends State { @Override public void enter() { - mDnsList = mDnsPinger.getDnsList(); - int numDnses = mDnsList.size(); - dnsCheckSuccesses = new int[numDnses]; - dnsResponseStrs = new String[numDnses]; - for (int i = 0; i < numDnses; i++) - dnsResponseStrs[i] = ""; - - if (DBG) { - dnsCheckLogStr = String.format("Pinging %s on ssid [%s]: ", - mDnsList, mConnectionInfo.getSSID()); - log(dnsCheckLogStr); - } - - idDnsMap.clear(); - for (int i=0; i < mNumDnsPings; i++) { - for (int j = 0; j < numDnses; j++) { - idDnsMap.put(mDnsPinger.pingDnsAsync(mDnsList.get(j), mDnsPingTimeoutMs, - DNS_START_DELAY_MS + DNS_INTRATEST_PING_INTERVAL_MS * i), j); - } - } + if (DBG) log(getName() + "\n"); } - @Override public boolean processMessage(Message msg) { - if (msg.what != DnsPinger.DNS_PING_RESULT) { - return NOT_HANDLED; - } - - int pingID = msg.arg1; - int pingResponseTime = msg.arg2; - - Integer dnsServerId = idDnsMap.get(pingID); - if (dnsServerId == null) { - loge("Received a Dns response with unknown ID!"); - return HANDLED; - } - - idDnsMap.remove(pingID); - if (pingResponseTime >= 0) - dnsCheckSuccesses[dnsServerId]++; - - if (DBG) { - if (pingResponseTime >= 0) { - dnsResponseStrs[dnsServerId] += "|" + pingResponseTime; - } else { - dnsResponseStrs[dnsServerId] += "|x"; - } - } - - /** - * After a full ping count, if we have more responses than this - * cutoff, the outcome is success; else it is 'failure'. - */ - - /** - * Our final success count will be at least this big, so we're - * guaranteed to succeed. - */ - if (dnsCheckSuccesses[dnsServerId] >= mMinDnsResponses) { - // DNS CHECKS OK, NOW WALLED GARDEN - if (DBG) { - log(makeLogString() + " SUCCESS"); - } + switch (msg.what) { + case EVENT_WATCHDOG_SETTINGS_CHANGE: + updateSettings(); + //STOPSHIP: Remove this at ship + DBG = true; + if (DBG) log("Updated secure settings and turned debug on"); - if (!shouldCheckWalledGarden()) { - transitionTo(mOnlineWatchState); + if (mPoorNetworkDetectionEnabled) { + transitionTo(mOnlineWatchState); + } else { + transitionTo(mOnlineState); + } return HANDLED; - } - - transitionTo(mDelayWalledGardenState); - return HANDLED; - } - - if (idDnsMap.isEmpty()) { - if (DBG) { - log(makeLogString() + " FAILURE"); - } - transitionTo(mDnsCheckFailureState); - return HANDLED; } - - return HANDLED; - } - - private String makeLogString() { - String logStr = dnsCheckLogStr; - for (String respStr : dnsResponseStrs) - logStr += " [" + respStr + "]"; - return logStr; - } - - @Override - public void exit() { - mDnsPinger.cancelPings(); - } - - private boolean shouldCheckWalledGarden() { - if (!mWalledGardenTestEnabled) { - if (DBG) - log("Skipping walled garden check - disabled"); - return false; - } - long waitTime = waitTime(mWalledGardenIntervalMs, - mLastWalledGardenCheckTime); - if (waitTime > 0) { - if (DBG) { - log("Skipping walled garden check - wait " + - waitTime + " ms."); - } - return false; - } - return true; + return NOT_HANDLED; } } - class DelayWalledGardenState extends State { + class WalledGardenCheckState extends State { + private int mWalledGardenToken = 0; @Override public void enter() { - sendMessageDelayed(MESSAGE_DELAYED_WALLED_GARDEN_CHECK, WALLED_GARDEN_START_DELAY_MS); + if (DBG) log(getName() + "\n"); + sendMessageDelayed(obtainMessage(CMD_DELAYED_WALLED_GARDEN_CHECK, + ++mWalledGardenToken, 0), WALLED_GARDEN_START_DELAY_MS); } @Override public boolean processMessage(Message msg) { switch (msg.what) { - case MESSAGE_DELAYED_WALLED_GARDEN_CHECK: - mLastWalledGardenCheckTime = SystemClock.elapsedRealtime(); - if (isWalledGardenConnection()) { - if (DBG) log("Walled garden test complete - walled garden detected"); - transitionTo(mWalledGardenState); - } else { - if (DBG) log("Walled garden test complete - online"); - if (mPoorNetworkDetectionEnabled) { - transitionTo(mOnlineWatchState); - } else { - transitionTo(mOnlineState); + case CMD_DELAYED_WALLED_GARDEN_CHECK: + if (msg.arg1 == mWalledGardenToken) { + mLastWalledGardenCheckTime = SystemClock.elapsedRealtime(); + if (isWalledGardenConnection()) { + if (DBG) log("Walled garden detected"); + setWalledGardenNotificationVisible(true); } + transitionTo(mOnlineWatchState); } - return HANDLED; + break; default: return NOT_HANDLED; } + return HANDLED; } } class OnlineWatchState extends State { - /** - * Signals a short-wait message is enqueued for the current 'guard' counter - */ - boolean unstableSignalChecks = false; - - /** - * The signal is unstable. We should enqueue a short-wait check, if one is enqueued - * already - */ - boolean signalUnstable = false; - - /** - * A monotonic counter to ensure that at most one check message will be processed from any - * set of check messages currently enqueued. Avoids duplicate checks when a low-signal - * event is observed. - */ - int checkGuard = 0; - Long lastCheckTime = null; - - /** Keeps track of dns pings. Map is from pingID to InetAddress used for ping */ - HashMap<Integer, InetAddress> pingInfoMap = new HashMap<Integer, InetAddress>(); - - @Override public void enter() { - lastCheckTime = SystemClock.elapsedRealtime(); - signalUnstable = false; - checkGuard++; - unstableSignalChecks = false; - pingInfoMap.clear(); - triggerSingleDnsCheck(); + if (DBG) log(getName() + "\n"); + if (mPoorNetworkDetectionEnabled) { + //Treat entry as an rssi change + handleRssiChange(); + } else { + transitionTo(mOnlineState); + } + } + + private void handleRssiChange() { + if (mCurrentSignalLevel <= RSSI_LEVEL_CUTOFF) { + if (DBG) log("Transition out, below cut off level: " + mCurrentSignalLevel); + mWsmChannel.sendMessage(POOR_LINK_DETECTED); + } else if (mCurrentSignalLevel <= RSSI_LEVEL_MONITOR) { + if (DBG) log("Start monitoring, level: " + mCurrentSignalLevel); + sendMessage(obtainMessage(CMD_ARP_CHECK, ++mArpToken, 0)); + } } @Override public boolean processMessage(Message msg) { switch (msg.what) { case EVENT_RSSI_CHANGE: - if (msg.arg1 != mNetEventCounter) { - if (DBG) { - log("Rssi change message out of sync, ignoring"); - } - return HANDLED; - } - int newRssi = msg.arg2; - signalUnstable = !rssiStrengthAboveCutoff(newRssi); - if (DBG) { - log("OnlineWatchState:: new rssi " + newRssi + " --> level " + - WifiManager.calculateSignalLevel(newRssi, WIFI_SIGNAL_LEVELS)); - } - - if (signalUnstable && !unstableSignalChecks) { - if (DBG) { - log("Sending triggered check msg"); - } - triggerSingleDnsCheck(); - } - return HANDLED; - case MESSAGE_SINGLE_DNS_CHECK: - if (msg.arg1 != checkGuard) { - if (DBG) { - log("Single check msg out of sync, ignoring."); - } - return HANDLED; - } - lastCheckTime = SystemClock.elapsedRealtime(); - pingInfoMap.clear(); - for (InetAddress curDns: mDnsPinger.getDnsList()) { - pingInfoMap.put(mDnsPinger.pingDnsAsync(curDns, mDnsPingTimeoutMs, 0), - curDns); - } - return HANDLED; - case DnsPinger.DNS_PING_RESULT: - InetAddress curDnsServer = pingInfoMap.get(msg.arg1); - if (curDnsServer == null) { - return HANDLED; - } - pingInfoMap.remove(msg.arg1); - int responseTime = msg.arg2; - if (responseTime >= 0) { - if (DBG) { - log("Single DNS ping OK. Response time: " - + responseTime + " from DNS " + curDnsServer); + int signalLevel = WifiManager.calculateSignalLevel(msg.arg1, + WifiManager.RSSI_LEVELS); + if (DBG) log("RSSI change old: " + mCurrentSignalLevel + "new: " + signalLevel); + mCurrentSignalLevel = signalLevel; + + handleRssiChange(); + + break; + case CMD_ARP_CHECK: + if (msg.arg1 == mArpToken) { + if (doArpTest(SINGLE_ARP_CHECK) != true) { + if (DBG) log("single ARP fail, full ARP check"); + //do a full test + if (doArpTest(FULL_ARP_CHECK) != true) { + if (DBG) log("notify full ARP fail, level: " + mCurrentSignalLevel); + mWsmChannel.sendMessage(POOR_LINK_DETECTED); + } } - pingInfoMap.clear(); - checkGuard++; - unstableSignalChecks = false; - triggerSingleDnsCheck(); - } else { - if (pingInfoMap.isEmpty()) { - if (DBG) { - log("Single dns ping failure. All dns servers failed, " - + "starting full checks."); - } - transitionTo(mDnsCheckingState); + if (mCurrentSignalLevel <= RSSI_LEVEL_MONITOR) { + if (DBG) log("Continue ARP check, rssi level: " + mCurrentSignalLevel); + sendMessageDelayed(obtainMessage(CMD_ARP_CHECK, ++mArpToken, 0), + mArpCheckIntervalMs); } } - return HANDLED; - } - return NOT_HANDLED; - } - - @Override - public void exit() { - mDnsPinger.cancelPings(); - } - - /** - * Times a dns check with an interval based on {@link #signalUnstable} - */ - private void triggerSingleDnsCheck() { - long waitInterval; - if (signalUnstable) { - waitInterval = mDnsCheckShortIntervalMs; - unstableSignalChecks = true; - } else { - waitInterval = mDnsCheckLongIntervalMs; + break; + default: + return NOT_HANDLED; } - sendMessageDelayed(obtainMessage(MESSAGE_SINGLE_DNS_CHECK, checkGuard, 0), - waitTime(waitInterval, lastCheckTime)); + return HANDLED; } } - /* Child state of ConnectedState indicating that we are online * and there is nothing to do */ class OnlineState extends State { } - class DnsCheckFailureState extends State { - - @Override - public void enter() { - mNumCheckFailures++; - obtainMessage(MESSAGE_HANDLE_BAD_AP, mNetEventCounter, 0).sendToTarget(); + private boolean shouldCheckWalledGarden() { + if (!mWalledGardenTestEnabled) { + if (DBG) log("Skipping walled garden check - disabled"); + return false; } - @Override - public boolean processMessage(Message msg) { - if (msg.what != MESSAGE_HANDLE_BAD_AP) { - return NOT_HANDLED; - } - - if (msg.arg1 != mNetEventCounter) { - if (DBG) { - log("Msg out of sync, ignoring..."); - } - return HANDLED; - } + long waitTime = (mWalledGardenIntervalMs + mLastWalledGardenCheckTime) + - SystemClock.elapsedRealtime(); - if (mDisableAPNextFailure || mNumCheckFailures >= mBssids.size() - || mNumCheckFailures >= mMaxSsidBlacklists) { - if (sWifiOnly) { - log("Would disable bad network, but device has no mobile data!" + - " Going idle..."); - // This state should be called idle -- will be changing flow. - transitionTo(mNotConnectedState); - return HANDLED; - } - - // TODO : Unban networks if they had low signal ? - log("Disabling current SSID " + wifiInfoToStr(mConnectionInfo) - + ". " + "numCheckFailures " + mNumCheckFailures - + ", numAPs " + mBssids.size()); - int networkId = mConnectionInfo.getNetworkId(); - if (!mHasConnectedWifiManager) { - mWifiManager.asyncConnect(mContext, getHandler()); - mHasConnectedWifiManager = true; - } - mWifiManager.disableNetwork(networkId, WifiConfiguration.DISABLED_DNS_FAILURE); - if (mShowDisabledNotification) { - setDisabledNetworkNotificationVisible(true); - } - transitionTo(mNotConnectedState); - } else { - log("Blacklisting current BSSID. " + wifiInfoToStr(mConnectionInfo) - + "numCheckFailures " + mNumCheckFailures + ", numAPs " + mBssids.size()); - - mWifiManager.addToBlacklist(mConnectionInfo.getBSSID()); - mWifiManager.reassociate(); - transitionTo(mBlacklistedApState); + if (mLastWalledGardenCheckTime != 0 && waitTime > 0) { + if (DBG) { + log("Skipping walled garden check - wait " + + waitTime + " ms."); } - return HANDLED; + return false; } + return true; } - class WalledGardenState extends State { - @Override - public void enter() { - obtainMessage(MESSAGE_HANDLE_WALLED_GARDEN, mNetEventCounter, 0).sendToTarget(); - } + private boolean doArpTest(int type) { + boolean success; - @Override - public boolean processMessage(Message msg) { - if (msg.what != MESSAGE_HANDLE_WALLED_GARDEN) { - return NOT_HANDLED; - } + String iface = mLinkProperties.getInterfaceName(); + String mac = mWifiInfo.getMacAddress(); + InetAddress inetAddress = null; + InetAddress gateway = null; - if (msg.arg1 != mNetEventCounter) { - if (DBG) { - log("WalledGardenState::Msg out of sync, ignoring..."); - } - return HANDLED; - } - setWalledGardenNotificationVisible(true); - if (mPoorNetworkDetectionEnabled) { - transitionTo(mOnlineWatchState); - } else { - transitionTo(mOnlineState); - } - return HANDLED; + for (LinkAddress la : mLinkProperties.getLinkAddresses()) { + inetAddress = la.getAddress(); + break; } - } - class BlacklistedApState extends State { - @Override - public void enter() { - mDisableAPNextFailure = true; - sendMessageDelayed(obtainMessage(MESSAGE_NETWORK_FOLLOWUP, mNetEventCounter, 0), - mBlacklistFollowupIntervalMs); + for (RouteInfo route : mLinkProperties.getRoutes()) { + gateway = route.getGateway(); + break; } - @Override - public boolean processMessage(Message msg) { - if (msg.what != MESSAGE_NETWORK_FOLLOWUP) { - return NOT_HANDLED; - } + if (DBG) log("ARP " + iface + "addr: " + inetAddress + "mac: " + mac + "gw: " + gateway); - if (msg.arg1 != mNetEventCounter) { - if (DBG) { - log("BlacklistedApState::Msg out of sync, ignoring..."); + try { + ArpPeer peer = new ArpPeer(iface, inetAddress, mac, gateway); + if (type == SINGLE_ARP_CHECK) { + success = (peer.doArp(mArpPingTimeoutMs) != null); + if (DBG) log("single ARP test result: " + success); + } else { + int responses = 0; + for (int i=0; i < mNumArpPings; i++) { + if(peer.doArp(mArpPingTimeoutMs) != null) responses++; } - return HANDLED; + if (DBG) log("full ARP test result: " + responses + "/" + mNumArpPings); + success = (responses >= mMinArpResponses); } - - transitionTo(mDnsCheckingState); - return HANDLED; + peer.close(); + } catch (SocketException se) { + //Consider an Arp socket creation issue as a successful Arp + //test to avoid any wifi connectivity issues + loge("ARP test initiation failure: " + se); + success = true; } - } + return success; + } /** * Convenience function for retrieving a single secure settings value |