summaryrefslogtreecommitdiffstats
path: root/wifi/java
diff options
context:
space:
mode:
Diffstat (limited to 'wifi/java')
-rw-r--r--wifi/java/android/net/wifi/IWifiManager.aidl2
-rw-r--r--wifi/java/android/net/wifi/RssiPacketCountInfo.java71
-rw-r--r--wifi/java/android/net/wifi/ScanResult.java30
-rw-r--r--wifi/java/android/net/wifi/WifiManager.java392
-rw-r--r--wifi/java/android/net/wifi/WifiNative.java24
-rw-r--r--wifi/java/android/net/wifi/WifiStateMachine.java335
-rw-r--r--wifi/java/android/net/wifi/WifiStateTracker.java14
-rw-r--r--wifi/java/android/net/wifi/WifiWatchdogStateMachine.java1233
-rw-r--r--wifi/java/android/net/wifi/p2p/WifiP2pService.java5
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;