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.java59
-rw-r--r--wifi/java/android/net/wifi/StateChangeResult.java7
-rw-r--r--wifi/java/android/net/wifi/SupplicantStateTracker.java3
-rw-r--r--wifi/java/android/net/wifi/WifiConfigStore.java13
-rw-r--r--wifi/java/android/net/wifi/WifiConfiguration.java21
-rw-r--r--wifi/java/android/net/wifi/WifiInfo.java44
-rw-r--r--wifi/java/android/net/wifi/WifiManager.java392
-rw-r--r--wifi/java/android/net/wifi/WifiMonitor.java16
-rw-r--r--wifi/java/android/net/wifi/WifiNative.java35
-rw-r--r--wifi/java/android/net/wifi/WifiSsid.java217
-rw-r--r--wifi/java/android/net/wifi/WifiStateMachine.java573
-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/WifiP2pDevice.java34
-rw-r--r--wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java5
-rw-r--r--wifi/java/android/net/wifi/p2p/WifiP2pGroupList.java16
-rw-r--r--wifi/java/android/net/wifi/p2p/WifiP2pInfo.java4
-rw-r--r--wifi/java/android/net/wifi/p2p/WifiP2pManager.java16
-rw-r--r--wifi/java/android/net/wifi/p2p/WifiP2pService.java103
-rw-r--r--wifi/java/android/net/wifi/p2p/WifiP2pWfdInfo.java192
22 files changed, 2102 insertions, 968 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..9977419 100644
--- a/wifi/java/android/net/wifi/ScanResult.java
+++ b/wifi/java/android/net/wifi/ScanResult.java
@@ -28,6 +28,10 @@ import android.os.Parcel;
public class ScanResult implements Parcelable {
/** The network name. */
public String SSID;
+
+ /** Ascii encoded SSID. This will replace SSID when we deprecate it. @hide */
+ public WifiSsid wifiSsid;
+
/** The address of the access point. */
public String BSSID;
/**
@@ -47,19 +51,35 @@ public class ScanResult implements Parcelable {
public int frequency;
/**
- * 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}
+ * Time Synchronization Function (tsf) timestamp in microseconds when
+ * this result was last seen.
*/
- public ScanResult(String SSID, String BSSID, String caps, int level, int frequency) {
- this.SSID = SSID;
+ public long timestamp;
+
+ /** {@hide} */
+ public ScanResult(WifiSsid wifiSsid, String BSSID, String caps, int level, int frequency,
+ long tsf) {
+ this.wifiSsid = wifiSsid;
+ this.SSID = (wifiSsid != null) ? wifiSsid.toString() : WifiSsid.NONE;
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) {
+ wifiSsid = source.wifiSsid;
+ SSID = source.SSID;
+ BSSID = source.BSSID;
+ capabilities = source.capabilities;
+ level = source.level;
+ frequency = source.frequency;
+ timestamp = source.timestamp;
+ }
}
@Override
@@ -68,7 +88,7 @@ public class ScanResult implements Parcelable {
String none = "<none>";
sb.append("SSID: ").
- append(SSID == null ? none : SSID).
+ append(wifiSsid == null ? WifiSsid.NONE : wifiSsid).
append(", BSSID: ").
append(BSSID == null ? none : BSSID).
append(", capabilities: ").
@@ -76,7 +96,9 @@ public class ScanResult implements Parcelable {
append(", level: ").
append(level).
append(", frequency: ").
- append(frequency);
+ append(frequency).
+ append(", timestamp: ").
+ append(timestamp);
return sb.toString();
}
@@ -88,23 +110,34 @@ public class ScanResult implements Parcelable {
/** Implement the Parcelable interface {@hide} */
public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(SSID);
+ if (wifiSsid != null) {
+ dest.writeInt(1);
+ wifiSsid.writeToParcel(dest, flags);
+ } else {
+ dest.writeInt(0);
+ }
dest.writeString(BSSID);
dest.writeString(capabilities);
dest.writeInt(level);
dest.writeInt(frequency);
+ dest.writeLong(timestamp);
}
/** Implement the Parcelable interface {@hide} */
public static final Creator<ScanResult> CREATOR =
new Creator<ScanResult>() {
public ScanResult createFromParcel(Parcel in) {
+ WifiSsid wifiSsid = null;
+ if (in.readInt() == 1) {
+ wifiSsid = WifiSsid.CREATOR.createFromParcel(in);
+ }
return new ScanResult(
+ wifiSsid,
in.readString(),
in.readString(),
- in.readString(),
in.readInt(),
- in.readInt()
+ in.readInt(),
+ in.readLong()
);
}
diff --git a/wifi/java/android/net/wifi/StateChangeResult.java b/wifi/java/android/net/wifi/StateChangeResult.java
index b15c4a6..c334b91 100644
--- a/wifi/java/android/net/wifi/StateChangeResult.java
+++ b/wifi/java/android/net/wifi/StateChangeResult.java
@@ -23,15 +23,16 @@
* @hide
*/
public class StateChangeResult {
- StateChangeResult(int networkId, String SSID, String BSSID, SupplicantState state) {
+ StateChangeResult(int networkId, WifiSsid wifiSsid, String BSSID,
+ SupplicantState state) {
this.state = state;
- this.SSID = SSID;
+ this.wifiSsid= wifiSsid;
this.BSSID = BSSID;
this.networkId = networkId;
}
int networkId;
- String SSID;
+ WifiSsid wifiSsid;
String BSSID;
SupplicantState state;
}
diff --git a/wifi/java/android/net/wifi/SupplicantStateTracker.java b/wifi/java/android/net/wifi/SupplicantStateTracker.java
index 6aeac5f..d1e9b67 100644
--- a/wifi/java/android/net/wifi/SupplicantStateTracker.java
+++ b/wifi/java/android/net/wifi/SupplicantStateTracker.java
@@ -25,6 +25,7 @@ import android.content.Intent;
import android.os.Handler;
import android.os.Message;
import android.os.Parcelable;
+import android.os.UserHandle;
import android.util.Log;
/**
@@ -145,7 +146,7 @@ class SupplicantStateTracker extends StateMachine {
WifiManager.EXTRA_SUPPLICANT_ERROR,
WifiManager.ERROR_AUTHENTICATING);
}
- mContext.sendStickyBroadcast(intent);
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
/********************************************************
diff --git a/wifi/java/android/net/wifi/WifiConfigStore.java b/wifi/java/android/net/wifi/WifiConfigStore.java
index e9f3480..84506b6 100644
--- a/wifi/java/android/net/wifi/WifiConfigStore.java
+++ b/wifi/java/android/net/wifi/WifiConfigStore.java
@@ -36,6 +36,7 @@ import android.os.Environment;
import android.os.Message;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
@@ -601,7 +602,7 @@ class WifiConfigStore {
intent.putExtra(WifiManager.EXTRA_MULTIPLE_NETWORKS_CHANGED, false);
intent.putExtra(WifiManager.EXTRA_WIFI_CONFIGURATION, network);
intent.putExtra(WifiManager.EXTRA_CHANGE_REASON, reason);
- mContext.sendBroadcast(intent);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
}
/**
@@ -611,7 +612,7 @@ class WifiConfigStore {
Intent intent = new Intent(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
intent.putExtra(WifiManager.EXTRA_MULTIPLE_NETWORKS_CHANGED, true);
- mContext.sendBroadcast(intent);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
}
void loadConfiguredNetworks() {
@@ -1317,7 +1318,13 @@ class WifiConfigStore {
value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.ssidVarName);
if (!TextUtils.isEmpty(value)) {
- config.SSID = value;
+ if (value.charAt(0) != '"') {
+ config.SSID = "\"" + WifiSsid.createFromHex(value).toString() + "\"";
+ //TODO: convert a hex string that is not UTF-8 decodable to a P-formatted
+ //supplicant string
+ } else {
+ config.SSID = value;
+ }
} else {
config.SSID = null;
}
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 0a846fd..c4fe1b4 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -524,6 +524,27 @@ public class WifiConfiguration implements Parcelable {
}
*/
+ /** {@hide} */
+ public String getPrintableSsid() {
+ if (SSID == null) return "";
+ final int length = SSID.length();
+ if (length > 2 && (SSID.charAt(0) == '"') && SSID.charAt(length - 1) == '"') {
+ return SSID.substring(1, length - 1);
+ }
+
+ /** The ascii-encoded string format is P"<ascii-encoded-string>"
+ * The decoding is implemented in the supplicant for a newly configured
+ * network.
+ */
+ if (length > 3 && (SSID.charAt(0) == 'P') && (SSID.charAt(1) == '"') &&
+ (SSID.charAt(length-1) == '"')) {
+ WifiSsid wifiSsid = WifiSsid.createFromAsciiEncoded(
+ SSID.substring(2, length - 1));
+ return wifiSsid.toString();
+ }
+ return SSID;
+ }
+
private static BitSet readBitSet(Parcel src) {
int cardinality = src.readInt();
diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java
index 1f1cfdd..05db571 100644
--- a/wifi/java/android/net/wifi/WifiInfo.java
+++ b/wifi/java/android/net/wifi/WifiInfo.java
@@ -20,6 +20,7 @@ import android.os.Parcelable;
import android.os.Parcel;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkUtils;
+import android.text.TextUtils;
import java.net.InetAddress;
import java.net.Inet6Address;
@@ -31,6 +32,7 @@ import java.util.EnumMap;
* is in the process of being set up.
*/
public class WifiInfo implements Parcelable {
+ private static final String TAG = "WifiInfo";
/**
* This is the map described in the Javadoc comment above. The positions
* of the elements of the array must correspond to the ordinal values
@@ -57,7 +59,7 @@ public class WifiInfo implements Parcelable {
private SupplicantState mSupplicantState;
private String mBSSID;
- private String mSSID;
+ private WifiSsid mWifiSsid;
private int mNetworkId;
private boolean mHiddenSSID;
/** Received Signal Strength Indicator */
@@ -77,7 +79,7 @@ public class WifiInfo implements Parcelable {
private boolean mMeteredHint;
WifiInfo() {
- mSSID = null;
+ mWifiSsid = null;
mBSSID = null;
mNetworkId = -1;
mSupplicantState = SupplicantState.UNINITIALIZED;
@@ -94,7 +96,7 @@ public class WifiInfo implements Parcelable {
if (source != null) {
mSupplicantState = source.mSupplicantState;
mBSSID = source.mBSSID;
- mSSID = source.mSSID;
+ mWifiSsid = source.mWifiSsid;
mNetworkId = source.mNetworkId;
mHiddenSSID = source.mHiddenSSID;
mRssi = source.mRssi;
@@ -105,21 +107,34 @@ public class WifiInfo implements Parcelable {
}
}
- void setSSID(String SSID) {
- mSSID = SSID;
+ void setSSID(WifiSsid wifiSsid) {
+ mWifiSsid = wifiSsid;
// network is considered not hidden by default
mHiddenSSID = false;
}
/**
* Returns the service set identifier (SSID) of the current 802.11 network.
- * If the SSID is an ASCII string, it will be returned surrounded by double
- * quotation marks.Otherwise, it is returned as a string of hex digits. The
+ * If the SSID can be decoded as UTF-8, it will be returned surrounded by double
+ * quotation marks. Otherwise, it is returned as a string of hex digits. The
* SSID may be {@code null} if there is no network currently connected.
* @return the SSID
*/
public String getSSID() {
- return mSSID;
+ if (mWifiSsid != null) {
+ String unicode = mWifiSsid.toString();
+ if (!TextUtils.isEmpty(unicode)) {
+ return "\"" + unicode + "\"";
+ } else {
+ return mWifiSsid.getHexString();
+ }
+ }
+ return WifiSsid.NONE;
+ }
+
+ /** @hide */
+ public WifiSsid getWifiSsid() {
+ return mWifiSsid;
}
void setBSSID(String BSSID) {
@@ -279,7 +294,7 @@ public class WifiInfo implements Parcelable {
StringBuffer sb = new StringBuffer();
String none = "<none>";
- sb.append("SSID: ").append(mSSID == null ? none : mSSID).
+ sb.append("SSID: ").append(mWifiSsid == null ? WifiSsid.NONE : mWifiSsid).
append(", BSSID: ").append(mBSSID == null ? none : mBSSID).
append(", MAC: ").append(mMacAddress == null ? none : mMacAddress).
append(", Supplicant state: ").
@@ -308,7 +323,12 @@ public class WifiInfo implements Parcelable {
} else {
dest.writeByte((byte)0);
}
- dest.writeString(getSSID());
+ if (mWifiSsid != null) {
+ dest.writeInt(1);
+ mWifiSsid.writeToParcel(dest, flags);
+ } else {
+ dest.writeInt(0);
+ }
dest.writeString(mBSSID);
dest.writeString(mMacAddress);
dest.writeInt(mMeteredHint ? 1 : 0);
@@ -328,7 +348,9 @@ public class WifiInfo implements Parcelable {
info.setInetAddress(InetAddress.getByAddress(in.createByteArray()));
} catch (UnknownHostException e) {}
}
- info.setSSID(in.readString());
+ if (in.readInt() == 1) {
+ info.mWifiSsid = WifiSsid.CREATOR.createFromParcel(in);
+ }
info.mBSSID = in.readString();
info.mMacAddress = in.readString();
info.mMeteredHint = in.readInt() != 0;
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/WifiMonitor.java b/wifi/java/android/net/wifi/WifiMonitor.java
index 17c930b..ab54a15 100644
--- a/wifi/java/android/net/wifi/WifiMonitor.java
+++ b/wifi/java/android/net/wifi/WifiMonitor.java
@@ -645,9 +645,12 @@ public class WifiMonitor {
* id=network-id state=new-state
*/
private void handleSupplicantStateChange(String dataString) {
- String SSID = null;
+ WifiSsid wifiSsid = null;
int index = dataString.lastIndexOf("SSID=");
- if (index != -1) SSID = dataString.substring(index + 5);
+ if (index != -1) {
+ wifiSsid = WifiSsid.createFromAsciiEncoded(
+ dataString.substring(index + 5));
+ }
String[] dataTokens = dataString.split(" ");
String BSSID = null;
@@ -690,7 +693,7 @@ public class WifiMonitor {
if (newSupplicantState == SupplicantState.INVALID) {
Log.w(TAG, "Invalid supplicant state: " + newState);
}
- notifySupplicantStateChange(networkId, SSID, BSSID, newSupplicantState);
+ notifySupplicantStateChange(networkId, wifiSsid, BSSID, newSupplicantState);
}
}
@@ -739,13 +742,14 @@ public class WifiMonitor {
* Send the state machine a notification that the state of the supplicant
* has changed.
* @param networkId the configured network on which the state change occurred
- * @param SSID network name
+ * @param wifiSsid network name
* @param BSSID network address
* @param newState the new {@code SupplicantState}
*/
- void notifySupplicantStateChange(int networkId, String SSID, String BSSID, SupplicantState newState) {
+ void notifySupplicantStateChange(int networkId, WifiSsid wifiSsid, String BSSID,
+ SupplicantState newState) {
mStateMachine.sendMessage(mStateMachine.obtainMessage(SUPPLICANT_STATE_CHANGE_EVENT,
- new StateChangeResult(networkId, SSID, BSSID, newState)));
+ new StateChangeResult(networkId, wifiSsid, BSSID, newState)));
}
/**
diff --git a/wifi/java/android/net/wifi/WifiNative.java b/wifi/java/android/net/wifi/WifiNative.java
index e520185..4c5fc5d 100644
--- a/wifi/java/android/net/wifi/WifiNative.java
+++ b/wifi/java/android/net/wifi/WifiNative.java
@@ -49,6 +49,7 @@ public class WifiNative {
static final int BLUETOOTH_COEXISTENCE_MODE_SENSE = 2;
String mInterface = "";
+ private boolean mSuspendOptEnabled = false;
public native static boolean loadDriver();
@@ -197,8 +198,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() {
@@ -335,6 +350,8 @@ public class WifiNative {
}
public boolean setSuspendOptimizations(boolean enabled) {
+ if (mSuspendOptEnabled == enabled) return true;
+ mSuspendOptEnabled = enabled;
if (enabled) {
return doBooleanCommand("DRIVER SETSUSPENDMODE 1");
} else {
@@ -368,6 +385,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");
@@ -478,6 +503,14 @@ public class WifiNative {
}
}
+ public boolean setWfdEnable(boolean enable) {
+ return doBooleanCommand("SET wifi_display " + (enable ? "1" : "0"));
+ }
+
+ public boolean setWfdDeviceInfo(String hex) {
+ return doBooleanCommand("WFD_SUBELEM_SET 0 " + hex);
+ }
+
/**
* "sta" prioritizes STA connection over P2P and "p2p" prioritizes
* P2P connection over STA
diff --git a/wifi/java/android/net/wifi/WifiSsid.java b/wifi/java/android/net/wifi/WifiSsid.java
new file mode 100644
index 0000000..6f36111
--- /dev/null
+++ b/wifi/java/android/net/wifi/WifiSsid.java
@@ -0,0 +1,217 @@
+/*
+ * 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.Parcelable;
+import android.os.Parcel;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.CodingErrorAction;
+
+/**
+ * Stores SSID octets and handles conversion.
+ *
+ * For Ascii encoded string, any octet < 32 or > 127 is encoded as
+ * a "\x" followed by the hex representation of the octet.
+ * Exception chars are ", \, \e, \n, \r, \t which are escaped by a \
+ * See src/utils/common.c for the implementation in the supplicant.
+ *
+ * @hide
+ */
+public class WifiSsid implements Parcelable {
+ private static final String TAG = "WifiSsid";
+
+ public ByteArrayOutputStream octets = new ByteArrayOutputStream(32);
+
+ private static final int HEX_RADIX = 16;
+ public static final String NONE = "<unknown ssid>";
+
+ private WifiSsid() {
+ }
+
+ public static WifiSsid createFromAsciiEncoded(String asciiEncoded) {
+ WifiSsid a = new WifiSsid();
+ a.convertToBytes(asciiEncoded);
+ return a;
+ }
+
+ public static WifiSsid createFromHex(String hexStr) {
+ WifiSsid a = new WifiSsid();
+ int length = 0;
+ if (hexStr == null) return a;
+
+ if (hexStr.startsWith("0x") || hexStr.startsWith("0X")) {
+ hexStr = hexStr.substring(2);
+ }
+
+ for (int i = 0; i < hexStr.length()-1; i += 2) {
+ int val;
+ try {
+ val = Integer.parseInt(hexStr.substring(i, i + 2), HEX_RADIX);
+ } catch(NumberFormatException e) {
+ val = 0;
+ }
+ a.octets.write(val);
+ }
+ return a;
+ }
+
+ /* This function is equivalent to printf_decode() at src/utils/common.c in
+ * the supplicant */
+ private void convertToBytes(String asciiEncoded) {
+ int i = 0;
+ int val = 0;
+ while (i< asciiEncoded.length()) {
+ char c = asciiEncoded.charAt(i);
+ switch (c) {
+ case '\\':
+ i++;
+ switch(asciiEncoded.charAt(i)) {
+ case '\\':
+ octets.write('\\');
+ break;
+ case '"':
+ octets.write('"');
+ break;
+ case 'n':
+ octets.write('\n');
+ break;
+ case 'r':
+ octets.write('\r');
+ break;
+ case 't':
+ octets.write('\t');
+ break;
+ case 'e':
+ octets.write(27); //escape char
+ break;
+ case 'x':
+ i++;
+ try {
+ val = Integer.parseInt(asciiEncoded.substring(i, i + 2), HEX_RADIX);
+ } catch (NumberFormatException e) {
+ val = -1;
+ }
+ if (val < 0) {
+ val = Character.digit(asciiEncoded.charAt(i), HEX_RADIX);
+ if (val < 0) break;
+ octets.write(val);
+ i++;
+ } else {
+ octets.write(val);
+ i += 2;
+ }
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ val = asciiEncoded.charAt(i) - '0';
+ i++;
+ if (asciiEncoded.charAt(i) >= '0' && asciiEncoded.charAt(i) <= '7') {
+ val = val * 8 + asciiEncoded.charAt(i) - '0';
+ i++;
+ }
+ if (asciiEncoded.charAt(i) >= '0' && asciiEncoded.charAt(i) <= '7') {
+ val = val * 8 + asciiEncoded.charAt(i) - '0';
+ i++;
+ }
+ octets.write(val);
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ octets.write(c);
+ i++;
+ break;
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ if (octets.size() <= 0) return "";
+ // TODO: Handle conversion to other charsets upon failure
+ Charset charset = Charset.forName("UTF-8");
+ CharsetDecoder decoder = charset.newDecoder()
+ .onMalformedInput(CodingErrorAction.REPLACE)
+ .onUnmappableCharacter(CodingErrorAction.REPLACE);
+ CharBuffer out = CharBuffer.allocate(32);
+
+ CoderResult result = decoder.decode(ByteBuffer.wrap(octets.toByteArray()), out, true);
+ out.flip();
+ if (result.isError()) {
+ return NONE;
+ }
+ return out.toString();
+ }
+
+ /** @hide */
+ public byte[] getOctets() {
+ return octets.toByteArray();
+ }
+
+ /** @hide */
+ public String getHexString() {
+ String out = "0x";
+ byte[] ssidbytes = getOctets();
+ for (int i = 0; i < octets.size(); i++) {
+ out += String.format("%02x", ssidbytes[i]);
+ }
+ return out;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(octets.size());
+ dest.writeByteArray(octets.toByteArray());
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public static final Creator<WifiSsid> CREATOR =
+ new Creator<WifiSsid>() {
+ public WifiSsid createFromParcel(Parcel in) {
+ WifiSsid ssid = new WifiSsid();
+ int length = in.readInt();
+ byte b[] = new byte[length];
+ in.readByteArray(b);
+ ssid.octets.write(b, 0, length);
+ return ssid;
+ }
+
+ public WifiSsid[] newArray(int size) {
+ return new WifiSsid[size];
+ }
+ };
+}
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index 15eb9b9..4aa092b 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;
@@ -71,12 +68,14 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.UserHandle;
import android.os.WorkSource;
import android.provider.Settings;
import android.util.EventLog;
import android.util.Log;
import android.util.LruCache;
+import com.android.internal.R;
import com.android.internal.app.IBatteryStats;
import com.android.internal.util.AsyncChannel;
import com.android.internal.util.Protocol;
@@ -94,13 +93,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 +106,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;
@@ -120,10 +113,11 @@ public class WifiStateMachine extends StateMachine {
private ConnectivityManager mCm;
private final boolean mP2pSupported;
+ private final AtomicBoolean mP2pConnected = new AtomicBoolean(false);
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;
@@ -146,13 +140,13 @@ public class WifiStateMachine extends StateMachine {
private boolean mScanResultIsPending = false;
/* Tracks if the current scan settings are active */
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 state machine has received any screen state change broadcast yet.
+ * We can miss one of these at boot.
+ */
+ private AtomicBoolean mScreenBroadcastReceived = new AtomicBoolean(false);
private boolean mBluetoothConnectionActive = false;
- private BroadcastReceiver mScreenReceiver;
- private IntentFilter mScreenFilter;
private PowerManager.WakeLock mSuspendWakeLock;
/**
@@ -263,6 +257,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;
@@ -342,10 +338,8 @@ public class WifiStateMachine extends StateMachine {
static final int CMD_START_PACKET_FILTERING = BASE + 84;
/* Clear packet filter */
static final int CMD_STOP_PACKET_FILTERING = BASE + 85;
- /* Set suspend mode optimizations in the driver */
- static final int CMD_SET_SUSPEND_OPTIMIZATIONS = BASE + 86;
- /* Clear suspend mode optimizations in the driver */
- static final int CMD_CLEAR_SUSPEND_OPTIMIZATIONS = BASE + 87;
+ /* Enable suspend mode optimizations in the driver */
+ static final int CMD_SET_SUSPEND_OPT_ENABLED = BASE + 86;
/* When there are no saved networks, we do a periodic scan to notify user of
* an open network */
static final int CMD_NO_NETWORKS_PERIODIC_SCAN = BASE + 88;
@@ -390,21 +384,32 @@ public class WifiStateMachine extends StateMachine {
*/
private static final int DEFAULT_MAX_DHCP_RETRIES = 9;
- /* Tracks if power save is enabled in driver */
- private boolean mPowerSaveEnabled = true;;
+ /* Tracks if suspend optimizations need to be disabled by DHCP,
+ * screen or due to high perf mode.
+ * When any of them needs to disable it, we keep the suspend optimizations
+ * disabled
+ */
+ private int mSuspendOptNeedsDisabled = 0;
+
+ private static final int SUSPEND_DUE_TO_DHCP = 1;
+ private static final int SUSPEND_DUE_TO_HIGH_PERF = 1<<1;
+ private static final int SUSPEND_DUE_TO_SCREEN = 1<<2;
+
+ /* Tracks if user has enabled suspend optimizations through settings */
+ private AtomicBoolean mUserWantsSuspendOpt = new AtomicBoolean(true);
/**
* Default framework scan interval in milliseconds. This is used in the scenario in which
* wifi chipset does not support background scanning to set up a
* periodic wake up scan so that the device can connect to a new access
- * point on the move. {@link Settings.Secure#WIFI_FRAMEWORK_SCAN_INTERVAL_MS} can
+ * point on the move. {@link Settings.Global#WIFI_FRAMEWORK_SCAN_INTERVAL_MS} can
* override this.
*/
private final int mDefaultFrameworkScanIntervalMs;
/**
* Supplicant scan interval in milliseconds.
- * Comes from {@link Settings.Secure#WIFI_SUPPLICANT_SCAN_INTERVAL_MS} or
+ * Comes from {@link Settings.Global#WIFI_SUPPLICANT_SCAN_INTERVAL_MS} or
* from the default config if the setting is not set
*/
private long mSupplicantScanIntervalMs;
@@ -466,6 +471,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 */
@@ -590,16 +597,19 @@ public class WifiStateMachine extends StateMachine {
mScanIntent = PendingIntent.getBroadcast(mContext, SCAN_REQUEST, scanIntent, 0);
mDefaultFrameworkScanIntervalMs = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_wifi_framework_scan_interval);
+ R.integer.config_wifi_framework_scan_interval);
mDriverStopDelayMs = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_wifi_driver_stop_delay);
+ R.integer.config_wifi_driver_stop_delay);
mBackgroundScanSupported = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_wifi_background_scan_support);
+ R.bool.config_wifi_background_scan_support);
mPrimaryDeviceType = mContext.getResources().getString(
- com.android.internal.R.string.config_wifi_p2p_device_type);
+ R.string.config_wifi_p2p_device_type);
+
+ mUserWantsSuspendOpt.set(Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED, 1) == 1);
mContext.registerReceiver(
new BroadcastReceiver() {
@@ -622,32 +632,23 @@ public class WifiStateMachine extends StateMachine {
},
new IntentFilter(ACTION_START_SCAN));
- mScreenFilter = new IntentFilter();
- mScreenFilter.addAction(Intent.ACTION_SCREEN_ON);
- mScreenFilter.addAction(Intent.ACTION_SCREEN_OFF);
- mScreenReceiver = new BroadcastReceiver() {
+ IntentFilter screenFilter = new IntentFilter();
+ screenFilter.addAction(Intent.ACTION_SCREEN_ON);
+ screenFilter.addAction(Intent.ACTION_SCREEN_OFF);
+ BroadcastReceiver screenReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(Intent.ACTION_SCREEN_ON)) {
- enableRssiPolling(true);
- if (mBackgroundScanSupported) {
- enableBackgroundScanCommand(false);
- }
- enableAllNetworks();
- sendMessage(CMD_CLEAR_SUSPEND_OPTIMIZATIONS);
+ handleScreenStateChanged(true);
} 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);
+ handleScreenStateChanged(false);
}
}
};
+ mContext.registerReceiver(screenReceiver, screenFilter);
+
mContext.registerReceiver(
new BroadcastReceiver() {
@Override
@@ -658,6 +659,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) {
+ mUserWantsSuspendOpt.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 +693,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 +864,10 @@ public class WifiStateMachine extends StateMachine {
}
}
+ public void captivePortalCheckComplete() {
+ sendMessage(obtainMessage(CMD_CAPTIVE_CHECK_COMPLETE));
+ }
+
/**
* TODO: doc
*/
@@ -878,7 +894,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 +1165,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("mUserWantsSuspendOpt ").append(mUserWantsSuspendOpt).append(LS);
+ sb.append("mSuspendOptNeedsDisabled ").append(mSuspendOptNeedsDisabled).append(LS);
sb.append("Supplicant status").append(LS)
.append(mWifiNative.status()).append(LS).append(LS);
@@ -1160,8 +1184,7 @@ public class WifiStateMachine extends StateMachine {
case CMD_START_DRIVER:
case CMD_SET_SCAN_MODE:
case CMD_SET_HIGH_PERF_MODE:
- case CMD_SET_SUSPEND_OPTIMIZATIONS:
- case CMD_CLEAR_SUSPEND_OPTIMIZATIONS:
+ case CMD_SET_SUSPEND_OPT_ENABLED:
case CMD_ENABLE_BACKGROUND_SCAN:
case CMD_ENABLE_ALL_NETWORKS:
return false;
@@ -1174,7 +1197,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;
@@ -1185,6 +1208,26 @@ public class WifiStateMachine extends StateMachine {
* Internal private functions
********************************************************/
+ private void handleScreenStateChanged(boolean screenOn) {
+ if (DBG) log("handleScreenStateChanged: " + screenOn);
+ enableRssiPolling(screenOn);
+ if (mBackgroundScanSupported) {
+ enableBackgroundScanCommand(screenOn == false);
+ }
+
+ if (screenOn) enableAllNetworks();
+ if (mUserWantsSuspendOpt.get()) {
+ if (screenOn) {
+ sendMessage(obtainMessage(CMD_SET_SUSPEND_OPT_ENABLED, 0, 0));
+ } else {
+ //Allow 2s for suspend optimizations to be set
+ mSuspendWakeLock.acquire(2000);
+ sendMessage(obtainMessage(CMD_SET_SUSPEND_OPT_ENABLED, 1, 0));
+ }
+ }
+ mScreenBroadcastReceived.set(true);
+ }
+
private void checkAndSetConnectivityInstance() {
if (mCm == null) {
mCm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
@@ -1293,6 +1336,30 @@ public class WifiStateMachine extends StateMachine {
setFrequencyBand(band, false);
}
+ private void setSuspendOptimizationsNative(int reason, boolean enabled) {
+ if (DBG) log("setSuspendOptimizationsNative: " + reason + " " + enabled);
+ if (enabled) {
+ mSuspendOptNeedsDisabled &= ~reason;
+ /* None of dhcp, screen or highperf need it disabled and user wants it enabled */
+ if (mSuspendOptNeedsDisabled == 0 && mUserWantsSuspendOpt.get()) {
+ mWifiNative.setSuspendOptimizations(true);
+ }
+ } else {
+ mSuspendOptNeedsDisabled |= reason;
+ mWifiNative.setSuspendOptimizations(false);
+ }
+ }
+
+ private void setSuspendOptimizations(int reason, boolean enabled) {
+ if (DBG) log("setSuspendOptimizations: " + reason + " " + enabled);
+ if (enabled) {
+ mSuspendOptNeedsDisabled &= ~reason;
+ } else {
+ mSuspendOptNeedsDisabled |= reason;
+ }
+ if (DBG) log("mSuspendOptNeedsDisabled " + mSuspendOptNeedsDisabled);
+ }
+
private void setWifiState(int wifiState) {
final int previousWifiState = mWifiState.get();
@@ -1314,7 +1381,7 @@ public class WifiStateMachine extends StateMachine {
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
intent.putExtra(WifiManager.EXTRA_WIFI_STATE, wifiState);
intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_STATE, previousWifiState);
- mContext.sendStickyBroadcast(intent);
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
private void setWifiApState(int wifiApState) {
@@ -1339,134 +1406,110 @@ public class WifiStateMachine extends StateMachine {
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
intent.putExtra(WifiManager.EXTRA_WIFI_AP_STATE, wifiApState);
intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE, previousWifiApState);
- mContext.sendStickyBroadcast(intent);
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
+ 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 = "";
+ WifiSsid wifiSsid = null;
+
+ 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)) {
+ wifiSsid = WifiSsid.createFromAsciiEncoded(
+ line.substring(SSID_STR.length()));
+ } else if (line.startsWith(DELIMITER_STR)) {
+ if (bssid != null) {
+ String ssid = (wifiSsid != null) ? wifiSsid.toString() : WifiSsid.NONE;
+ String key = bssid + ssid;
+ ScanResult scanResult = mScanResultCache.get(key);
+ if (scanResult != null) {
+ scanResult.level = level;
+ scanResult.wifiSsid = wifiSsid;
+ // Keep existing API
+ scanResult.SSID = (wifiSsid != null) ? wifiSsid.toString() :
+ WifiSsid.NONE;
+ 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);
+ wifiSsid, 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 = "";
+ wifiSsid = null;
}
}
}
-
- 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 +1568,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);
@@ -1550,14 +1617,14 @@ public class WifiStateMachine extends StateMachine {
private void sendScanResultsAvailableBroadcast() {
Intent intent = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- mContext.sendBroadcast(intent);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
}
private void sendRssiChangeBroadcast(final int newRssi) {
Intent intent = new Intent(WifiManager.RSSI_CHANGED_ACTION);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
intent.putExtra(WifiManager.EXTRA_NEW_RSSI, newRssi);
- mContext.sendBroadcast(intent);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
}
private void sendNetworkStateChangeBroadcast(String bssid) {
@@ -1571,21 +1638,21 @@ public class WifiStateMachine extends StateMachine {
mNetworkInfo.getDetailedState() == DetailedState.CONNECTED) {
intent.putExtra(WifiManager.EXTRA_WIFI_INFO, new WifiInfo(mWifiInfo));
}
- mContext.sendStickyBroadcast(intent);
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
private void sendLinkConfigurationChangedBroadcast() {
Intent intent = new Intent(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
intent.putExtra(WifiManager.EXTRA_LINK_PROPERTIES, new LinkProperties(mLinkProperties));
- mContext.sendBroadcast(intent);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
}
private void sendSupplicantConnectionChangedBroadcast(boolean connected) {
Intent intent = new Intent(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
intent.putExtra(WifiManager.EXTRA_SUPPLICANT_CONNECTED, connected);
- mContext.sendBroadcast(intent);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
}
/**
@@ -1599,7 +1666,7 @@ public class WifiStateMachine extends StateMachine {
}
if (state != mNetworkInfo.getDetailedState()) {
- mNetworkInfo.setDetailedState(state, null, null);
+ mNetworkInfo.setDetailedState(state, null, mWifiInfo.getSSID());
}
}
@@ -1624,11 +1691,8 @@ 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.setSSID(stateChangeResult.SSID);
+ mWifiInfo.setBSSID(stateChangeResult.BSSID);
+ mWifiInfo.setSSID(stateChangeResult.wifiSsid);
mSupplicantStateTracker.sendMessage(Message.obtain(message));
@@ -1649,10 +1713,7 @@ public class WifiStateMachine extends StateMachine {
/* In case we were in middle of DHCP operation
restore back powermode */
handlePostDhcpSetup();
-
mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_STOP_DHCP);
- mDhcpStateMachine.doQuit();
- mDhcpStateMachine = null;
}
try {
@@ -1711,18 +1772,19 @@ public class WifiStateMachine extends StateMachine {
mWifiNative.BLUETOOTH_COEXISTENCE_MODE_DISABLED);
}
- /* Disable power save during DHCP */
- if (mPowerSaveEnabled) {
- mPowerSaveEnabled = false;
- mWifiNative.setPowerSave(mPowerSaveEnabled);
- }
+ /* Disable power save and suspend optimizations during DHCP */
+ // Note: The order here is important for now. Brcm driver changes
+ // power settings when we control suspend mode optimizations.
+ // TODO: Remove this comment when the driver is fixed.
+ setSuspendOptimizationsNative(SUSPEND_DUE_TO_DHCP, false);
+ mWifiNative.setPowerSave(false);
}
void handlePostDhcpSetup() {
- /* Restore power save */
- mPowerSaveEnabled = true;
- mWifiNative.setPowerSave(mPowerSaveEnabled);
+ /* Restore power save and suspend optimizations */
+ setSuspendOptimizationsNative(SUSPEND_DUE_TO_DHCP, true);
+ mWifiNative.setPowerSave(true);
// Set the coexistence mode back to its default value
mWifiNative.setBluetoothCoexistenceMode(
@@ -1794,12 +1856,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);
@@ -1855,7 +1917,11 @@ public class WifiStateMachine extends StateMachine {
mEnableBackgroundScan = (message.arg1 == 1);
break;
case CMD_SET_HIGH_PERF_MODE:
- mHighPerfMode = (message.arg1 == 1);
+ if (message.arg1 == 1) {
+ setSuspendOptimizations(SUSPEND_DUE_TO_HIGH_PERF, false);
+ } else {
+ setSuspendOptimizations(SUSPEND_DUE_TO_HIGH_PERF, true);
+ }
break;
/* Discard */
case CMD_LOAD_DRIVER:
@@ -1902,11 +1968,18 @@ public class WifiStateMachine extends StateMachine {
case CMD_RESPONSE_AP_CONFIG:
case WifiWatchdogStateMachine.POOR_LINK_DETECTED:
case WifiWatchdogStateMachine.GOOD_LINK_DETECTED:
- case CMD_CLEAR_SUSPEND_OPTIMIZATIONS:
case CMD_NO_NETWORKS_PERIODIC_SCAN:
break;
- case CMD_SET_SUSPEND_OPTIMIZATIONS:
- mSuspendWakeLock.release();
+ case DhcpStateMachine.CMD_ON_QUIT:
+ mDhcpStateMachine = null;
+ break;
+ case CMD_SET_SUSPEND_OPT_ENABLED:
+ if (message.arg1 == 1) {
+ mSuspendWakeLock.release();
+ setSuspendOptimizations(SUSPEND_DUE_TO_SCREEN, true);
+ } else {
+ setSuspendOptimizations(SUSPEND_DUE_TO_SCREEN, false);
+ }
break;
case WifiMonitor.DRIVER_HUNG_EVENT:
setWifiEnabled(false);
@@ -1936,8 +2009,13 @@ 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;
+ case WifiP2pService.P2P_CONNECTION_CHANGED:
+ NetworkInfo info = (NetworkInfo) message.obj;
+ mP2pConnected.set(info.isConnected());
break;
default:
loge("Error! unhandled message" + message);
@@ -2336,10 +2414,10 @@ public class WifiStateMachine extends StateMachine {
mNetworkInfo.setIsAvailable(true);
int defaultInterval = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_wifi_supplicant_scan_interval);
+ R.integer.config_wifi_supplicant_scan_interval);
- mSupplicantScanIntervalMs = Settings.Secure.getLong(mContext.getContentResolver(),
- Settings.Secure.WIFI_SUPPLICANT_SCAN_INTERVAL_MS,
+ mSupplicantScanIntervalMs = Settings.Global.getLong(mContext.getContentResolver(),
+ Settings.Global.WIFI_SUPPLICANT_SCAN_INTERVAL_MS,
defaultInterval);
mWifiNative.setScanInterval((int)mSupplicantScanIntervalMs / 1000);
@@ -2474,6 +2552,9 @@ public class WifiStateMachine extends StateMachine {
/* Send any reset commands to supplicant before shutting it down */
handleNetworkDisconnect();
+ if (mDhcpStateMachine != null) {
+ mDhcpStateMachine.doQuit();
+ }
if (DBG) log("stopping supplicant");
if (!mWifiNative.stopSupplicant()) {
@@ -2638,8 +2719,6 @@ public class WifiStateMachine extends StateMachine {
mWifiNative.stopFilteringMulticastV4Packets();
}
- mWifiNative.setPowerSave(mPowerSaveEnabled);
-
if (mIsScanMode) {
mWifiNative.setScanResultHandling(SCAN_ONLY_MODE);
mWifiNative.disconnect();
@@ -2654,9 +2733,19 @@ public class WifiStateMachine extends StateMachine {
transitionTo(mDisconnectedState);
}
- if (mP2pSupported) mWifiP2pChannel.sendMessage(WifiStateMachine.CMD_ENABLE_P2P);
+ // We may have missed screen update at boot
+ if (mScreenBroadcastReceived.get() == false) {
+ PowerManager powerManager = (PowerManager)mContext.getSystemService(
+ Context.POWER_SERVICE);
+ handleScreenStateChanged(powerManager.isScreenOn());
+ } else {
+ // Set the right suspend mode settings
+ mWifiNative.setSuspendOptimizations(mSuspendOptNeedsDisabled == 0
+ && mUserWantsSuspendOpt.get());
+ }
+ mWifiNative.setPowerSave(true);
- mContext.registerReceiver(mScreenReceiver, mScreenFilter);
+ if (mP2pSupported) mWifiP2pChannel.sendMessage(WifiStateMachine.CMD_ENABLE_P2P);
}
@Override
public boolean processMessage(Message message) {
@@ -2765,20 +2854,19 @@ public class WifiStateMachine extends StateMachine {
loge("Illegal arugments to CMD_STOP_PACKET_FILTERING");
}
break;
- case CMD_SET_SUSPEND_OPTIMIZATIONS:
- if (!mHighPerfMode) {
- mWifiNative.setSuspendOptimizations(true);
+ case CMD_SET_SUSPEND_OPT_ENABLED:
+ if (message.arg1 == 1) {
+ setSuspendOptimizationsNative(SUSPEND_DUE_TO_SCREEN, true);
+ mSuspendWakeLock.release();
+ } else {
+ setSuspendOptimizationsNative(SUSPEND_DUE_TO_SCREEN, false);
}
- mSuspendWakeLock.release();
- break;
- case CMD_CLEAR_SUSPEND_OPTIMIZATIONS:
- mWifiNative.setSuspendOptimizations(false);
break;
case CMD_SET_HIGH_PERF_MODE:
- mHighPerfMode = (message.arg1 == 1);
- if (mHighPerfMode) {
- //Disable any suspend optimizations
- mWifiNative.setSuspendOptimizations(false);
+ if (message.arg1 == 1) {
+ setSuspendOptimizationsNative(SUSPEND_DUE_TO_HIGH_PERF, false);
+ } else {
+ setSuspendOptimizationsNative(SUSPEND_DUE_TO_HIGH_PERF, true);
}
break;
default:
@@ -2791,10 +2879,9 @@ 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 +3227,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;
@@ -3171,8 +3260,11 @@ public class WifiStateMachine extends StateMachine {
if (!mWifiConfigStore.isUsingStaticIp(mLastNetworkId)) {
//start DHCP
- mDhcpStateMachine = DhcpStateMachine.makeDhcpStateMachine(
- mContext, WifiStateMachine.this, mInterfaceName);
+ if (mDhcpStateMachine == null) {
+ mDhcpStateMachine = DhcpStateMachine.makeDhcpStateMachine(
+ mContext, WifiStateMachine.this, mInterfaceName);
+
+ }
mDhcpStateMachine.registerForPreDhcpNotification();
mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_START_DHCP);
} else {
@@ -3236,6 +3328,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 +3355,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);
@@ -3354,8 +3465,8 @@ public class WifiStateMachine extends StateMachine {
if (DBG) log(getName() + "\n");
EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
- mFrameworkScanIntervalMs = Settings.Secure.getLong(mContext.getContentResolver(),
- Settings.Secure.WIFI_FRAMEWORK_SCAN_INTERVAL_MS,
+ mFrameworkScanIntervalMs = Settings.Global.getLong(mContext.getContentResolver(),
+ Settings.Global.WIFI_FRAMEWORK_SCAN_INTERVAL_MS,
mDefaultFrameworkScanIntervalMs);
/*
* We initiate background scanning if it is enabled, otherwise we
@@ -3381,7 +3492,7 @@ public class WifiStateMachine extends StateMachine {
* The scans are useful to notify the user of the presence of an open network.
* Note that these are not wake up scans.
*/
- if (mWifiConfigStore.getConfiguredNetworks().size() == 0) {
+ if (!mP2pConnected.get() && mWifiConfigStore.getConfiguredNetworks().size() == 0) {
sendMessageDelayed(obtainMessage(CMD_NO_NETWORKS_PERIODIC_SCAN,
++mPeriodicScanToken, 0), mSupplicantScanIntervalMs);
}
@@ -3392,6 +3503,7 @@ public class WifiStateMachine extends StateMachine {
boolean ret = HANDLED;
switch (message.what) {
case CMD_NO_NETWORKS_PERIODIC_SCAN:
+ if (mP2pConnected.get()) break;
if (message.arg1 == mPeriodicScanToken &&
mWifiConfigStore.getConfiguredNetworks().size() == 0) {
sendMessage(CMD_START_SCAN);
@@ -3452,6 +3564,21 @@ public class WifiStateMachine extends StateMachine {
/* Handled in parent state */
ret = NOT_HANDLED;
break;
+ case WifiP2pService.P2P_CONNECTION_CHANGED:
+ NetworkInfo info = (NetworkInfo) message.obj;
+ mP2pConnected.set(info.isConnected());
+ if (mP2pConnected.get()) {
+ int defaultInterval = mContext.getResources().getInteger(
+ R.integer.config_wifi_scan_interval_p2p_connected);
+ long scanIntervalMs = Settings.Global.getLong(mContext.getContentResolver(),
+ Settings.Global.WIFI_SCAN_INTERVAL_WHEN_P2P_CONNECTED_MS,
+ defaultInterval);
+ mWifiNative.setScanInterval((int) scanIntervalMs/1000);
+ } else if (mWifiConfigStore.getConfiguredNetworks().size() == 0) {
+ if (DBG) log("Turn on scanning after p2p disconnected");
+ sendMessageDelayed(obtainMessage(CMD_NO_NETWORKS_PERIODIC_SCAN,
+ ++mPeriodicScanToken, 0), mSupplicantScanIntervalMs);
+ }
default:
ret = NOT_HANDLED;
}
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/WifiP2pDevice.java b/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java
index c86ec8b..863a055 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java
@@ -107,12 +107,14 @@ public class WifiP2pDevice implements Parcelable {
/** Device connection status */
public int status = UNAVAILABLE;
- /** Detailed device string pattern
+ /** @hide */
+ public WifiP2pWfdInfo wfdInfo;
+
+ /** Detailed device string pattern with WFD info
* Example:
- * P2P-DEVICE-FOUND fa:7b:7a:42:02:13 p2p_dev_addr=fa:7b:7a:42:02:13
- * pri_dev_type=1-0050F204-1 name='p2p-TEST1' config_methods=0x188 dev_capab=0x27
- * group_capab=0x0
- *
+ * P2P-DEVICE-FOUND 00:18:6b:de:a3:6e p2p_dev_addr=00:18:6b:de:a3:6e
+ * pri_dev_type=1-0050F204-1 name='DWD-300-DEA36E' config_methods=0x188
+ * dev_capab=0x21 group_capab=0x9
*/
private static final Pattern detailedDevicePattern = Pattern.compile(
"((?:[0-9a-f]{2}:){5}[0-9a-f]{2}) " +
@@ -122,7 +124,8 @@ public class WifiP2pDevice implements Parcelable {
"name='(.*)' " +
"config_methods=(0x[0-9a-fA-F]+) " +
"dev_capab=(0x[0-9a-fA-F]+) " +
- "group_capab=(0x[0-9a-fA-F]+)"
+ "group_capab=(0x[0-9a-fA-F]+)" +
+ "( wfd_dev_info=0x000006([0-9a-fA-F]{12}))?"
);
/** 2 token device address pattern
@@ -151,7 +154,7 @@ public class WifiP2pDevice implements Parcelable {
* @param string formats supported include
* P2P-DEVICE-FOUND fa:7b:7a:42:02:13 p2p_dev_addr=fa:7b:7a:42:02:13
* pri_dev_type=1-0050F204-1 name='p2p-TEST1' config_methods=0x188 dev_capab=0x27
- * group_capab=0x0
+ * group_capab=0x0 wfd_dev_info=000006015d022a0032
*
* P2P-DEVICE-LOST p2p_dev_addr=fa:7b:7a:42:02:13
*
@@ -203,6 +206,12 @@ public class WifiP2pDevice implements Parcelable {
wpsConfigMethodsSupported = parseHex(match.group(6));
deviceCapability = parseHex(match.group(7));
groupCapability = parseHex(match.group(8));
+ if (match.group(9) != null) {
+ String str = match.group(10);
+ wfdInfo = new WifiP2pWfdInfo(parseHex(str.substring(0,4)),
+ parseHex(str.substring(4,8)),
+ parseHex(str.substring(8,12)));
+ }
break;
}
@@ -273,6 +282,7 @@ public class WifiP2pDevice implements Parcelable {
sbuf.append("\n grpcapab: ").append(groupCapability);
sbuf.append("\n devcapab: ").append(deviceCapability);
sbuf.append("\n status: ").append(status);
+ sbuf.append("\n wfdInfo: ").append(wfdInfo);
return sbuf.toString();
}
@@ -292,6 +302,7 @@ public class WifiP2pDevice implements Parcelable {
deviceCapability = source.deviceCapability;
groupCapability = source.groupCapability;
status = source.status;
+ wfdInfo = source.wfdInfo;
}
}
@@ -305,6 +316,12 @@ public class WifiP2pDevice implements Parcelable {
dest.writeInt(deviceCapability);
dest.writeInt(groupCapability);
dest.writeInt(status);
+ if (wfdInfo != null) {
+ dest.writeInt(1);
+ wfdInfo.writeToParcel(dest, flags);
+ } else {
+ dest.writeInt(0);
+ }
}
/** Implement the Parcelable interface */
@@ -320,6 +337,9 @@ public class WifiP2pDevice implements Parcelable {
device.deviceCapability = in.readInt();
device.groupCapability = in.readInt();
device.status = in.readInt();
+ if (in.readInt() == 1) {
+ device.wfdInfo = WifiP2pWfdInfo.CREATOR.createFromParcel(in);
+ }
return device;
}
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java b/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java
index 48cdbc2..2093bda 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java
@@ -35,10 +35,9 @@ import java.util.HashMap;
*/
public class WifiP2pDeviceList implements Parcelable {
- private HashMap<String, WifiP2pDevice> mDevices;
+ private final HashMap<String, WifiP2pDevice> mDevices = new HashMap<String, WifiP2pDevice>();
public WifiP2pDeviceList() {
- mDevices = new HashMap<String, WifiP2pDevice>();
}
/** copy constructor */
@@ -52,7 +51,6 @@ public class WifiP2pDeviceList implements Parcelable {
/** @hide */
public WifiP2pDeviceList(ArrayList<WifiP2pDevice> devices) {
- mDevices = new HashMap<String, WifiP2pDevice>();
for (WifiP2pDevice device : devices) {
if (device.deviceAddress != null) {
mDevices.put(device.deviceAddress, device);
@@ -78,6 +76,7 @@ public class WifiP2pDeviceList implements Parcelable {
d.wpsConfigMethodsSupported = device.wpsConfigMethodsSupported;
d.deviceCapability = device.deviceCapability;
d.groupCapability = device.groupCapability;
+ d.wfdInfo = device.wfdInfo;
return;
}
//Not found, add a new one
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pGroupList.java b/wifi/java/android/net/wifi/p2p/WifiP2pGroupList.java
index 3459a5a..98f0972 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pGroupList.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pGroupList.java
@@ -16,6 +16,7 @@
package android.net.wifi.p2p;
import java.util.Collection;
+import java.util.Map;
import android.os.Parcel;
import android.os.Parcelable;
@@ -32,8 +33,9 @@ public class WifiP2pGroupList implements Parcelable {
private static final int CREDENTIAL_MAX_NUM = 32;
- private LruCache<Integer, WifiP2pGroup> mGroups;
- private GroupDeleteListener mListener;
+ private final LruCache<Integer, WifiP2pGroup> mGroups;
+ private final GroupDeleteListener mListener;
+
private boolean isClearCalled = false;
public interface GroupDeleteListener {
@@ -41,10 +43,10 @@ public class WifiP2pGroupList implements Parcelable {
}
WifiP2pGroupList() {
- this(null);
+ this(null, null);
}
- WifiP2pGroupList(GroupDeleteListener listener) {
+ WifiP2pGroupList(WifiP2pGroupList source, GroupDeleteListener listener) {
mListener = listener;
mGroups = new LruCache<Integer, WifiP2pGroup>(CREDENTIAL_MAX_NUM) {
@Override
@@ -55,6 +57,12 @@ public class WifiP2pGroupList implements Parcelable {
}
}
};
+
+ if (source != null) {
+ for (Map.Entry<Integer, WifiP2pGroup> item : source.mGroups.snapshot().entrySet()) {
+ mGroups.put(item.getKey(), item.getValue());
+ }
+ }
}
/**
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pInfo.java b/wifi/java/android/net/wifi/p2p/WifiP2pInfo.java
index dce315a..8972b7e 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pInfo.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pInfo.java
@@ -44,8 +44,8 @@ public class WifiP2pInfo implements Parcelable {
public String toString() {
StringBuffer sbuf = new StringBuffer();
sbuf.append("groupFormed: ").append(groupFormed)
- .append("isGroupOwner: ").append(isGroupOwner)
- .append("groupOwnerAddress: ").append(groupOwnerAddress);
+ .append(" isGroupOwner: ").append(isGroupOwner)
+ .append(" groupOwnerAddress: ").append(groupOwnerAddress);
return sbuf.toString();
}
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
index 96d3a7f..6edc232 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
@@ -455,6 +455,13 @@ public class WifiP2pManager {
/** @hide */
public static final int RESPONSE_PERSISTENT_GROUP_INFO = BASE + 63;
+ /** @hide */
+ public static final int SET_WFD_INFO = BASE + 64;
+ /** @hide */
+ public static final int SET_WFD_INFO_FAILED = BASE + 65;
+ /** @hide */
+ public static final int SET_WFD_INFO_SUCCEEDED = BASE + 66;
+
/**
* Create a new WifiP2pManager instance. Applications use
* {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
@@ -742,6 +749,7 @@ public class WifiP2pManager {
case WifiP2pManager.CLEAR_SERVICE_REQUESTS_FAILED:
case WifiP2pManager.SET_DEVICE_NAME_FAILED:
case WifiP2pManager.DELETE_PERSISTENT_GROUP_FAILED:
+ case WifiP2pManager.SET_WFD_INFO_FAILED:
if (listener != null) {
((ActionListener) listener).onFailure(message.arg1);
}
@@ -762,6 +770,7 @@ public class WifiP2pManager {
case WifiP2pManager.CLEAR_SERVICE_REQUESTS_SUCCEEDED:
case WifiP2pManager.SET_DEVICE_NAME_SUCCEEDED:
case WifiP2pManager.DELETE_PERSISTENT_GROUP_SUCCEEDED:
+ case WifiP2pManager.SET_WFD_INFO_SUCCEEDED:
if (listener != null) {
((ActionListener) listener).onSuccess();
}
@@ -1297,6 +1306,13 @@ public class WifiP2pManager {
c.mAsyncChannel.sendMessage(SET_DEVICE_NAME, 0, c.putListener(listener), d);
}
+ /** @hide */
+ public void setWFDInfo(
+ Channel c, WifiP2pWfdInfo wfdInfo,
+ ActionListener listener) {
+ checkChannel(c);
+ c.mAsyncChannel.sendMessage(SET_WFD_INFO, 0, c.putListener(listener), wfdInfo);
+ }
/**
* Set dialog listener to over-ride system dialogs on p2p events. This function
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pService.java b/wifi/java/android/net/wifi/p2p/WifiP2pService.java
index 0be2b27..35dd764 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pService.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pService.java
@@ -62,6 +62,7 @@ import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
+import android.os.UserHandle;
import android.os.Parcelable.Creator;
import android.provider.Settings;
import android.text.TextUtils;
@@ -134,19 +135,7 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
private static final int DISCOVER_TIMEOUT_S = 120;
/* Idle time after a peer is gone when the group is torn down */
- private static final int GROUP_IDLE_TIME_S = 2;
-
- /**
- * Delay between restarts upon failure to setup connection with supplicant
- */
- private static final int P2P_RESTART_INTERVAL_MSECS = 5000;
-
- /**
- * Number of times we attempt to restart p2p
- */
- private static final int P2P_RESTART_TRIES = 5;
-
- private int mP2pRestartCount = 0;
+ private static final int GROUP_IDLE_TIME_S = 5;
private static final int BASE = Protocol.BASE_WIFI_P2P_SERVICE;
@@ -158,6 +147,9 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
/* User rejected a peer request */
private static final int PEER_CONNECTION_USER_REJECT = BASE + 3;
+ /* Commands to the WifiStateMachine */
+ public static final int P2P_CONNECTION_CHANGED = BASE + 11;
+
private final boolean mP2pSupported;
private WifiP2pDevice mThisDevice = new WifiP2pDevice();
@@ -358,8 +350,8 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
private WifiNative mWifiNative = new WifiNative(mInterface);
private WifiMonitor mWifiMonitor = new WifiMonitor(this, mWifiNative);
- private WifiP2pDeviceList mPeers = new WifiP2pDeviceList();
- private WifiP2pGroupList mGroups = new WifiP2pGroupList(
+ private final WifiP2pDeviceList mPeers = new WifiP2pDeviceList();
+ private final WifiP2pGroupList mGroups = new WifiP2pGroupList(null,
new GroupDeleteListener() {
@Override
public void onDeleteGroup(int netId) {
@@ -369,7 +361,7 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
sendP2pPersistentGroupsChangedBroadcast();
}
});
- private WifiP2pInfo mWifiP2pInfo = new WifiP2pInfo();
+ private final WifiP2pInfo mWifiP2pInfo = new WifiP2pInfo();
private WifiP2pGroup mGroup;
// Saved WifiP2pConfig for a peer connection
@@ -495,18 +487,25 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
replyToMessage(message, WifiP2pManager.DELETE_PERSISTENT_GROUP,
WifiP2pManager.BUSY);
break;
+ case WifiP2pManager.SET_WFD_INFO:
+ replyToMessage(message, WifiP2pManager.SET_WFD_INFO_FAILED,
+ WifiP2pManager.BUSY);
+ break;
case WifiP2pManager.REQUEST_PEERS:
- replyToMessage(message, WifiP2pManager.RESPONSE_PEERS, mPeers);
+ replyToMessage(message, WifiP2pManager.RESPONSE_PEERS,
+ new WifiP2pDeviceList(mPeers));
break;
case WifiP2pManager.REQUEST_CONNECTION_INFO:
- replyToMessage(message, WifiP2pManager.RESPONSE_CONNECTION_INFO, mWifiP2pInfo);
+ replyToMessage(message, WifiP2pManager.RESPONSE_CONNECTION_INFO,
+ new WifiP2pInfo(mWifiP2pInfo));
break;
case WifiP2pManager.REQUEST_GROUP_INFO:
- replyToMessage(message, WifiP2pManager.RESPONSE_GROUP_INFO, mGroup);
+ replyToMessage(message, WifiP2pManager.RESPONSE_GROUP_INFO,
+ mGroup != null ? new WifiP2pGroup(mGroup) : null);
break;
case WifiP2pManager.REQUEST_PERSISTENT_GROUP_INFO:
replyToMessage(message, WifiP2pManager.RESPONSE_PERSISTENT_GROUP_INFO,
- mGroups);
+ new WifiP2pGroupList(mGroups, null));
break;
case WifiP2pManager.SET_DIALOG_LISTENER:
String appPkgName = (String)message.getData().getString(
@@ -628,6 +627,10 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
replyToMessage(message, WifiP2pManager.DELETE_PERSISTENT_GROUP,
WifiP2pManager.P2P_UNSUPPORTED);
break;
+ case WifiP2pManager.SET_WFD_INFO:
+ replyToMessage(message, WifiP2pManager.SET_WFD_INFO_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
default:
return NOT_HANDLED;
}
@@ -740,6 +743,7 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
transitionTo(mP2pDisablingState);
break;
case WifiP2pManager.SET_DEVICE_NAME:
+ {
WifiP2pDevice d = (WifiP2pDevice) message.obj;
if (d != null && setAndPersistDeviceName(d.deviceName)) {
if (DBG) logd("set device name " + d.deviceName);
@@ -749,6 +753,18 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
WifiP2pManager.ERROR);
}
break;
+ }
+ case WifiP2pManager.SET_WFD_INFO:
+ {
+ WifiP2pWfdInfo d = (WifiP2pWfdInfo) message.obj;
+ if (d != null && setWfdInfo(d)) {
+ replyToMessage(message, WifiP2pManager.SET_WFD_INFO_SUCCEEDED);
+ } else {
+ replyToMessage(message, WifiP2pManager.SET_WFD_INFO_FAILED,
+ WifiP2pManager.ERROR);
+ }
+ break;
+ }
case WifiP2pManager.DISCOVER_PEERS:
// do not send service discovery request while normal find operation.
clearSupplicantServiceRequest();
@@ -1349,7 +1365,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);
@@ -1475,7 +1491,7 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
public void exit() {
mSavedProvDiscDevice = null;
updateThisDevice(WifiP2pDevice.AVAILABLE);
- setWifiP2pInfoOnGroupTermination();
+ resetWifiP2pInfo();
mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null, null);
sendP2pConnectionChangedBroadcast();
}
@@ -1534,7 +1550,7 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
intent.putExtra(WifiP2pManager.EXTRA_WIFI_STATE,
WifiP2pManager.WIFI_P2P_STATE_DISABLED);
}
- mContext.sendStickyBroadcast(intent);
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
private void sendP2pDiscoveryChangedBroadcast(boolean started) {
@@ -1548,20 +1564,20 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
intent.putExtra(WifiP2pManager.EXTRA_DISCOVERY_STATE, started ?
WifiP2pManager.WIFI_P2P_DISCOVERY_STARTED :
WifiP2pManager.WIFI_P2P_DISCOVERY_STOPPED);
- mContext.sendStickyBroadcast(intent);
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
private void sendThisDeviceChangedBroadcast() {
final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_DEVICE, new WifiP2pDevice(mThisDevice));
- mContext.sendStickyBroadcast(intent);
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
private void sendP2pPeersChangedBroadcast() {
final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- mContext.sendBroadcast(intent);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
}
private void sendP2pConnectionChangedBroadcast() {
@@ -1571,14 +1587,16 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
| Intent.FLAG_RECEIVER_REPLACE_PENDING);
intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO, new WifiP2pInfo(mWifiP2pInfo));
intent.putExtra(WifiP2pManager.EXTRA_NETWORK_INFO, new NetworkInfo(mNetworkInfo));
- mContext.sendStickyBroadcast(intent);
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ mWifiChannel.sendMessage(WifiP2pService.P2P_CONNECTION_CHANGED,
+ new NetworkInfo(mNetworkInfo));
}
private void sendP2pPersistentGroupsChangedBroadcast() {
if (DBG) logd("sending p2p persistent groups changed broadcast");
Intent intent = new Intent(WifiP2pManager.WIFI_P2P_PERSISTENT_GROUPS_CHANGED_ACTION);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- mContext.sendStickyBroadcast(intent);
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
private void startDhcpServer(String intf) {
@@ -1599,9 +1617,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;
@@ -1953,7 +1972,7 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
mWifiP2pInfo.groupOwnerAddress = NetworkUtils.numericToInetAddress(serverAddress);
}
- private void setWifiP2pInfoOnGroupTermination() {
+ private void resetWifiP2pInfo() {
mWifiP2pInfo.groupFormed = false;
mWifiP2pInfo.isGroupOwner = false;
mWifiP2pInfo.groupOwnerAddress = null;
@@ -2016,6 +2035,27 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
return true;
}
+ private boolean setWfdInfo(WifiP2pWfdInfo wfdInfo) {
+ boolean success;
+
+ if (!wfdInfo.isWfdEnabled()) {
+ success = mWifiNative.setWfdEnable(false);
+ } else {
+ success =
+ mWifiNative.setWfdEnable(true)
+ && mWifiNative.setWfdDeviceInfo(wfdInfo.getDeviceInfoHex());
+ }
+
+ if (!success) {
+ loge("Failed to set wfd properties");
+ return false;
+ }
+
+ mThisDevice.wfdInfo = wfdInfo;
+ sendThisDeviceChangedBroadcast();
+ return true;
+ }
+
private void initializeP2pSettings() {
mWifiNative.setPersistentReconnect(true);
mThisDevice.deviceName = getPersistedDeviceName();
@@ -2048,6 +2088,9 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
}
private void handleGroupCreationFailure() {
+ resetWifiP2pInfo();
+ mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.FAILED, null, null);
+ sendP2pConnectionChangedBroadcast();
mSavedPeerConfig = null;
/* After cancelling group formation, new connections on existing peers can fail
* at supplicant. Flush and restart discovery */
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pWfdInfo.java b/wifi/java/android/net/wifi/p2p/WifiP2pWfdInfo.java
new file mode 100644
index 0000000..b6bbfc4
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pWfdInfo.java
@@ -0,0 +1,192 @@
+/*
+ * 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.p2p;
+
+import android.os.Parcelable;
+import android.os.Parcel;
+
+/**
+ * A class representing Wifi Display information for a device
+ * @hide
+ */
+public class WifiP2pWfdInfo implements Parcelable {
+
+ private static final String TAG = "WifiP2pWfdInfo";
+
+ private boolean mWfdEnabled;
+
+ private int mDeviceInfo;
+
+ public static final int WFD_SOURCE = 0;
+ public static final int PRIMARY_SINK = 1;
+ public static final int SECONDARY_SINK = 2;
+ public static final int SOURCE_OR_PRIMARY_SINK = 3;
+
+ /* Device information bitmap */
+ /** One of {@link #WFD_SOURCE}, {@link #PRIMARY_SINK}, {@link #SECONDARY_SINK}
+ * or {@link #SOURCE_OR_PRIMARY_SINK}
+ */
+ private static final int DEVICE_TYPE = 0x3;
+ private static final int COUPLED_SINK_SUPPORT_AT_SOURCE = 0x4;
+ private static final int COUPLED_SINK_SUPPORT_AT_SINK = 0x8;
+ private static final int SESSION_AVAILABLE = 0x30;
+ private static final int SESSION_AVAILABLE_BIT1 = 0x10;
+ private static final int SESSION_AVAILABLE_BIT2 = 0x20;
+
+ private int mCtrlPort;
+
+ private int mMaxThroughput;
+
+ public WifiP2pWfdInfo() {
+ }
+
+ public WifiP2pWfdInfo(int devInfo, int ctrlPort, int maxTput) {
+ mWfdEnabled = true;
+ mDeviceInfo = devInfo;
+ mCtrlPort = ctrlPort;
+ mMaxThroughput = maxTput;
+ }
+
+ public boolean isWfdEnabled() {
+ return mWfdEnabled;
+ }
+
+ public void setWfdEnabled(boolean enabled) {
+ mWfdEnabled = enabled;
+ }
+
+ public int getDeviceType() {
+ return (mDeviceInfo & DEVICE_TYPE);
+ }
+
+ public boolean setDeviceType(int deviceType) {
+ if (deviceType >= WFD_SOURCE && deviceType <= SOURCE_OR_PRIMARY_SINK) {
+ mDeviceInfo |= deviceType;
+ return true;
+ }
+ return false;
+ }
+
+ public boolean isCoupledSinkSupportedAtSource() {
+ return (mDeviceInfo & COUPLED_SINK_SUPPORT_AT_SINK) != 0;
+ }
+
+ public void setCoupledSinkSupportAtSource(boolean enabled) {
+ if (enabled ) {
+ mDeviceInfo |= COUPLED_SINK_SUPPORT_AT_SINK;
+ } else {
+ mDeviceInfo &= ~COUPLED_SINK_SUPPORT_AT_SINK;
+ }
+ }
+
+ public boolean isCoupledSinkSupportedAtSink() {
+ return (mDeviceInfo & COUPLED_SINK_SUPPORT_AT_SINK) != 0;
+ }
+
+ public void setCoupledSinkSupportAtSink(boolean enabled) {
+ if (enabled ) {
+ mDeviceInfo |= COUPLED_SINK_SUPPORT_AT_SINK;
+ } else {
+ mDeviceInfo &= ~COUPLED_SINK_SUPPORT_AT_SINK;
+ }
+ }
+
+ public boolean isSessionAvailable() {
+ return (mDeviceInfo & SESSION_AVAILABLE) != 0;
+ }
+
+ public void setSessionAvailable(boolean enabled) {
+ if (enabled) {
+ mDeviceInfo |= SESSION_AVAILABLE_BIT1;
+ mDeviceInfo &= ~SESSION_AVAILABLE_BIT2;
+ } else {
+ mDeviceInfo &= ~SESSION_AVAILABLE;
+ }
+ }
+
+ public int getControlPort() {
+ return mCtrlPort;
+ }
+
+ public void setControlPort(int port) {
+ mCtrlPort = port;
+ }
+
+ public void setMaxThroughput(int maxThroughput) {
+ mMaxThroughput = maxThroughput;
+ }
+
+ public int getMaxThroughput() {
+ return mMaxThroughput;
+ }
+
+ public String getDeviceInfoHex() {
+ return String.format("%04x%04x%04x%04x", 6, mDeviceInfo, mCtrlPort, mMaxThroughput);
+ }
+
+ public String toString() {
+ StringBuffer sbuf = new StringBuffer();
+ sbuf.append("WFD enabled: ").append(mWfdEnabled);
+ sbuf.append("WFD DeviceInfo: ").append(mDeviceInfo);
+ sbuf.append("\n WFD CtrlPort: ").append(mCtrlPort);
+ sbuf.append("\n WFD MaxThroughput: ").append(mMaxThroughput);
+ return sbuf.toString();
+ }
+
+ /** Implement the Parcelable interface */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** copy constructor */
+ public WifiP2pWfdInfo(WifiP2pWfdInfo source) {
+ if (source != null) {
+ mDeviceInfo = source.mDeviceInfo;
+ mCtrlPort = source.mCtrlPort;
+ mMaxThroughput = source.mMaxThroughput;
+ }
+ }
+
+ /** Implement the Parcelable interface */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mWfdEnabled ? 1 : 0);
+ dest.writeInt(mDeviceInfo);
+ dest.writeInt(mCtrlPort);
+ dest.writeInt(mMaxThroughput);
+ }
+
+ public void readFromParcel(Parcel in) {
+ mWfdEnabled = (in.readInt() == 1);
+ mDeviceInfo = in.readInt();
+ mCtrlPort = in.readInt();
+ mMaxThroughput = in.readInt();
+ }
+
+ /** Implement the Parcelable interface */
+ public static final Creator<WifiP2pWfdInfo> CREATOR =
+ new Creator<WifiP2pWfdInfo>() {
+ public WifiP2pWfdInfo createFromParcel(Parcel in) {
+ WifiP2pWfdInfo device = new WifiP2pWfdInfo();
+ device.readFromParcel(in);
+ return device;
+ }
+
+ public WifiP2pWfdInfo[] newArray(int size) {
+ return new WifiP2pWfdInfo[size];
+ }
+ };
+}