diff options
Diffstat (limited to 'wifi/java')
| -rw-r--r-- | wifi/java/android/net/wifi/IWifiManager.aidl | 2 | ||||
| -rw-r--r-- | wifi/java/android/net/wifi/RssiPacketCountInfo.java | 71 | ||||
| -rw-r--r-- | wifi/java/android/net/wifi/ScanResult.java | 30 | ||||
| -rw-r--r-- | wifi/java/android/net/wifi/WifiManager.java | 392 | ||||
| -rw-r--r-- | wifi/java/android/net/wifi/WifiNative.java | 24 | ||||
| -rw-r--r-- | wifi/java/android/net/wifi/WifiStateMachine.java | 335 | ||||
| -rw-r--r-- | wifi/java/android/net/wifi/WifiStateTracker.java | 14 | ||||
| -rw-r--r-- | wifi/java/android/net/wifi/WifiWatchdogStateMachine.java | 1233 | ||||
| -rw-r--r-- | wifi/java/android/net/wifi/p2p/WifiP2pService.java | 5 |
9 files changed, 1293 insertions, 813 deletions
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index 6b08074..55de065 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -106,5 +106,7 @@ interface IWifiManager Messenger getWifiStateMachineMessenger(); String getConfigFile(); + + void captivePortalCheckComplete(); } diff --git a/wifi/java/android/net/wifi/RssiPacketCountInfo.java b/wifi/java/android/net/wifi/RssiPacketCountInfo.java new file mode 100644 index 0000000..f549e1d --- /dev/null +++ b/wifi/java/android/net/wifi/RssiPacketCountInfo.java @@ -0,0 +1,71 @@ +/* + * 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.wifi; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Bundle of RSSI and packet count information, for WiFi watchdog + * + * @see WifiWatchdogStateMachine + * + * @hide + */ +public class RssiPacketCountInfo implements Parcelable { + + public int rssi; + + public int txgood; + + public int txbad; + + public RssiPacketCountInfo() { + rssi = txgood = txbad = 0; + } + + private RssiPacketCountInfo(Parcel in) { + rssi = in.readInt(); + txgood = in.readInt(); + txbad = in.readInt(); + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(rssi); + out.writeInt(txgood); + out.writeInt(txbad); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<RssiPacketCountInfo> CREATOR = + new Parcelable.Creator<RssiPacketCountInfo>() { + @Override + public RssiPacketCountInfo createFromParcel(Parcel in) { + return new RssiPacketCountInfo(in); + } + + @Override + public RssiPacketCountInfo[] newArray(int size) { + return new RssiPacketCountInfo[size]; + } + }; +} diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java index 32261de..3e20756 100644 --- a/wifi/java/android/net/wifi/ScanResult.java +++ b/wifi/java/android/net/wifi/ScanResult.java @@ -47,19 +47,37 @@ public class ScanResult implements Parcelable { public int frequency; /** + * Time Synchronization Function (tsf) timestamp in microseconds when + * this result was last seen. + */ + public long timestamp; + + /** * We'd like to obtain the following attributes, * but they are not reported via the socket * interface, even though they are known * internally by wpa_supplicant. * {@hide} */ - public ScanResult(String SSID, String BSSID, String caps, int level, int frequency) { + public ScanResult(String SSID, String BSSID, String caps, int level, int frequency, long tsf) { this.SSID = SSID; this.BSSID = BSSID; this.capabilities = caps; this.level = level; this.frequency = frequency; - //networkConfig = null; + this.timestamp = tsf; + } + + /** copy constructor {@hide} */ + public ScanResult(ScanResult source) { + if (source != null) { + SSID = source.SSID; + BSSID = source.BSSID; + capabilities = source.capabilities; + level = source.level; + frequency = source.frequency; + timestamp = source.timestamp; + } } @Override @@ -76,7 +94,9 @@ public class ScanResult implements Parcelable { append(", level: "). append(level). append(", frequency: "). - append(frequency); + append(frequency). + append(", timestamp: "). + append(timestamp); return sb.toString(); } @@ -93,6 +113,7 @@ public class ScanResult implements Parcelable { dest.writeString(capabilities); dest.writeInt(level); dest.writeInt(frequency); + dest.writeLong(timestamp); } /** Implement the Parcelable interface {@hide} */ @@ -104,7 +125,8 @@ public class ScanResult implements Parcelable { in.readString(), in.readString(), in.readInt(), - in.readInt() + in.readInt(), + in.readLong() ); } diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 36f38f9..aa59158 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -23,13 +23,17 @@ import android.net.DhcpInfo; import android.os.Binder; import android.os.IBinder; import android.os.Handler; +import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.WorkSource; import android.os.Messenger; +import android.util.Log; import android.util.SparseArray; +import java.util.concurrent.CountDownLatch; + import com.android.internal.util.AsyncChannel; import com.android.internal.util.Protocol; @@ -58,6 +62,7 @@ import java.util.List; */ public class WifiManager { + private static final String TAG = "WifiManager"; // Supplicant error codes: /** * The error code if there was a problem authenticating. @@ -481,9 +486,6 @@ public class WifiManager { /** @hide */ public static final int DATA_ACTIVITY_INOUT = 0x03; - IWifiManager mService; - Handler mHandler; - /* Maximum number of active locks we allow. * This limit was added to prevent apps from creating a ridiculous number * of locks and crashing the system by overflowing the global ref table. @@ -493,19 +495,33 @@ public class WifiManager { /* Number of currently active WifiLocks and MulticastLocks */ private int mActiveLockCount; + private Context mContext; + IWifiManager mService; + + private static final int INVALID_KEY = 0; + private int mListenerKey = 1; + private final SparseArray mListenerMap = new SparseArray(); + private final Object mListenerMapLock = new Object(); + + private AsyncChannel mAsyncChannel = new AsyncChannel(); + private ServiceHandler mHandler; + private Messenger mWifiServiceMessenger; + private final CountDownLatch mConnected = new CountDownLatch(1); + /** * Create a new WifiManager instance. * Applications will almost always want to use * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve * the standard {@link android.content.Context#WIFI_SERVICE Context.WIFI_SERVICE}. + * @param context the application context * @param service the Binder interface - * @param handler target for messages * @hide - hide this because it takes in a parameter of type IWifiManager, which * is a system private class. */ - public WifiManager(IWifiManager service, Handler handler) { + public WifiManager(Context context, IWifiManager service) { + mContext = context; mService = service; - mHandler = handler; + init(); } /** @@ -890,6 +906,17 @@ public class WifiManager { } /** + * Return TX packet counter, for CTS test of WiFi watchdog. + * @param listener is the interface to receive result + * + * @hide for CTS test only + */ + public void getTxPacketCount(TxPacketCountListener listener) { + validateChannel(); + mAsyncChannel.sendMessage(RSSI_PKTCNT_FETCH, 0, putListener(listener)); + } + + /** * Calculates the level of the signal. This should be used any time a signal * is being shown. * @@ -1127,11 +1154,18 @@ public class WifiManager { /** @hide */ public static final int DISABLE_NETWORK_SUCCEEDED = BASE + 19; + /** @hide */ + public static final int RSSI_PKTCNT_FETCH = BASE + 20; + /** @hide */ + public static final int RSSI_PKTCNT_FETCH_SUCCEEDED = BASE + 21; + /** @hide */ + public static final int RSSI_PKTCNT_FETCH_FAILED = BASE + 22; + /* For system use only */ /** @hide */ - public static final int ENABLE_TRAFFIC_STATS_POLL = BASE + 21; + public static final int ENABLE_TRAFFIC_STATS_POLL = BASE + 31; /** @hide */ - public static final int TRAFFIC_STATS_POLL = BASE + 22; + public static final int TRAFFIC_STATS_POLL = BASE + 32; /** @@ -1168,15 +1202,6 @@ public class WifiManager { /** WPS timed out {@hide} */ public static final int WPS_TIMED_OUT = 7; - /** Interface for callback invocation when framework channel is lost {@hide} */ - public interface ChannelListener { - /** - * The channel to the framework has been disconnected. - * Application could try re-initializing using {@link #initialize} - */ - public void onChannelDisconnected(); - } - /** Interface for callback invocation on an application action {@hide} */ public interface ActionListener { /** The operation succeeded */ @@ -1205,134 +1230,155 @@ public class WifiManager { public void onFailure(int reason); } - /** - * A channel that connects the application to the Wifi framework. - * Most operations require a Channel as an argument. An instance of Channel is obtained - * by doing a call on {@link #initialize} - * @hide - */ - public static class Channel { - Channel(Looper looper, ChannelListener l) { - mAsyncChannel = new AsyncChannel(); - mHandler = new WifiHandler(looper); - mChannelListener = l; + /** Interface for callback invocation on a TX packet count poll action {@hide} */ + public interface TxPacketCountListener { + /** + * The operation succeeded + * @param count TX packet counter + */ + public void onSuccess(int count); + /** + * The operation failed + * @param reason The reason for failure could be one of + * {@link #ERROR}, {@link #IN_PROGRESS} or {@link #BUSY} + */ + public void onFailure(int reason); + } + + private class ServiceHandler extends Handler { + ServiceHandler(Looper looper) { + super(looper); } - private ChannelListener mChannelListener; - private SparseArray<Object> mListenerMap = new SparseArray<Object>(); - private Object mListenerMapLock = new Object(); - private int mListenerKey = 0; - private static final int INVALID_KEY = -1; - - AsyncChannel mAsyncChannel; - WifiHandler mHandler; - class WifiHandler extends Handler { - WifiHandler(Looper looper) { - super(looper); - } - @Override - public void handleMessage(Message message) { - Object listener = removeListener(message.arg2); - switch (message.what) { - case AsyncChannel.CMD_CHANNEL_DISCONNECTED: - if (mChannelListener != null) { - mChannelListener.onChannelDisconnected(); - mChannelListener = null; - } - break; - /* ActionListeners grouped together */ - case WifiManager.CONNECT_NETWORK_FAILED: - case WifiManager.FORGET_NETWORK_FAILED: - case WifiManager.SAVE_NETWORK_FAILED: - case WifiManager.CANCEL_WPS_FAILED: - case WifiManager.DISABLE_NETWORK_FAILED: - if (listener != null) { - ((ActionListener) listener).onFailure(message.arg1); - } - break; - /* ActionListeners grouped together */ - case WifiManager.CONNECT_NETWORK_SUCCEEDED: - case WifiManager.FORGET_NETWORK_SUCCEEDED: - case WifiManager.SAVE_NETWORK_SUCCEEDED: - case WifiManager.CANCEL_WPS_SUCCEDED: - case WifiManager.DISABLE_NETWORK_SUCCEEDED: - if (listener != null) { - ((ActionListener) listener).onSuccess(); - } - break; - case WifiManager.START_WPS_SUCCEEDED: - if (listener != null) { - WpsResult result = (WpsResult) message.obj; - ((WpsListener) listener).onStartSuccess(result.pin); - //Listener needs to stay until completion or failure - synchronized(mListenerMapLock) { - mListenerMap.put(message.arg2, listener); - } - } - break; - case WifiManager.WPS_COMPLETED: - if (listener != null) { - ((WpsListener) listener).onCompletion(); - } - break; - case WifiManager.WPS_FAILED: - if (listener != null) { - ((WpsListener) listener).onFailure(message.arg1); + @Override + public void handleMessage(Message message) { + Object listener = removeListener(message.arg2); + switch (message.what) { + case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: + if (message.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { + mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION); + } else { + Log.e(TAG, "Failed to set up channel connection"); + // This will cause all further async API calls on the WifiManager + // to fail and throw an exception + mAsyncChannel = null; + } + mConnected.countDown(); + break; + case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED: + // Ignore + break; + case AsyncChannel.CMD_CHANNEL_DISCONNECTED: + Log.e(TAG, "Channel connection lost"); + // This will cause all further async API calls on the WifiManager + // to fail and throw an exception + mAsyncChannel = null; + break; + /* ActionListeners grouped together */ + case WifiManager.CONNECT_NETWORK_FAILED: + case WifiManager.FORGET_NETWORK_FAILED: + case WifiManager.SAVE_NETWORK_FAILED: + case WifiManager.CANCEL_WPS_FAILED: + case WifiManager.DISABLE_NETWORK_FAILED: + if (listener != null) { + ((ActionListener) listener).onFailure(message.arg1); + } + break; + /* ActionListeners grouped together */ + case WifiManager.CONNECT_NETWORK_SUCCEEDED: + case WifiManager.FORGET_NETWORK_SUCCEEDED: + case WifiManager.SAVE_NETWORK_SUCCEEDED: + case WifiManager.CANCEL_WPS_SUCCEDED: + case WifiManager.DISABLE_NETWORK_SUCCEEDED: + if (listener != null) { + ((ActionListener) listener).onSuccess(); + } + break; + case WifiManager.START_WPS_SUCCEEDED: + if (listener != null) { + WpsResult result = (WpsResult) message.obj; + ((WpsListener) listener).onStartSuccess(result.pin); + //Listener needs to stay until completion or failure + synchronized(mListenerMapLock) { + mListenerMap.put(message.arg2, listener); } - break; - default: - //ignore - break; - } + } + break; + case WifiManager.WPS_COMPLETED: + if (listener != null) { + ((WpsListener) listener).onCompletion(); + } + break; + case WifiManager.WPS_FAILED: + if (listener != null) { + ((WpsListener) listener).onFailure(message.arg1); + } + break; + case WifiManager.RSSI_PKTCNT_FETCH_SUCCEEDED: + if (listener != null) { + RssiPacketCountInfo info = (RssiPacketCountInfo) message.obj; + if (info != null) + ((TxPacketCountListener) listener).onSuccess(info.txgood + info.txbad); + else + ((TxPacketCountListener) listener).onFailure(ERROR); + } + break; + case WifiManager.RSSI_PKTCNT_FETCH_FAILED: + if (listener != null) { + ((TxPacketCountListener) listener).onFailure(message.arg1); + } + break; + default: + //ignore + break; } } + } - int putListener(Object listener) { - if (listener == null) return INVALID_KEY; - int key; - synchronized (mListenerMapLock) { - do { - key = mListenerKey++; - } while (key == INVALID_KEY); - mListenerMap.put(key, listener); - } - return key; + private int putListener(Object listener) { + if (listener == null) return INVALID_KEY; + int key; + synchronized (mListenerMapLock) { + do { + key = mListenerKey++; + } while (key == INVALID_KEY); + mListenerMap.put(key, listener); } + return key; + } - Object removeListener(int key) { - if (key == INVALID_KEY) return null; - synchronized (mListenerMapLock) { - Object listener = mListenerMap.get(key); - mListenerMap.remove(key); - return listener; - } + private Object removeListener(int key) { + if (key == INVALID_KEY) return null; + synchronized (mListenerMapLock) { + Object listener = mListenerMap.get(key); + mListenerMap.remove(key); + return listener; } } - /** - * Registers the application with the Wi-Fi framework. This function - * must be the first to be called before any Wi-Fi operations are performed. - * - * @param srcContext is the context of the source - * @param srcLooper is the Looper on which the callbacks are receivied - * @param listener for callback at loss of framework communication. Can be null. - * @return Channel instance that is necessary for performing any further Wi-Fi operations. - * A null is returned upon failure to initialize. - * @hide - */ - public Channel initialize(Context srcContext, Looper srcLooper, ChannelListener listener) { - Messenger messenger = getWifiServiceMessenger(); - if (messenger == null) return null; + private void init() { + mWifiServiceMessenger = getWifiServiceMessenger(); + if (mWifiServiceMessenger == null) { + mAsyncChannel = null; + return; + } - Channel c = new Channel(srcLooper, listener); - if (c.mAsyncChannel.connectSync(srcContext, c.mHandler, messenger) - == AsyncChannel.STATUS_SUCCESSFUL) { - return c; - } else { - return null; + HandlerThread t = new HandlerThread("WifiManager"); + t.start(); + mHandler = new ServiceHandler(t.getLooper()); + mAsyncChannel.connect(mContext, mHandler, mWifiServiceMessenger); + try { + mConnected.await(); + } catch (InterruptedException e) { + Log.e(TAG, "interrupted wait at init"); } } + private void validateChannel() { + if (mAsyncChannel == null) throw new IllegalStateException( + "No permission to access and change wifi or a bad initialization"); + } + /** * Connect to a network with the given configuration. The network also * gets added to the supplicant configuration. @@ -1341,20 +1387,21 @@ public class WifiManager { * sequence of addNetwork(), enableNetwork(), saveConfiguration() and * reconnect() * - * @param c is the channel created at {@link #initialize} * @param config the set of variables that describe the configuration, * contained in a {@link WifiConfiguration} object. * @param listener for callbacks on success or failure. Can be null. + * @throws IllegalStateException if the WifiManager instance needs to be + * initialized again + * * @hide */ - public void connect(Channel c, WifiConfiguration config, ActionListener listener) { - if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + public void connect(WifiConfiguration config, ActionListener listener) { if (config == null) throw new IllegalArgumentException("config cannot be null"); - + validateChannel(); // Use INVALID_NETWORK_ID for arg1 when passing a config object // arg1 is used to pass network id when the network already exists - c.mAsyncChannel.sendMessage(CONNECT_NETWORK, WifiConfiguration.INVALID_NETWORK_ID, - c.putListener(listener), config); + mAsyncChannel.sendMessage(CONNECT_NETWORK, WifiConfiguration.INVALID_NETWORK_ID, + putListener(listener), config); } /** @@ -1363,17 +1410,17 @@ public class WifiManager { * This function is used instead of a enableNetwork(), saveConfiguration() and * reconnect() * - * @param c is the channel created at {@link #initialize} * @param networkId the network id identifiying the network in the * supplicant configuration list * @param listener for callbacks on success or failure. Can be null. + * @throws IllegalStateException if the WifiManager instance needs to be + * initialized again * @hide */ - public void connect(Channel c, int networkId, ActionListener listener) { - if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + public void connect(int networkId, ActionListener listener) { if (networkId < 0) throw new IllegalArgumentException("Network id cannot be negative"); - - c.mAsyncChannel.sendMessage(CONNECT_NETWORK, networkId, c.putListener(listener)); + validateChannel(); + mAsyncChannel.sendMessage(CONNECT_NETWORK, networkId, putListener(listener)); } /** @@ -1387,17 +1434,17 @@ public class WifiManager { * For an existing network, it accomplishes the task of updateNetwork() * and saveConfiguration() * - * @param c is the channel created at {@link #initialize} * @param config the set of variables that describe the configuration, * contained in a {@link WifiConfiguration} object. * @param listener for callbacks on success or failure. Can be null. + * @throws IllegalStateException if the WifiManager instance needs to be + * initialized again * @hide */ - public void save(Channel c, WifiConfiguration config, ActionListener listener) { - if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + public void save(WifiConfiguration config, ActionListener listener) { if (config == null) throw new IllegalArgumentException("config cannot be null"); - - c.mAsyncChannel.sendMessage(SAVE_NETWORK, 0, c.putListener(listener), config); + validateChannel(); + mAsyncChannel.sendMessage(SAVE_NETWORK, 0, putListener(listener), config); } /** @@ -1406,64 +1453,62 @@ public class WifiManager { * This function is used instead of a sequence of removeNetwork() * and saveConfiguration(). * - * @param c is the channel created at {@link #initialize} * @param config the set of variables that describe the configuration, * contained in a {@link WifiConfiguration} object. * @param listener for callbacks on success or failure. Can be null. + * @throws IllegalStateException if the WifiManager instance needs to be + * initialized again * @hide */ - public void forget(Channel c, int netId, ActionListener listener) { - if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + public void forget(int netId, ActionListener listener) { if (netId < 0) throw new IllegalArgumentException("Network id cannot be negative"); - - c.mAsyncChannel.sendMessage(FORGET_NETWORK, netId, c.putListener(listener)); + validateChannel(); + mAsyncChannel.sendMessage(FORGET_NETWORK, netId, putListener(listener)); } /** * Disable network * - * @param c is the channel created at {@link #initialize} * @param netId is the network Id * @param listener for callbacks on success or failure. Can be null. + * @throws IllegalStateException if the WifiManager instance needs to be + * initialized again * @hide */ - public void disable(Channel c, int netId, ActionListener listener) { - if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + public void disable(int netId, ActionListener listener) { if (netId < 0) throw new IllegalArgumentException("Network id cannot be negative"); - - c.mAsyncChannel.sendMessage(DISABLE_NETWORK, netId, c.putListener(listener)); + validateChannel(); + mAsyncChannel.sendMessage(DISABLE_NETWORK, netId, putListener(listener)); } /** * Start Wi-fi Protected Setup * - * @param c is the channel created at {@link #initialize} * @param config WPS configuration * @param listener for callbacks on success or failure. Can be null. + * @throws IllegalStateException if the WifiManager instance needs to be + * initialized again * @hide */ - public void startWps(Channel c, WpsInfo config, WpsListener listener) { - if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + public void startWps(WpsInfo config, WpsListener listener) { if (config == null) throw new IllegalArgumentException("config cannot be null"); - - c.mAsyncChannel.sendMessage(START_WPS, 0, c.putListener(listener), config); + validateChannel(); + mAsyncChannel.sendMessage(START_WPS, 0, putListener(listener), config); } /** * Cancel any ongoing Wi-fi Protected Setup * - * @param c is the channel created at {@link #initialize} * @param listener for callbacks on success or failure. Can be null. + * @throws IllegalStateException if the WifiManager instance needs to be + * initialized again * @hide */ - public void cancelWps(Channel c, ActionListener listener) { - if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); - - c.mAsyncChannel.sendMessage(CANCEL_WPS, 0, c.putListener(listener)); + public void cancelWps(ActionListener listener) { + validateChannel(); + mAsyncChannel.sendMessage(CANCEL_WPS, 0, putListener(listener)); } - - /** * Get a reference to WifiService handler. This is used by a client to establish * an AsyncChannel communication with WifiService @@ -1476,6 +1521,8 @@ public class WifiManager { return mService.getWifiServiceMessenger(); } catch (RemoteException e) { return null; + } catch (SecurityException e) { + return null; } } @@ -1492,8 +1539,6 @@ public class WifiManager { } } - - /** * Returns the file in which IP and proxy configuration data is stored * @hide @@ -1926,4 +1971,11 @@ public class WifiManager { return false; } } + + /** @hide */ + public void captivePortalCheckComplete() { + try { + mService.captivePortalCheckComplete(); + } catch (RemoteException e) {} + } } diff --git a/wifi/java/android/net/wifi/WifiNative.java b/wifi/java/android/net/wifi/WifiNative.java index e520185..b9feb34 100644 --- a/wifi/java/android/net/wifi/WifiNative.java +++ b/wifi/java/android/net/wifi/WifiNative.java @@ -197,8 +197,22 @@ public class WifiNative { return null; } + /** + * Format of results: + * ================= + * bssid=68:7f:74:d7:1b:6e + * freq=2412 + * level=-43 + * tsf=1344621975160944 + * age=2623 + * flags=[WPA2-PSK-CCMP][WPS][ESS] + * ssid=zubyb + * + * RANGE=ALL gets all scan results + * MASK=<N> see wpa_supplicant/src/common/wpa_ctrl.h for details + */ public String scanResults() { - return doStringCommand("SCAN_RESULTS"); + return doStringCommand("BSS RANGE=ALL MASK=0x1986"); } public boolean startDriver() { @@ -368,6 +382,14 @@ public class WifiNative { return doStringCommand("SIGNAL_POLL"); } + /** Example outout: + * TXGOOD=396 + * TXBAD=1 + */ + public String pktcntPoll() { + return doStringCommand("PKTCNT_POLL"); + } + public boolean startWpsPbc(String bssid) { if (TextUtils.isEmpty(bssid)) { return doBooleanCommand("WPS_PBC"); diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java index 15eb9b9..b4456b9 100644 --- a/wifi/java/android/net/wifi/WifiStateMachine.java +++ b/wifi/java/android/net/wifi/WifiStateMachine.java @@ -23,13 +23,8 @@ import static android.net.wifi.WifiManager.WIFI_STATE_ENABLING; import static android.net.wifi.WifiManager.WIFI_STATE_UNKNOWN; /** - * TODO: Add soft AP states as part of WIFI_STATE_XXX - * Retain WIFI_STATE_ENABLING that indicates driver is loading - * Add WIFI_STATE_AP_ENABLED to indicate soft AP has started - * and WIFI_STATE_FAILED for failure + * TODO: * Deprecate WIFI_STATE_UNKNOWN - * - * Doing this will simplify the logic for sending broadcasts */ import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED; import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLING; @@ -46,6 +41,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.database.ContentObserver; import android.net.ConnectivityManager; import android.net.DhcpInfo; import android.net.DhcpInfoInternal; @@ -56,6 +52,7 @@ import android.net.LinkProperties; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; import android.net.NetworkUtils; +import android.net.wifi.RssiPacketCountInfo; import android.net.wifi.WpsResult.Status; import android.net.wifi.p2p.WifiP2pManager; import android.net.wifi.p2p.WifiP2pService; @@ -94,13 +91,10 @@ import java.util.regex.Pattern; * Track the state of Wifi connectivity. All event handling is done here, * and all changes in connectivity state are initiated here. * - * Wi-Fi now supports three modes of operation: Client, Soft Ap and Direct - * In the current implementation, we do not support any concurrency and thus only - * one of Client, Soft Ap or Direct operation is supported at any time. - * - * The WifiStateMachine supports Soft Ap and Client operations while WifiP2pService - * handles Direct. WifiP2pService and WifiStateMachine co-ordinate to ensure only - * one exists at a certain time. + * Wi-Fi now supports three modes of operation: Client, SoftAp and p2p + * In the current implementation, we support concurrent wifi p2p and wifi operation. + * The WifiStateMachine handles SoftAp and Client operations while WifiP2pService + * handles p2p operation. * * @hide */ @@ -110,9 +104,6 @@ public class WifiStateMachine extends StateMachine { private static final String NETWORKTYPE = "WIFI"; private static final boolean DBG = false; - /* TODO: This is no more used with the hostapd code. Clean up */ - private static final String SOFTAP_IFACE = "wl0.1"; - private WifiMonitor mWifiMonitor; private WifiNative mWifiNative; private WifiConfigStore mWifiConfigStore; @@ -123,7 +114,7 @@ public class WifiStateMachine extends StateMachine { private final String mPrimaryDeviceType; /* Scan results handling */ - private List<ScanResult> mScanResults; + private List<ScanResult> mScanResults = new ArrayList<ScanResult>(); private static final Pattern scanResultPattern = Pattern.compile("\t+"); private static final int SCAN_RESULT_CACHE_SIZE = 80; private final LruCache<String, ScanResult> mScanResultCache; @@ -148,6 +139,8 @@ public class WifiStateMachine extends StateMachine { private boolean mSetScanActive = false; /* High perf mode is true if an app has held a high perf Wifi Lock */ private boolean mHighPerfMode = false; + /* Tracks if user has disabled suspend optimizations through settings */ + private AtomicBoolean mSuspendOptEnabled = new AtomicBoolean(true); private boolean mBluetoothConnectionActive = false; @@ -263,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; @@ -466,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 */ @@ -601,6 +598,9 @@ public class WifiStateMachine extends StateMachine { mPrimaryDeviceType = mContext.getResources().getString( com.android.internal.R.string.config_wifi_p2p_device_type); + mSuspendOptEnabled.set(Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED, 1) == 1); + mContext.registerReceiver( new BroadcastReceiver() { @Override @@ -636,18 +636,25 @@ public class WifiStateMachine extends StateMachine { enableBackgroundScanCommand(false); } enableAllNetworks(); - sendMessage(CMD_CLEAR_SUSPEND_OPTIMIZATIONS); + if (mSuspendOptEnabled.get()) { + if (DBG) log("Clear suspend optimizations"); + sendMessage(CMD_CLEAR_SUSPEND_OPTIMIZATIONS); + } } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { enableRssiPolling(false); if (mBackgroundScanSupported) { enableBackgroundScanCommand(true); } - //Allow 2s for suspend optimizations to be set - mSuspendWakeLock.acquire(2000); - sendMessage(CMD_SET_SUSPEND_OPTIMIZATIONS); + if (mSuspendOptEnabled.get()) { + if (DBG) log("Enable suspend optimizations"); + //Allow 2s for suspend optimizations to be set + mSuspendWakeLock.acquire(2000); + sendMessage(CMD_SET_SUSPEND_OPTIMIZATIONS); + } } } }; + mContext.registerReceiver( new BroadcastReceiver() { @Override @@ -658,6 +665,16 @@ public class WifiStateMachine extends StateMachine { }, new IntentFilter(ACTION_DELAYED_DRIVER_STOP)); + mContext.getContentResolver().registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED), false, + new ContentObserver(getHandler()) { + @Override + public void onChange(boolean selfChange) { + mSuspendOptEnabled.set(Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED, 1) == 1); + } + }); + mScanResultCache = new LruCache<String, ScanResult>(SCAN_RESULT_CACHE_SIZE); PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); @@ -682,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); @@ -852,6 +870,10 @@ public class WifiStateMachine extends StateMachine { } } + public void captivePortalCheckComplete() { + sendMessage(obtainMessage(CMD_CAPTIVE_CHECK_COMPLETE)); + } + /** * TODO: doc */ @@ -878,7 +900,13 @@ public class WifiStateMachine extends StateMachine { * TODO: doc */ public List<ScanResult> syncGetScanResultsList() { - return mScanResults; + synchronized (mScanResultCache) { + List<ScanResult> scanList = new ArrayList<ScanResult>(); + for(ScanResult result: mScanResults) { + scanList.add(new ScanResult(result)); + } + return scanList; + } } /** @@ -1143,6 +1171,8 @@ public class WifiStateMachine extends StateMachine { sb.append("mLastNetworkId ").append(mLastNetworkId).append(LS); sb.append("mReconnectCount ").append(mReconnectCount).append(LS); sb.append("mIsScanMode ").append(mIsScanMode).append(LS); + sb.append("mHighPerfMode").append(mHighPerfMode).append(LS); + sb.append("mSuspendOptEnabled").append(mSuspendOptEnabled).append(LS); sb.append("Supplicant status").append(LS) .append(mWifiNative.status()).append(LS).append(LS); @@ -1174,7 +1204,7 @@ public class WifiStateMachine extends StateMachine { case CMD_RSSI_POLL: case CMD_DELAYED_STOP_DRIVER: case WifiMonitor.SCAN_RESULTS_EVENT: - case WifiWatchdogStateMachine.RSSI_FETCH: + case WifiManager.RSSI_PKTCNT_FETCH: return false; default: return true; @@ -1342,131 +1372,103 @@ public class WifiStateMachine extends StateMachine { mContext.sendStickyBroadcast(intent); } + private static final String BSSID_STR = "bssid="; + private static final String FREQ_STR = "freq="; + private static final String LEVEL_STR = "level="; + private static final String TSF_STR = "tsf="; + private static final String FLAGS_STR = "flags="; + private static final String SSID_STR = "ssid="; + private static final String DELIMITER_STR = "===="; /** - * Parse the scan result line passed to us by wpa_supplicant (helper). - * @param line the line to parse - * @return the {@link ScanResult} object + * Format: + * bssid=68:7f:76:d7:1a:6e + * freq=2412 + * level=-44 + * tsf=1344626243700342 + * flags=[WPA2-PSK-CCMP][WPS][ESS] + * ssid=zfdy + * ==== + * bssid=68:5f:74:d7:1a:6f + * freq=5180 + * level=-73 + * tsf=1344626243700373 + * flags=[WPA2-PSK-CCMP][WPS][ESS] + * ssid=zuby + * ==== */ - private ScanResult parseScanResult(String line) { - ScanResult scanResult = null; - if (line != null) { - /* - * Cache implementation (LinkedHashMap) is not synchronized, thus, - * must synchronized here! - */ - synchronized (mScanResultCache) { - String[] result = scanResultPattern.split(line); - if (3 <= result.length && result.length <= 5) { - String bssid = result[0]; - // bssid | frequency | level | flags | ssid - int frequency; - int level; + private void setScanResults(String scanResults) { + String bssid = ""; + int level = 0; + int freq = 0; + long tsf = 0; + String flags = ""; + String ssid = ""; + + if (scanResults == null) { + return; + } + + synchronized(mScanResultCache) { + mScanResults = new ArrayList<ScanResult>(); + String[] lines = scanResults.split("\n"); + + for (String line : lines) { + if (line.startsWith(BSSID_STR)) { + bssid = line.substring(BSSID_STR.length()); + } else if (line.startsWith(FREQ_STR)) { + try { + freq = Integer.parseInt(line.substring(FREQ_STR.length())); + } catch (NumberFormatException e) { + freq = 0; + } + } else if (line.startsWith(LEVEL_STR)) { try { - frequency = Integer.parseInt(result[1]); - level = Integer.parseInt(result[2]); + level = Integer.parseInt(line.substring(LEVEL_STR.length())); /* some implementations avoid negative values by adding 256 * so we need to adjust for that here. */ if (level > 0) level -= 256; - } catch (NumberFormatException e) { - frequency = 0; + } catch(NumberFormatException e) { level = 0; } - - /* - * The formatting of the results returned by - * wpa_supplicant is intended to make the fields - * line up nicely when printed, - * not to make them easy to parse. So we have to - * apply some heuristics to figure out which field - * is the SSID and which field is the flags. - */ - String ssid; - String flags; - if (result.length == 4) { - if (result[3].charAt(0) == '[') { - flags = result[3]; - ssid = ""; + } else if (line.startsWith(TSF_STR)) { + try { + tsf = Long.parseLong(line.substring(TSF_STR.length())); + } catch (NumberFormatException e) { + tsf = 0; + } + } else if (line.startsWith(FLAGS_STR)) { + flags = line.substring(FLAGS_STR.length()); + } else if (line.startsWith(SSID_STR)) { + ssid = line.substring(SSID_STR.length()); + if (ssid == null) ssid = ""; + } else if (line.startsWith(DELIMITER_STR)) { + if (bssid != null) { + String key = bssid + ssid; + ScanResult scanResult = mScanResultCache.get(key); + if (scanResult != null) { + scanResult.level = level; + scanResult.SSID = ssid; + scanResult.capabilities = flags; + scanResult.frequency = freq; + scanResult.timestamp = tsf; } else { - flags = ""; - ssid = result[3]; - } - } else if (result.length == 5) { - flags = result[3]; - ssid = result[4]; - } else { - // Here, we must have 3 fields: no flags and ssid - // set - flags = ""; - ssid = ""; - } - - // bssid + ssid is the hash key - String key = bssid + ssid; - scanResult = mScanResultCache.get(key); - if (scanResult != null) { - scanResult.level = level; - scanResult.SSID = ssid; - scanResult.capabilities = flags; - scanResult.frequency = frequency; - } else { - // Do not add scan results that have no SSID set - if (0 < ssid.trim().length()) { scanResult = new ScanResult( - ssid, bssid, flags, level, frequency); + ssid, bssid, flags, level, freq, tsf); mScanResultCache.put(key, scanResult); } - } - } else { - loge("Misformatted scan result text with " + - result.length + " fields: " + line); + mScanResults.add(scanResult); + } + bssid = null; + level = 0; + freq = 0; + tsf = 0; + flags = ""; + ssid = ""; } } } - - return scanResult; - } - - /** - * scanResults input format - * 00:bb:cc:dd:cc:ee 2427 166 [WPA-EAP-TKIP][WPA2-EAP-CCMP] Net1 - * 00:bb:cc:dd:cc:ff 2412 165 [WPA-EAP-TKIP][WPA2-EAP-CCMP] Net2 - */ - private void setScanResults(String scanResults) { - if (scanResults == null) { - return; - } - - List<ScanResult> scanList = new ArrayList<ScanResult>(); - - int lineCount = 0; - - int scanResultsLen = scanResults.length(); - // Parse the result string, keeping in mind that the last line does - // not end with a newline. - for (int lineBeg = 0, lineEnd = 0; lineEnd <= scanResultsLen; ++lineEnd) { - if (lineEnd == scanResultsLen || scanResults.charAt(lineEnd) == '\n') { - ++lineCount; - - if (lineCount == 1) { - lineBeg = lineEnd + 1; - continue; - } - if (lineEnd > lineBeg) { - String line = scanResults.substring(lineBeg, lineEnd); - ScanResult scanResult = parseScanResult(line); - if (scanResult != null) { - scanList.add(scanResult); - } else { - //TODO: hidden network handling - } - } - lineBeg = lineEnd + 1; - } - } - - mScanResults = scanList; } /* @@ -1525,6 +1527,30 @@ public class WifiStateMachine extends StateMachine { } } + /* + * Fetch TX packet counters on current connection + */ + private void fetchPktcntNative(RssiPacketCountInfo info) { + String pktcntPoll = mWifiNative.pktcntPoll(); + + if (pktcntPoll != null) { + String[] lines = pktcntPoll.split("\n"); + for (String line : lines) { + String[] prop = line.split("="); + if (prop.length < 2) continue; + try { + if (prop[0].equals("TXGOOD")) { + info.txgood = Integer.parseInt(prop[1]); + } else if (prop[0].equals("TXBAD")) { + info.txbad = Integer.parseInt(prop[1]); + } + } catch (NumberFormatException e) { + //Ignore + } + } + } + } + private void configureLinkProperties() { if (mWifiConfigStore.isUsingStaticIp(mLastNetworkId)) { mLinkProperties = mWifiConfigStore.getLinkProperties(mLastNetworkId); @@ -1599,7 +1625,7 @@ public class WifiStateMachine extends StateMachine { } if (state != mNetworkInfo.getDetailedState()) { - mNetworkInfo.setDetailedState(state, null, null); + mNetworkInfo.setDetailedState(state, null, mWifiInfo.getSSID()); } } @@ -1624,10 +1650,7 @@ public class WifiStateMachine extends StateMachine { mWifiInfo.setNetworkId(WifiConfiguration.INVALID_NETWORK_ID); } - if (state == SupplicantState.ASSOCIATING) { - /* BSSID is valid only in ASSOCIATING state */ - mWifiInfo.setBSSID(stateChangeResult.BSSID); - } + mWifiInfo.setBSSID(stateChangeResult.BSSID); mWifiInfo.setSSID(stateChangeResult.SSID); mSupplicantStateTracker.sendMessage(Message.obtain(message)); @@ -1794,12 +1817,12 @@ public class WifiStateMachine extends StateMachine { new Thread(new Runnable() { public void run() { try { - mNwService.startAccessPoint(config, mInterfaceName, SOFTAP_IFACE); + mNwService.startAccessPoint(config, mInterfaceName); } catch (Exception e) { loge("Exception in softap start " + e); try { mNwService.stopAccessPoint(mInterfaceName); - mNwService.startAccessPoint(config, mInterfaceName, SOFTAP_IFACE); + mNwService.startAccessPoint(config, mInterfaceName); } catch (Exception e1) { loge("Exception in softap re-start " + e1); sendMessage(CMD_START_AP_FAILURE); @@ -1936,8 +1959,9 @@ public class WifiStateMachine extends StateMachine { replyToMessage(message, WifiManager.DISABLE_NETWORK_FAILED, WifiManager.BUSY); break; - case WifiWatchdogStateMachine.RSSI_FETCH: - replyToMessage(message, WifiWatchdogStateMachine.RSSI_FETCH_FAILED); + case WifiManager.RSSI_PKTCNT_FETCH: + replyToMessage(message, WifiManager.RSSI_PKTCNT_FETCH_FAILED, + WifiManager.BUSY); break; default: loge("Error! unhandled message" + message); @@ -2791,7 +2815,7 @@ public class WifiStateMachine extends StateMachine { if (DBG) log(getName() + "\n"); mIsRunning = false; updateBatteryWorkSource(null); - mScanResults = null; + mScanResults = new ArrayList<ScanResult>(); if (mP2pSupported) mWifiP2pChannel.sendMessage(WifiStateMachine.CMD_DISABLE_P2P); mContext.unregisterReceiver(mScreenReceiver); @@ -3140,10 +3164,12 @@ public class WifiStateMachine extends StateMachine { mRssiPollToken, 0), POLL_RSSI_INTERVAL_MSECS); } break; - case WifiWatchdogStateMachine.RSSI_FETCH: + case WifiManager.RSSI_PKTCNT_FETCH: + RssiPacketCountInfo info = new RssiPacketCountInfo(); fetchRssiAndLinkSpeedNative(); - replyToMessage(message, WifiWatchdogStateMachine.RSSI_FETCH_SUCCEEDED, - mWifiInfo.getRssi()); + info.rssi = mWifiInfo.getRssi(); + fetchPktcntNative(info); + replyToMessage(message, WifiManager.RSSI_PKTCNT_FETCH_SUCCEEDED, info); break; default: return NOT_HANDLED; @@ -3236,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) { @@ -3243,7 +3289,6 @@ public class WifiStateMachine extends StateMachine { } catch (IllegalStateException e) { loge("Failed to enable IPv6: " + e); } - setNetworkDetailedState(DetailedState.CONNECTED); mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.CONNECTED); sendNetworkStateChangeBroadcast(mLastBssid); diff --git a/wifi/java/android/net/wifi/WifiStateTracker.java b/wifi/java/android/net/wifi/WifiStateTracker.java index bfb91e2..a5a2469 100644 --- a/wifi/java/android/net/wifi/WifiStateTracker.java +++ b/wifi/java/android/net/wifi/WifiStateTracker.java @@ -23,6 +23,7 @@ import android.content.IntentFilter; import android.net.LinkCapabilities; import android.net.LinkProperties; import android.net.NetworkInfo; +import android.net.NetworkInfo.DetailedState; import android.net.NetworkStateTracker; import android.net.wifi.p2p.WifiP2pManager; import android.os.Handler; @@ -113,6 +114,14 @@ public class WifiStateTracker implements NetworkStateTracker { } /** + * Captive check is complete, switch to network + */ + @Override + public void captivePortalCheckComplete() { + mWifiManager.captivePortalCheckComplete(); + } + + /** * Turn the wireless radio off for a network. * @param turnOn {@code true} to turn the radio on, {@code false} */ @@ -235,9 +244,10 @@ public class WifiStateTracker implements NetworkStateTracker { mLinkCapabilities = new LinkCapabilities(); } // don't want to send redundent state messages - // TODO can this be fixed in WifiStateMachine? + // but send portal check detailed state notice NetworkInfo.State state = mNetworkInfo.getState(); - if (mLastState == state) { + if (mLastState == state && + mNetworkInfo.getDetailedState() != DetailedState.CAPTIVE_PORTAL_CHECK) { return; } else { mLastState = state; diff --git a/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java b/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java index c6d3eae..7fa6aac 100644 --- a/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java +++ b/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java @@ -16,29 +16,22 @@ 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.arp.ArpPeer; import android.net.ConnectivityManager; -import android.net.LinkAddress; import android.net.LinkProperties; import android.net.NetworkInfo; -import android.net.RouteInfo; -import android.net.Uri; +import android.net.wifi.RssiPacketCountInfo; import android.os.Message; import android.os.SystemClock; -import android.os.SystemProperties; import android.provider.Settings; import android.provider.Settings.Secure; import android.util.Log; +import android.util.LruCache; import com.android.internal.R; import com.android.internal.util.AsyncChannel; @@ -46,43 +39,36 @@ 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.InetAddress; -import java.net.SocketException; -import java.net.URL; +import java.text.DecimalFormat; /** - * 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. + * WifiWatchdogStateMachine monitors the connection to a WiFi network. When WiFi + * 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). + * <p> + * We now monitor the quality of the last hop on WiFi using packet loss ratio as + * an indicator to decide if the link is good enough to switch to Wi-Fi as the + * uplink. + * <p> + * When WiFi is connected, the WiFi watchdog keeps sampling the RSSI and the + * instant packet loss, and record it as per-AP loss-to-rssi statistics. When + * the instant packet loss is higher than a threshold, the WiFi watchdog sends a + * poor link notification to avoid WiFi connection temporarily. + * <p> + * While WiFi is being avoided, the WiFi watchdog keep watching the RSSI to + * bring the WiFi connection back. Once the RSSI is high enough to achieve a + * lower packet loss, a good link detection is sent such that the WiFi + * connection become available again. + * <p> + * BSSID roaming has been taken into account. When user is moving across + * multiple APs, the WiFi watchdog will detect that and keep watching the + * currently connected AP. + * <p> + * Power impact should be minimal since much of the measurement relies on + * passive statistics already being tracked at the driver and the polling is + * done when screen is turned on and the RSSI is in a certain range. * * @hide */ @@ -91,129 +77,221 @@ public class WifiWatchdogStateMachine extends StateMachine { /* STOPSHIP: Keep this configurable for debugging until ship */ private static boolean DBG = false; private static final String TAG = "WifiWatchdogStateMachine"; - private static final String WALLED_GARDEN_NOTIFICATION_ID = "WifiWatchdog.walledgarden"; - - /* RSSI Levels as used by notification icon - Level 4 -55 <= RSSI - Level 3 -66 <= RSSI < -55 - Level 2 -77 <= RSSI < -67 - Level 1 -88 <= RSSI < -78 - Level 0 RSSI < -88 */ - - /* Wi-fi connection is monitored actively below this - threshold */ - private static final int RSSI_LEVEL_MONITOR = 0; - /* Rssi threshold is at level 0 (-88dBm) */ - private static final int RSSI_MONITOR_THRESHOLD = -88; - /* Number of times RSSI is measured to be low before being avoided */ - private static final int RSSI_MONITOR_COUNT = 5; - private int mRssiMonitorCount = 0; - - /* Avoid flapping. The interval is changed over time as long as we continue to avoid - * under the max interval after which we reset the interval again */ - private static final int MIN_INTERVAL_AVOID_BSSID_MS[] = {0, 30 * 1000, 60 * 1000, - 5 * 60 * 1000, 30 * 60 * 1000}; - /* Index into the interval array MIN_INTERVAL_AVOID_BSSID_MS */ - private int mMinIntervalArrayIndex = 0; - - private long mLastBssidAvoidedTime; - private int mCurrentSignalLevel; + private static final int BASE = Protocol.BASE_WIFI_WATCHDOG; - private static final long DEFAULT_ARP_CHECK_INTERVAL_MS = 2 * 60 * 1000; - private static final long DEFAULT_RSSI_FETCH_INTERVAL_MS = 1000; - private static final long DEFAULT_WALLED_GARDEN_INTERVAL_MS = 30 * 60 * 1000; + /* Internal events */ + private static final int EVENT_WATCHDOG_TOGGLED = BASE + 1; + private static final int EVENT_NETWORK_STATE_CHANGE = BASE + 2; + private static final int EVENT_RSSI_CHANGE = BASE + 3; + private static final int EVENT_SUPPLICANT_STATE_CHANGE = 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 EVENT_BSSID_CHANGE = BASE + 7; + private static final int EVENT_SCREEN_ON = BASE + 8; + private static final int EVENT_SCREEN_OFF = BASE + 9; - private static final int DEFAULT_NUM_ARP_PINGS = 5; - private static final int DEFAULT_MIN_ARP_RESPONSES = 1; + /* Internal messages */ + private static final int CMD_RSSI_FETCH = BASE + 11; - private static final int DEFAULT_ARP_PING_TIMEOUT_MS = 100; + /* Notifications from/to WifiStateMachine */ + static final int POOR_LINK_DETECTED = BASE + 21; + static final int GOOD_LINK_DETECTED = BASE + 22; - // 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; + /* + * RSSI levels as used by notification icon + * Level 4 -55 <= RSSI + * Level 3 -66 <= RSSI < -55 + * Level 2 -77 <= RSSI < -67 + * Level 1 -88 <= RSSI < -78 + * Level 0 RSSI < -88 + */ - /* 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 + /** + * WiFi link statistics is monitored and recorded actively below this threshold. + * <p> + * Larger threshold is more adaptive but increases sampling cost. */ - private static final int WALLED_GARDEN_START_DELAY_MS = 3000; + private static final int LINK_MONITOR_LEVEL_THRESHOLD = WifiManager.RSSI_LEVELS - 1; - private static final int BASE = Protocol.BASE_WIFI_WATCHDOG; + /** + * Remember packet loss statistics of how many BSSIDs. + * <p> + * Larger size is usually better but requires more space. + */ + private static final int BSSID_STAT_CACHE_SIZE = 20; /** - * Indicates the enable setting of WWS may have changed + * RSSI range of a BSSID statistics. + * Within the range, (RSSI -> packet loss %) mappings are stored. + * <p> + * Larger range is usually better but requires more space. */ - private static final int EVENT_WATCHDOG_TOGGLED = BASE + 1; + private static final int BSSID_STAT_RANGE_LOW_DBM = -105; /** - * Indicates the wifi network state has changed. Passed w/ original intent - * which has a non-null networkInfo object + * See {@link #BSSID_STAT_RANGE_LOW_DBM}. */ - private static final int EVENT_NETWORK_STATE_CHANGE = BASE + 2; - /* Passed with RSSI information */ - private static final int EVENT_RSSI_CHANGE = BASE + 3; - 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 BSSID_STAT_RANGE_HIGH_DBM = -45; - /* Internal messages */ - private static final int CMD_ARP_CHECK = BASE + 11; - private static final int CMD_DELAYED_WALLED_GARDEN_CHECK = BASE + 12; - private static final int CMD_RSSI_FETCH = BASE + 13; + /** + * How many consecutive empty data point to trigger a empty-cache detection. + * In this case, a preset/default loss value (function on RSSI) is used. + * <p> + * In normal uses, some RSSI values may never be seen due to channel randomness. + * However, the size of such empty RSSI chunk in normal use is generally 1~2. + */ + private static final int BSSID_STAT_EMPTY_COUNT = 3; - /* Notifications to WifiStateMachine */ - static final int POOR_LINK_DETECTED = BASE + 21; - static final int GOOD_LINK_DETECTED = BASE + 22; - static final int RSSI_FETCH = BASE + 23; - static final int RSSI_FETCH_SUCCEEDED = BASE + 24; - static final int RSSI_FETCH_FAILED = BASE + 25; + /** + * Sample interval for packet loss statistics, in msec. + * <p> + * Smaller interval is more accurate but increases sampling cost (battery consumption). + */ + private static final long LINK_SAMPLING_INTERVAL_MS = 1 * 1000; + + /** + * Coefficients (alpha) for moving average for packet loss tracking. + * Must be within (0.0, 1.0). + * <p> + * Equivalent number of samples: N = 2 / alpha - 1 . + * We want the historic loss to base on more data points to be statistically reliable. + * We want the current instant loss to base on less data points to be responsive. + */ + private static final double EXP_COEFFICIENT_RECORD = 0.1; + + /** + * See {@link #EXP_COEFFICIENT_RECORD}. + */ + private static final double EXP_COEFFICIENT_MONITOR = 0.5; + + /** + * Thresholds for sending good/poor link notifications, in packet loss %. + * Good threshold must be smaller than poor threshold. + * Use smaller poor threshold to avoid WiFi more aggressively. + * Use smaller good threshold to bring back WiFi more conservatively. + * <p> + * When approaching the boundary, loss ratio jumps significantly within a few dBs. + * 50% loss threshold is a good balance between accuracy and reponsiveness. + * <=10% good threshold is a safe value to avoid jumping back to WiFi too easily. + */ + private static final double POOR_LINK_LOSS_THRESHOLD = 0.5; + + /** + * See {@link #POOR_LINK_LOSS_THRESHOLD}. + */ + private static final double GOOD_LINK_LOSS_THRESHOLD = 0.1; + + /** + * Number of samples to confirm before sending a poor link notification. + * Response time = confirm_count * sample_interval . + * <p> + * A smaller threshold improves response speed but may suffer from randomness. + * According to experiments, 3~5 are good values to achieve a balance. + * These parameters should be tuned along with {@link #LINK_SAMPLING_INTERVAL_MS}. + */ + private static final int POOR_LINK_SAMPLE_COUNT = 3; + + /** + * Minimum volume (converted from pkt/sec) to detect a poor link, to avoid randomness. + * <p> + * According to experiments, 1pkt/sec is too sensitive but 3pkt/sec is slightly unresponsive. + */ + private static final double POOR_LINK_MIN_VOLUME = 2.0 * LINK_SAMPLING_INTERVAL_MS / 1000.0; + + /** + * When a poor link is detected, we scan over this range (based on current + * poor link RSSI) for a target RSSI that satisfies a target packet loss. + * Refer to {@link #GOOD_LINK_TARGET}. + * <p> + * We want range_min not too small to avoid jumping back to WiFi too easily. + */ + private static final int GOOD_LINK_RSSI_RANGE_MIN = 3; + + /** + * See {@link #GOOD_LINK_RSSI_RANGE_MIN}. + */ + private static final int GOOD_LINK_RSSI_RANGE_MAX = 20; - private static final int SINGLE_ARP_CHECK = 0; - private static final int FULL_ARP_CHECK = 1; + /** + * Adaptive good link target to avoid flapping. + * When a poor link is detected, a good link target is calculated as follows: + * <p> + * targetRSSI = min { rssi | loss(rssi) < GOOD_LINK_LOSS_THRESHOLD } + rssi_adj[i], + * where rssi is within the above GOOD_LINK_RSSI_RANGE. + * targetCount = sample_count[i] . + * <p> + * While WiFi is being avoided, we keep monitoring its signal strength. + * Good link notification is sent when we see current RSSI >= targetRSSI + * for targetCount consecutive times. + * <p> + * Index i is incremented each time after a poor link detection. + * Index i is decreased to at most k if the last poor link was at lease reduce_time[k] ago. + * <p> + * Intuitively, larger index i makes it more difficult to get back to WiFi, avoiding flapping. + * In experiments, (+9 dB / 30 counts) makes it quite difficult to achieve. + * Avoid using it unless flapping is really bad (say, last poor link is < 1 min ago). + */ + private static final GoodLinkTarget[] GOOD_LINK_TARGET = { + /* rssi_adj, sample_count, reduce_time */ + new GoodLinkTarget( 0, 3, 30 * 60000 ), + new GoodLinkTarget( 3, 5, 5 * 60000 ), + new GoodLinkTarget( 6, 10, 1 * 60000 ), + new GoodLinkTarget( 9, 30, 0 * 60000 ), + }; + /** + * The max time to avoid a BSSID, to prevent avoiding forever. + * If current RSSI is at least min_rssi[i], the max avoidance time is at most max_time[i] + * <p> + * It is unusual to experience high packet loss at high RSSI. Something unusual must be + * happening (e.g. strong interference). For higher signal strengths, we set the avoidance + * time to be low to allow for quick turn around from temporary interference. + * <p> + * See {@link BssidStatistics#poorLinkDetected}. + */ + private static final MaxAvoidTime[] MAX_AVOID_TIME = { + /* max_time, min_rssi */ + new MaxAvoidTime( 30 * 60000, -200 ), + new MaxAvoidTime( 5 * 60000, -70 ), + new MaxAvoidTime( 0 * 60000, -55 ), + }; + + /* Framework related */ private Context mContext; private ContentResolver mContentResolver; private WifiManager mWifiManager; private IntentFilter mIntentFilter; private BroadcastReceiver mBroadcastReceiver; - private AsyncChannel mWsmChannel = new AsyncChannel();; + private AsyncChannel mWsmChannel = new AsyncChannel(); + private WifiInfo mWifiInfo; + private LinkProperties mLinkProperties; + + /* System settingss related */ + private static boolean sWifiOnly = false; + private boolean mPoorNetworkDetectionEnabled; + + /* Poor link detection related */ + private LruCache<String, BssidStatistics> mBssidCache = + new LruCache<String, BssidStatistics>(BSSID_STAT_CACHE_SIZE); + private int mRssiFetchToken = 0; + private int mCurrentSignalLevel; + private BssidStatistics mCurrentBssid; + private VolumeWeightedEMA mCurrentLoss; + private boolean mIsScreenOn = true; + private static double sPresetLoss[]; + /* WiFi watchdog state machine related */ 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 WalledGardenCheckState mWalledGardenCheckState = new WalledGardenCheckState(); - /* Online and watching link connectivity */ private OnlineWatchState mOnlineWatchState = new OnlineWatchState(); - /* RSSI level is below RSSI_LEVEL_MONITOR and needs close monitoring */ - private RssiMonitoringState mRssiMonitoringState = new RssiMonitoringState(); - /* Online and doing nothing */ + private LinkMonitoringState mLinkMonitoringState = new LinkMonitoringState(); private OnlineState mOnlineState = new OnlineState(); - private int mArpToken = 0; - private long mArpCheckIntervalMs; - private int mRssiFetchToken = 0; - private long mRssiFetchIntervalMs; - private long mWalledGardenIntervalMs; - private int mNumArpPings; - private int mMinArpResponses; - private int mArpPingTimeoutMs; - private boolean mPoorNetworkDetectionEnabled; - private boolean mWalledGardenTestEnabled; - private String mWalledGardenUrl; - - private WifiInfo mWifiInfo; - private LinkProperties mLinkProperties; - - private long mLastWalledGardenCheckTime = 0; - - private static boolean sWifiOnly = false; - private boolean mWalledGardenNotificationShown; - /** * STATE MAP * Default @@ -234,7 +312,7 @@ public class WifiWatchdogStateMachine extends StateMachine { setupNetworkReceiver(); - // The content observer to listen needs a handler + // the content observer to listen needs a handler registerForSettingsChanges(); registerForWatchdogToggle(); addState(mDefaultState); @@ -243,9 +321,8 @@ public class WifiWatchdogStateMachine extends StateMachine { addState(mNotConnectedState, mWatchdogEnabledState); addState(mVerifyingLinkState, mWatchdogEnabledState); addState(mConnectedState, mWatchdogEnabledState); - addState(mWalledGardenCheckState, mConnectedState); addState(mOnlineWatchState, mConnectedState); - addState(mRssiMonitoringState, mOnlineWatchState); + addState(mLinkMonitoringState, mConnectedState); addState(mOnlineState, mConnectedState); if (isWatchdogEnabled()) { @@ -263,15 +340,14 @@ 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) { - log("Disabling poor network avoidance for wi-fi only device"); + logd("Disabling poor network avoidance for wi-fi only device"); putSettingsBoolean(contentResolver, Settings.Secure.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED, false); } @@ -286,15 +362,20 @@ public class WifiWatchdogStateMachine extends StateMachine { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { - sendMessage(EVENT_NETWORK_STATE_CHANGE, intent); - } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) { + if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) { obtainMessage(EVENT_RSSI_CHANGE, intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200), 0).sendToTarget(); + } else if (action.equals(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION)) { + sendMessage(EVENT_SUPPLICANT_STATE_CHANGE, intent); + } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { + sendMessage(EVENT_NETWORK_STATE_CHANGE, intent); + } else if (action.equals(Intent.ACTION_SCREEN_ON)) { + sendMessage(EVENT_SCREEN_ON); + } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { + sendMessage(EVENT_SCREEN_OFF); } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { - sendMessage(EVENT_WIFI_RADIO_STATE_CHANGE, - intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, - WifiManager.WIFI_STATE_UNKNOWN)); + sendMessage(EVENT_WIFI_RADIO_STATE_CHANGE,intent.getIntExtra( + WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN)); } } }; @@ -303,6 +384,9 @@ 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.SUPPLICANT_STATE_CHANGED_ACTION); + mIntentFilter.addAction(Intent.ACTION_SCREEN_ON); + mIntentFilter.addAction(Intent.ACTION_SCREEN_OFF); mContext.registerReceiver(mBroadcastReceiver, mIntentFilter); } @@ -334,59 +418,8 @@ public class WifiWatchdogStateMachine extends StateMachine { }; mContext.getContentResolver().registerContentObserver( - Settings.Secure.getUriFor( - Settings.Secure.WIFI_WATCHDOG_ARP_CHECK_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_NUM_ARP_PINGS), - false, contentObserver); - mContext.getContentResolver().registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_MIN_ARP_RESPONSES), - false, contentObserver); - mContext.getContentResolver().registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_ARP_PING_TIMEOUT_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) { - log("Walled garden check - probably not a portal: exception " + e); - } - return false; - } finally { - if (urlConnection != null) { - urlConnection.disconnect(); - } - } } public void dump(PrintWriter pw) { @@ -395,90 +428,29 @@ public class WifiWatchdogStateMachine extends StateMachine { pw.println("mWifiInfo: [" + mWifiInfo + "]"); pw.println("mLinkProperties: [" + mLinkProperties + "]"); pw.println("mCurrentSignalLevel: [" + mCurrentSignalLevel + "]"); - pw.println("mArpCheckIntervalMs: [" + mArpCheckIntervalMs+ "]"); - pw.println("mRssiFetchIntervalMs: [" + mRssiFetchIntervalMs + "]"); - 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() { boolean ret = getSettingsBoolean(mContentResolver, Settings.Secure.WIFI_WATCHDOG_ON, true); - if (DBG) log("watchdog enabled " + ret); + if (DBG) logd("Watchdog enabled " + ret); return ret; } private void updateSettings() { - if (DBG) log("Updating secure settings"); - - mArpCheckIntervalMs = Secure.getLong(mContentResolver, - Secure.WIFI_WATCHDOG_ARP_CHECK_INTERVAL_MS, - DEFAULT_ARP_CHECK_INTERVAL_MS); - mRssiFetchIntervalMs = Secure.getLong(mContentResolver, - Secure.WIFI_WATCHDOG_RSSI_FETCH_INTERVAL_MS, - DEFAULT_RSSI_FETCH_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); + if (DBG) logd("Updating secure settings"); + 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; } + /** + * Default state, guard for unhandled messages. + */ class DefaultState extends State { @Override public void enter() { - if (DBG) log(getName() + "\n"); + if (DBG) logd(getName()); } @Override @@ -486,34 +458,41 @@ public class WifiWatchdogStateMachine extends StateMachine { switch (msg.what) { case EVENT_WATCHDOG_SETTINGS_CHANGE: updateSettings(); - if (DBG) { - log("Updating wifi-watchdog secure settings"); - } + if (DBG) logd("Updating wifi-watchdog secure settings"); break; case EVENT_RSSI_CHANGE: mCurrentSignalLevel = calculateSignalLevel(msg.arg1); break; case EVENT_WIFI_RADIO_STATE_CHANGE: case EVENT_NETWORK_STATE_CHANGE: - case CMD_ARP_CHECK: - case CMD_DELAYED_WALLED_GARDEN_CHECK: + case EVENT_SUPPLICANT_STATE_CHANGE: + case EVENT_BSSID_CHANGE: case CMD_RSSI_FETCH: - case RSSI_FETCH_SUCCEEDED: - case RSSI_FETCH_FAILED: - //ignore + case WifiManager.RSSI_PKTCNT_FETCH_SUCCEEDED: + case WifiManager.RSSI_PKTCNT_FETCH_FAILED: + // ignore + break; + case EVENT_SCREEN_ON: + mIsScreenOn = true; + break; + case EVENT_SCREEN_OFF: + mIsScreenOn = false; break; default: - log("Unhandled message " + msg + " in state " + getCurrentState().getName()); + loge("Unhandled message " + msg + " in state " + getCurrentState().getName()); break; } return HANDLED; } } + /** + * WiFi watchdog is disabled by the setting. + */ class WatchdogDisabledState extends State { @Override public void enter() { - if (DBG) log(getName() + "\n"); + if (DBG) logd(getName()); } @Override @@ -530,8 +509,8 @@ public class WifiWatchdogStateMachine extends StateMachine { switch (networkInfo.getDetailedState()) { case VERIFYING_POOR_LINK: - if (DBG) log("Watchdog disabled, verify link"); - mWsmChannel.sendMessage(GOOD_LINK_DETECTED); + if (DBG) logd("Watchdog disabled, verify link"); + sendLinkStatusNotification(true); break; default: break; @@ -542,98 +521,103 @@ public class WifiWatchdogStateMachine extends StateMachine { } } + /** + * WiFi watchdog is enabled by the setting. + */ class WatchdogEnabledState extends State { @Override public void enter() { - if (DBG) log("WifiWatchdogService enabled"); + if (DBG) logd(getName()); } @Override public boolean processMessage(Message msg) { + Intent intent; switch (msg.what) { case EVENT_WATCHDOG_TOGGLED: if (!isWatchdogEnabled()) transitionTo(mWatchdogDisabledState); break; + case EVENT_NETWORK_STATE_CHANGE: - Intent intent = (Intent) msg.obj; - NetworkInfo networkInfo = (NetworkInfo) - intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); + intent = (Intent) msg.obj; + NetworkInfo networkInfo = + (NetworkInfo) intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); + if (DBG) logd("Network state change " + networkInfo.getDetailedState()); - if (DBG) log("network state change " + networkInfo.getDetailedState()); + mWifiInfo = (WifiInfo) intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO); + updateCurrentBssid(mWifiInfo != null ? mWifiInfo.getBSSID() : null); 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) { if (mWifiInfo == null) { - log("Ignoring link verification, mWifiInfo is NULL"); - mWsmChannel.sendMessage(GOOD_LINK_DETECTED); + if (DBG) logd("Ignoring link verification, mWifiInfo is NULL"); + sendLinkStatusNotification(true); } else { transitionTo(mVerifyingLinkState); } } else { - mWsmChannel.sendMessage(GOOD_LINK_DETECTED); + sendLinkStatusNotification(true); } break; case CONNECTED: - if (shouldCheckWalledGarden()) { - transitionTo(mWalledGardenCheckState); - } else { - transitionTo(mOnlineWatchState); - } + transitionTo(mOnlineWatchState); break; default: transitionTo(mNotConnectedState); break; } break; + + case EVENT_SUPPLICANT_STATE_CHANGE: + intent = (Intent) msg.obj; + SupplicantState supplicantState = (SupplicantState) intent.getParcelableExtra( + WifiManager.EXTRA_NEW_STATE); + if (supplicantState == SupplicantState.COMPLETED) { + mWifiInfo = mWifiManager.getConnectionInfo(); + updateCurrentBssid(mWifiInfo.getBSSID()); + } + break; + case EVENT_WIFI_RADIO_STATE_CHANGE: - if ((Integer) msg.obj == WifiManager.WIFI_STATE_DISABLING) { - if (DBG) log("WifiStateDisabling -- Resetting WatchdogState"); + if ((Integer) msg.obj == WifiManager.WIFI_STATE_DISABLING) transitionTo(mNotConnectedState); - } break; + default: return NOT_HANDLED; } - setWalledGardenNotificationVisible(false); return HANDLED; } - - @Override - public void exit() { - if (DBG) log("WifiWatchdogService disabled"); - } } + /** + * WiFi is disconnected. + */ class NotConnectedState extends State { @Override public void enter() { - if (DBG) log(getName() + "\n"); + if (DBG) logd(getName()); } } + /** + * WiFi is connected, but waiting for good link detection message. + */ class VerifyingLinkState extends State { + + private int mSampleCount; + @Override public void enter() { - if (DBG) log(getName() + "\n"); - //Treat entry as an rssi change - handleRssiChange(); - } - - private void handleRssiChange() { - if (mCurrentSignalLevel <= RSSI_LEVEL_MONITOR) { - //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)); - } + if (DBG) logd(getName()); + mSampleCount = 0; + mCurrentBssid.newLinkDetected(); + sendMessage(obtainMessage(CMD_RSSI_FETCH, ++mRssiFetchToken, 0)); } @Override @@ -642,25 +626,51 @@ public class WifiWatchdogStateMachine extends StateMachine { case EVENT_WATCHDOG_SETTINGS_CHANGE: updateSettings(); if (!mPoorNetworkDetectionEnabled) { - mWsmChannel.sendMessage(GOOD_LINK_DETECTED); + sendLinkStatusNotification(true); } break; - case EVENT_RSSI_CHANGE: - mCurrentSignalLevel = calculateSignalLevel(msg.arg1); - handleRssiChange(); + + case EVENT_BSSID_CHANGE: + transitionTo(mVerifyingLinkState); + break; + + case CMD_RSSI_FETCH: + if (msg.arg1 == mRssiFetchToken) { + mWsmChannel.sendMessage(WifiManager.RSSI_PKTCNT_FETCH); + sendMessageDelayed(obtainMessage(CMD_RSSI_FETCH, ++mRssiFetchToken, 0), + LINK_SAMPLING_INTERVAL_MS); + } 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); + + case WifiManager.RSSI_PKTCNT_FETCH_SUCCEEDED: + RssiPacketCountInfo info = (RssiPacketCountInfo) msg.obj; + int rssi = info.rssi; + if (DBG) logd("Fetch RSSI succeed, rssi=" + rssi); + + long time = mCurrentBssid.mBssidAvoidTimeMax - SystemClock.elapsedRealtime(); + if (time <= 0) { + // max avoidance time is met + if (DBG) logd("Max avoid time elapsed"); + sendLinkStatusNotification(true); + } else { + if (rssi >= mCurrentBssid.mGoodLinkTargetRssi) { + if (++mSampleCount >= mCurrentBssid.mGoodLinkTargetCount) { + // link is good again + if (DBG) logd("Good link detected, rssi=" + rssi); + mCurrentBssid.mBssidAvoidTimeMax = 0; + sendLinkStatusNotification(true); + } } else { - if (DBG) log("Continue ARP check, rssi level: " + mCurrentSignalLevel); - sendMessageDelayed(obtainMessage(CMD_ARP_CHECK, ++mArpToken, 0), - mArpCheckIntervalMs); + mSampleCount = 0; + if (DBG) logd("Link is still poor, time left=" + time); } } break; + + case WifiManager.RSSI_PKTCNT_FETCH_FAILED: + if (DBG) logd("RSSI_FETCH_FAILED"); + break; + default: return NOT_HANDLED; } @@ -668,19 +678,23 @@ public class WifiWatchdogStateMachine extends StateMachine { } } + /** + * WiFi is connected and link is verified. + */ class ConnectedState extends State { @Override public void enter() { - if (DBG) log(getName() + "\n"); + if (DBG) logd(getName()); } + @Override public boolean processMessage(Message msg) { switch (msg.what) { case EVENT_WATCHDOG_SETTINGS_CHANGE: updateSettings(); - //STOPSHIP: Remove this at ship + // STOPSHIP: Remove this at ship + logd("Updated secure settings and turned debug on"); DBG = true; - if (DBG) log("Updated secure settings and turned debug on"); if (mPoorNetworkDetectionEnabled) { transitionTo(mOnlineWatchState); @@ -693,40 +707,15 @@ public class WifiWatchdogStateMachine extends StateMachine { } } - class WalledGardenCheckState extends State { - private int mWalledGardenToken = 0; - @Override - public void enter() { - 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 CMD_DELAYED_WALLED_GARDEN_CHECK: - if (msg.arg1 == mWalledGardenToken) { - mLastWalledGardenCheckTime = SystemClock.elapsedRealtime(); - if (isWalledGardenConnection()) { - if (DBG) log("Walled garden detected"); - setWalledGardenNotificationVisible(true); - } - transitionTo(mOnlineWatchState); - } - break; - default: - return NOT_HANDLED; - } - return HANDLED; - } - } - + /** + * RSSI is high enough and don't need link monitoring. + */ class OnlineWatchState extends State { + @Override public void enter() { - if (DBG) log(getName() + "\n"); + if (DBG) logd(getName()); if (mPoorNetworkDetectionEnabled) { - //Treat entry as an rssi change + // treat entry as an rssi change handleRssiChange(); } else { transitionTo(mOnlineState); @@ -734,10 +723,10 @@ public class WifiWatchdogStateMachine extends StateMachine { } private void handleRssiChange() { - if (mCurrentSignalLevel <= RSSI_LEVEL_MONITOR) { - transitionTo(mRssiMonitoringState); + if (mCurrentSignalLevel <= LINK_MONITOR_LEVEL_THRESHOLD) { + transitionTo(mLinkMonitoringState); } else { - //stay here + // stay here } } @@ -746,16 +735,7 @@ public class WifiWatchdogStateMachine extends StateMachine { switch (msg.what) { case EVENT_RSSI_CHANGE: mCurrentSignalLevel = calculateSignalLevel(msg.arg1); - //Ready to avoid bssid again ? - long time = android.os.SystemClock.elapsedRealtime(); - if (time - mLastBssidAvoidedTime > MIN_INTERVAL_AVOID_BSSID_MS[ - mMinIntervalArrayIndex]) { - handleRssiChange(); - } else { - if (DBG) log("Early to avoid " + mWifiInfo + " time: " + time + - " last avoided: " + mLastBssidAvoidedTime + - " mMinIntervalArrayIndex: " + mMinIntervalArrayIndex); - } + handleRssiChange(); break; default: return NOT_HANDLED; @@ -764,48 +744,110 @@ public class WifiWatchdogStateMachine extends StateMachine { } } - class RssiMonitoringState extends State { + /** + * Keep sampling the link and monitor any poor link situation. + */ + class LinkMonitoringState extends State { + + private int mSampleCount; + + private int mLastRssi; + private int mLastTxGood; + private int mLastTxBad; + + @Override public void enter() { - if (DBG) log(getName() + "\n"); + if (DBG) logd(getName()); + mSampleCount = 0; + mCurrentLoss = new VolumeWeightedEMA(EXP_COEFFICIENT_MONITOR); sendMessage(obtainMessage(CMD_RSSI_FETCH, ++mRssiFetchToken, 0)); } + @Override public boolean processMessage(Message msg) { switch (msg.what) { case EVENT_RSSI_CHANGE: mCurrentSignalLevel = calculateSignalLevel(msg.arg1); - if (mCurrentSignalLevel <= RSSI_LEVEL_MONITOR) { - //stay here; + if (mCurrentSignalLevel <= LINK_MONITOR_LEVEL_THRESHOLD) { + // stay here; } else { - //We dont need frequent RSSI monitoring any more + // we don't need frequent RSSI monitoring any more transitionTo(mOnlineWatchState); } break; + + case EVENT_BSSID_CHANGE: + transitionTo(mLinkMonitoringState); + break; + case CMD_RSSI_FETCH: - if (msg.arg1 == mRssiFetchToken) { - mWsmChannel.sendMessage(RSSI_FETCH); + if (!mIsScreenOn) { + transitionTo(mOnlineState); + } else if (msg.arg1 == mRssiFetchToken) { + mWsmChannel.sendMessage(WifiManager.RSSI_PKTCNT_FETCH); sendMessageDelayed(obtainMessage(CMD_RSSI_FETCH, ++mRssiFetchToken, 0), - mRssiFetchIntervalMs); + LINK_SAMPLING_INTERVAL_MS); } break; - case RSSI_FETCH_SUCCEEDED: - int rssi = msg.arg1; - if (DBG) log("RSSI_FETCH_SUCCEEDED: " + rssi); - if (msg.arg1 < RSSI_MONITOR_THRESHOLD) { - mRssiMonitorCount++; - } else { - mRssiMonitorCount = 0; - } - if (mRssiMonitorCount > RSSI_MONITOR_COUNT) { - sendPoorLinkDetected(); - ++mRssiFetchToken; + case WifiManager.RSSI_PKTCNT_FETCH_SUCCEEDED: + RssiPacketCountInfo info = (RssiPacketCountInfo) msg.obj; + int rssi = info.rssi; + int mrssi = (mLastRssi + rssi) / 2; + int txbad = info.txbad; + int txgood = info.txgood; + if (DBG) logd("Fetch RSSI succeed, rssi=" + rssi + " mrssi=" + mrssi + " txbad=" + + txbad + " txgood=" + txgood); + + // skip the first data point as we want incremental values + long now = SystemClock.elapsedRealtime(); + if (now - mCurrentBssid.mLastTimeSample < LINK_SAMPLING_INTERVAL_MS * 2) { + + // update packet loss statistics + int dbad = txbad - mLastTxBad; + int dgood = txgood - mLastTxGood; + int dtotal = dbad + dgood; + + if (dtotal > 0) { + // calculate packet loss in the last sampling interval + double loss = ((double) dbad) / ((double) dtotal); + + mCurrentLoss.update(loss, dtotal); + + if (DBG) { + DecimalFormat df = new DecimalFormat("#.##"); + logd("Incremental loss=" + dbad + "/" + dtotal + " Current loss=" + + df.format(mCurrentLoss.mValue * 100) + "% volume=" + + df.format(mCurrentLoss.mVolume)); + } + + mCurrentBssid.updateLoss(mrssi, loss, dtotal); + + // check for high packet loss and send poor link notification + if (mCurrentLoss.mValue > POOR_LINK_LOSS_THRESHOLD + && mCurrentLoss.mVolume > POOR_LINK_MIN_VOLUME) { + if (++mSampleCount >= POOR_LINK_SAMPLE_COUNT) + if (mCurrentBssid.poorLinkDetected(rssi)) { + sendLinkStatusNotification(false); + ++mRssiFetchToken; + } + } else { + mSampleCount = 0; + } + } } + + mCurrentBssid.mLastTimeSample = now; + mLastTxBad = txbad; + mLastTxGood = txgood; + mLastRssi = rssi; break; - case RSSI_FETCH_FAILED: - //can happen if we are waiting to get a disconnect notification - if (DBG) log("RSSI_FETCH_FAILED"); + + case WifiManager.RSSI_PKTCNT_FETCH_FAILED: + // can happen if we are waiting to get a disconnect notification + if (DBG) logd("RSSI_FETCH_FAILED"); break; + default: return NOT_HANDLED; } @@ -813,107 +855,75 @@ public class WifiWatchdogStateMachine extends StateMachine { } } - /* Child state of ConnectedState indicating that we are online - * and there is nothing to do + /** + * Child state of ConnectedState indicating that we are online and there is nothing to do. */ class OnlineState extends State { @Override public void enter() { - if (DBG) log(getName() + "\n"); + if (DBG) logd(getName()); } - } - - private boolean shouldCheckWalledGarden() { - if (!mWalledGardenTestEnabled) { - if (DBG) log("Skipping walled garden check - disabled"); - return false; - } - - long waitTime = (mWalledGardenIntervalMs + mLastWalledGardenCheckTime) - - SystemClock.elapsedRealtime(); - if (mLastWalledGardenCheckTime != 0 && waitTime > 0) { - if (DBG) { - log("Skipping walled garden check - wait " + - waitTime + " ms."); + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case EVENT_SCREEN_ON: + mIsScreenOn = true; + if (mPoorNetworkDetectionEnabled) + transitionTo(mOnlineWatchState); + break; + default: + return NOT_HANDLED; } - return false; + return HANDLED; } - return true; } - private boolean doArpTest(int type) { - boolean success; + private void updateCurrentBssid(String bssid) { + if (DBG) logd("Update current BSSID to " + (bssid != null ? bssid : "null")); - String iface = mLinkProperties.getInterfaceName(); - String mac = mWifiInfo.getMacAddress(); - InetAddress inetAddress = null; - InetAddress gateway = null; - - for (LinkAddress la : mLinkProperties.getLinkAddresses()) { - inetAddress = la.getAddress(); - break; + // if currently not connected, then set current BSSID to null + if (bssid == null) { + if (mCurrentBssid == null) return; + mCurrentBssid = null; + if (DBG) logd("BSSID changed"); + sendMessage(EVENT_BSSID_CHANGE); + return; } - for (RouteInfo route : mLinkProperties.getRoutes()) { - gateway = route.getGateway(); - break; - } + // if it is already the current BSSID, then done + if (mCurrentBssid != null && bssid.equals(mCurrentBssid.mBssid)) return; - if (DBG) log("ARP " + iface + "addr: " + inetAddress + "mac: " + mac + "gw: " + gateway); + // search for the new BSSID in the cache, add to cache if not found + mCurrentBssid = mBssidCache.get(bssid); + if (mCurrentBssid == null) { + mCurrentBssid = new BssidStatistics(bssid); + mBssidCache.put(bssid, mCurrentBssid); + } - 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++; - } - if (DBG) log("full ARP test result: " + responses + "/" + mNumArpPings); - success = (responses >= mMinArpResponses); - } - 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; - } catch (IllegalArgumentException ae) { - log("ARP test initiation failure: " + ae); - success = true; - } - - return success; + // send BSSID change notification + if (DBG) logd("BSSID changed"); + sendMessage(EVENT_BSSID_CHANGE); } private int calculateSignalLevel(int rssi) { - int signalLevel = WifiManager.calculateSignalLevel(rssi, - WifiManager.RSSI_LEVELS); - if (DBG) log("RSSI current: " + mCurrentSignalLevel + "new: " + rssi + ", " + signalLevel); + int signalLevel = WifiManager.calculateSignalLevel(rssi, WifiManager.RSSI_LEVELS); + if (DBG) + logd("RSSI current: " + mCurrentSignalLevel + " new: " + rssi + ", " + signalLevel); return signalLevel; } - private void sendPoorLinkDetected() { - if (DBG) log("send POOR_LINK_DETECTED " + mWifiInfo); - mWsmChannel.sendMessage(POOR_LINK_DETECTED); - - long time = android.os.SystemClock.elapsedRealtime(); - if (time - mLastBssidAvoidedTime > MIN_INTERVAL_AVOID_BSSID_MS[ - MIN_INTERVAL_AVOID_BSSID_MS.length - 1]) { - mMinIntervalArrayIndex = 1; - if (DBG) log("set mMinIntervalArrayIndex to 1"); + private void sendLinkStatusNotification(boolean isGood) { + if (DBG) logd("########################################"); + if (isGood) { + mWsmChannel.sendMessage(GOOD_LINK_DETECTED); + mCurrentBssid.mLastTimeGood = SystemClock.elapsedRealtime(); + logd("Good link notification is sent"); } else { - - if (mMinIntervalArrayIndex < MIN_INTERVAL_AVOID_BSSID_MS.length - 1) { - mMinIntervalArrayIndex++; - } - if (DBG) log("mMinIntervalArrayIndex: " + mMinIntervalArrayIndex); + mWsmChannel.sendMessage(POOR_LINK_DETECTED); + mCurrentBssid.mLastTimePoor = SystemClock.elapsedRealtime(); + logd("Poor link notification is sent"); } - - mLastBssidAvoidedTime = android.os.SystemClock.elapsedRealtime(); } /** @@ -932,30 +942,28 @@ public class WifiWatchdogStateMachine extends StateMachine { } /** - * Convenience function for retrieving a single secure settings value - * as a boolean. Note that internally setting values are always - * stored as strings; this function converts the string to a boolean - * for you. The default value will be returned if the setting is - * not defined or not a valid boolean. + * Convenience function for retrieving a single secure settings value as a + * boolean. Note that internally setting values are always stored as + * strings; this function converts the string to a boolean for you. The + * default value will be returned if the setting is not defined or not a + * valid boolean. * * @param cr The ContentResolver to access. * @param name The name of the setting to retrieve. * @param def Value to return if the setting is not defined. - * - * @return The setting's current value, or 'def' if it is not defined - * or not a valid boolean. + * @return The setting's current value, or 'def' if it is not defined or not + * a valid boolean. */ private static boolean getSettingsBoolean(ContentResolver cr, String name, boolean def) { return Settings.Secure.getInt(cr, name, def ? 1 : 0) == 1; } /** - * Convenience function for updating a single settings value as an - * integer. This will either create a new entry in the table if the - * given name does not exist, or modify the value of the existing row - * with that name. Note that internally setting values are always - * stored as strings, so this function converts the given value to a - * string before storing it. + * Convenience function for updating a single settings value as an integer. + * This will either create a new entry in the table if the given name does + * not exist, or modify the value of the existing row with that name. Note + * that internally setting values are always stored as strings, so this + * function converts the given value to a string before storing it. * * @param cr The ContentResolver to access. * @param name The name of the setting to modify. @@ -966,11 +974,258 @@ public class WifiWatchdogStateMachine extends StateMachine { return Settings.Secure.putInt(cr, name, value ? 1 : 0); } - private static void log(String s) { + private static void logd(String s) { Log.d(TAG, s); } private static void loge(String s) { Log.e(TAG, s); } + + /** + * Bundle of good link count parameters + */ + private static class GoodLinkTarget { + public final int RSSI_ADJ_DBM; + public final int SAMPLE_COUNT; + public final int REDUCE_TIME_MS; + public GoodLinkTarget(int adj, int count, int time) { + RSSI_ADJ_DBM = adj; + SAMPLE_COUNT = count; + REDUCE_TIME_MS = time; + } + } + + /** + * Bundle of max avoidance time parameters + */ + private static class MaxAvoidTime { + public final int TIME_MS; + public final int MIN_RSSI_DBM; + public MaxAvoidTime(int time, int rssi) { + TIME_MS = time; + MIN_RSSI_DBM = rssi; + } + } + + /** + * Volume-weighted Exponential Moving Average (V-EMA) + * - volume-weighted: each update has its own weight (number of packets) + * - exponential: O(1) time and O(1) space for both update and query + * - moving average: reflect most recent results and expire old ones + */ + private class VolumeWeightedEMA { + private double mValue; + private double mVolume; + private double mProduct; + private final double mAlpha; + + public VolumeWeightedEMA(double coefficient) { + mValue = 0.0; + mVolume = 0.0; + mProduct = 0.0; + mAlpha = coefficient; + } + + public void update(double newValue, int newVolume) { + if (newVolume <= 0) return; + // core update formulas + double newProduct = newValue * newVolume; + mProduct = mAlpha * newProduct + (1 - mAlpha) * mProduct; + mVolume = mAlpha * newVolume + (1 - mAlpha) * mVolume; + mValue = mProduct / mVolume; + } + } + + /** + * Record (RSSI -> pakce loss %) mappings of one BSSID + */ + private class BssidStatistics { + + /* MAC address of this BSSID */ + private final String mBssid; + + /* RSSI -> packet loss % mappings */ + private VolumeWeightedEMA[] mEntries; + private int mRssiBase; + private int mEntriesSize; + + /* Target to send good link notification, set when poor link is detected */ + private int mGoodLinkTargetRssi; + private int mGoodLinkTargetCount; + + /* Index of GOOD_LINK_TARGET array */ + private int mGoodLinkTargetIndex; + + /* Timestamps of some last events */ + private long mLastTimeSample; + private long mLastTimeGood; + private long mLastTimePoor; + + /* Max time to avoid this BSSID */ + private long mBssidAvoidTimeMax; + + /** + * Constructor + * + * @param bssid is the address of this BSSID + */ + public BssidStatistics(String bssid) { + this.mBssid = bssid; + mRssiBase = BSSID_STAT_RANGE_LOW_DBM; + mEntriesSize = BSSID_STAT_RANGE_HIGH_DBM - BSSID_STAT_RANGE_LOW_DBM + 1; + mEntries = new VolumeWeightedEMA[mEntriesSize]; + for (int i = 0; i < mEntriesSize; i++) + mEntries[i] = new VolumeWeightedEMA(EXP_COEFFICIENT_RECORD); + } + + /** + * Update this BSSID cache + * + * @param rssi is the RSSI + * @param value is the new instant loss value at this RSSI + * @param volume is the volume for this single update + */ + public void updateLoss(int rssi, double value, int volume) { + if (volume <= 0) return; + int index = rssi - mRssiBase; + if (index < 0 || index >= mEntriesSize) return; + mEntries[index].update(value, volume); + if (DBG) { + DecimalFormat df = new DecimalFormat("#.##"); + logd("Cache updated: loss[" + rssi + "]=" + df.format(mEntries[index].mValue * 100) + + "% volume=" + df.format(mEntries[index].mVolume)); + } + } + + /** + * Get preset loss if the cache has insufficient data, observed from experiments. + * + * @param rssi is the input RSSI + * @return preset loss of the given RSSI + */ + public double presetLoss(int rssi) { + if (rssi <= -90) return 1.0; + if (rssi > 0) return 0.0; + + if (sPresetLoss == null) { + // pre-calculate all preset losses only once, then reuse them + final int size = 90; + sPresetLoss = new double[size]; + for (int i = 0; i < size; i++) sPresetLoss[i] = 1.0 / Math.pow(90 - i, 1.5); + } + return sPresetLoss[-rssi]; + } + + /** + * A poor link is detected, calculate a target RSSI to bring WiFi back. + * + * @param rssi is the current RSSI + * @return true iff the current BSSID should be avoided + */ + public boolean poorLinkDetected(int rssi) { + if (DBG) logd("Poor link detected, rssi=" + rssi); + + long now = SystemClock.elapsedRealtime(); + long lastGood = now - mLastTimeGood; + long lastPoor = now - mLastTimePoor; + + // reduce the difficulty of good link target if last avoidance was long time ago + while (mGoodLinkTargetIndex > 0 + && lastPoor >= GOOD_LINK_TARGET[mGoodLinkTargetIndex - 1].REDUCE_TIME_MS) + mGoodLinkTargetIndex--; + mGoodLinkTargetCount = GOOD_LINK_TARGET[mGoodLinkTargetIndex].SAMPLE_COUNT; + + // scan for a target RSSI at which the link is good + int from = rssi + GOOD_LINK_RSSI_RANGE_MIN; + int to = rssi + GOOD_LINK_RSSI_RANGE_MAX; + mGoodLinkTargetRssi = findRssiTarget(from, to, GOOD_LINK_LOSS_THRESHOLD); + mGoodLinkTargetRssi += GOOD_LINK_TARGET[mGoodLinkTargetIndex].RSSI_ADJ_DBM; + if (mGoodLinkTargetIndex < GOOD_LINK_TARGET.length - 1) mGoodLinkTargetIndex++; + + // calculate max avoidance time to prevent avoiding forever + int p = 0, pmax = MAX_AVOID_TIME.length - 1; + while (p < pmax && rssi >= MAX_AVOID_TIME[p + 1].MIN_RSSI_DBM) p++; + long avoidMax = MAX_AVOID_TIME[p].TIME_MS; + + // don't avoid if max avoidance time is 0 (RSSI is super high) + if (avoidMax <= 0) return false; + + // set max avoidance time, send poor link notification + mBssidAvoidTimeMax = now + avoidMax; + + if (DBG) logd("goodRssi=" + mGoodLinkTargetRssi + " goodCount=" + mGoodLinkTargetCount + + " lastGood=" + lastGood + " lastPoor=" + lastPoor + " avoidMax=" + avoidMax); + + return true; + } + + /** + * A new BSSID is connected, recalculate target RSSI threshold + */ + public void newLinkDetected() { + // if this BSSID is currently being avoided, the reuse those values + if (mBssidAvoidTimeMax > 0) { + if (DBG) logd("Previous avoidance still in effect, rssi=" + mGoodLinkTargetRssi + + " count=" + mGoodLinkTargetCount); + return; + } + + // calculate a new RSSI threshold for new link verifying + int from = BSSID_STAT_RANGE_LOW_DBM; + int to = BSSID_STAT_RANGE_HIGH_DBM; + mGoodLinkTargetRssi = findRssiTarget(from, to, GOOD_LINK_LOSS_THRESHOLD); + mGoodLinkTargetCount = 1; + mBssidAvoidTimeMax = SystemClock.elapsedRealtime() + MAX_AVOID_TIME[0].TIME_MS; + if (DBG) logd("New link verifying target set, rssi=" + mGoodLinkTargetRssi + " count=" + + mGoodLinkTargetCount); + } + + /** + * Return the first RSSI within the range where loss[rssi] < threshold + * + * @param from start scanning from this RSSI + * @param to stop scanning at this RSSI + * @param threshold target threshold for scanning + * @return target RSSI + */ + public int findRssiTarget(int from, int to, double threshold) { + from -= mRssiBase; + to -= mRssiBase; + int emptyCount = 0; + int d = from < to ? 1 : -1; + for (int i = from; i != to; i += d) + // don't use a data point if it volume is too small (statistically unreliable) + if (i >= 0 && i < mEntriesSize && mEntries[i].mVolume > 1.0) { + emptyCount = 0; + if (mEntries[i].mValue < threshold) { + // scan target found + int rssi = mRssiBase + i; + if (DBG) { + DecimalFormat df = new DecimalFormat("#.##"); + logd("Scan target found: rssi=" + rssi + " threshold=" + + df.format(threshold * 100) + "% value=" + + df.format(mEntries[i].mValue * 100) + "% volume=" + + df.format(mEntries[i].mVolume)); + } + return rssi; + } + } else if (++emptyCount >= BSSID_STAT_EMPTY_COUNT) { + // cache has insufficient data around this RSSI, use preset loss instead + int rssi = mRssiBase + i; + double lossPreset = presetLoss(rssi); + if (lossPreset < threshold) { + if (DBG) { + DecimalFormat df = new DecimalFormat("#.##"); + logd("Scan target found: rssi=" + rssi + " threshold=" + + df.format(threshold * 100) + "% value=" + + df.format(lossPreset * 100) + "% volume=preset"); + } + return rssi; + } + } + + return mRssiBase + to; + } + } } diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pService.java b/wifi/java/android/net/wifi/p2p/WifiP2pService.java index 0212552..f269f22 100644 --- a/wifi/java/android/net/wifi/p2p/WifiP2pService.java +++ b/wifi/java/android/net/wifi/p2p/WifiP2pService.java @@ -1349,7 +1349,7 @@ public class WifiP2pService extends IWifiP2pManager.Stub { } if (mGroup.isGroupOwner()) { - stopDhcpServer(); + stopDhcpServer(mGroup.getInterface()); } else { if (DBG) logd("stop DHCP client"); mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_STOP_DHCP); @@ -1599,9 +1599,10 @@ public class WifiP2pService extends IWifiP2pManager.Stub { logd("Started Dhcp server on " + intf); } - private void stopDhcpServer() { + private void stopDhcpServer(String intf) { try { mNwService.stopTethering(); + mNwService.clearInterfaceAddresses(intf); } catch (Exception e) { loge("Error stopping Dhcp server" + e); return; |
