resolve merge conflicts of 849c5c7 to mnc-dev

This resovles the merge conflict for ag/1514448/
After Android M, this function uses num_bssid instead of num_ap.
Both are prone to stack overflow attacks.

Bug: 31856351
Test: compile, unit tests, manual test

Change-Id: I194850a4c79ddf478d98e750f65b24e82d99ebc0
diff --git a/service/Android.mk b/service/Android.mk
old mode 100755
new mode 100644
index fd7b41c..f325782
--- a/service/Android.mk
+++ b/service/Android.mk
@@ -16,16 +16,14 @@
 
 ifneq ($(TARGET_BUILD_PDK), true)
 
-# Make HAL stub library
+# Make HAL stub library 1
 # ============================================================
 
 include $(CLEAR_VARS)
 
 LOCAL_REQUIRED_MODULES :=
 
-LOCAL_CFLAGS += -Wno-unused-parameter -Wno-int-to-pointer-cast
-LOCAL_CFLAGS += -Wno-maybe-uninitialized -Wno-parentheses
-LOCAL_CPPFLAGS += -Wno-conversion-null
+LOCAL_CFLAGS += -Wno-unused-parameter
 
 LOCAL_C_INCLUDES += \
 	external/libnl-headers \
@@ -38,6 +36,27 @@
 
 include $(BUILD_STATIC_LIBRARY)
 
+# Make HAL stub library 2
+# ============================================================
+
+include $(CLEAR_VARS)
+
+LOCAL_REQUIRED_MODULES :=
+
+LOCAL_CFLAGS += -Wno-unused-parameter
+
+LOCAL_C_INCLUDES += \
+	$(LOCAL_PATH)/jni \
+	external/libnl-headers \
+	$(call include-path-for, libhardware_legacy)/hardware_legacy
+
+LOCAL_SRC_FILES := \
+	lib/wifi_hal_stub.cpp
+
+LOCAL_MODULE := libwifi-hal-stub
+
+include $(BUILD_STATIC_LIBRARY)
+
 # set correct hal library path
 # ============================================================
 LIB_WIFI_HAL := libwifi-hal
@@ -45,9 +64,7 @@
 ifeq ($(BOARD_WLAN_DEVICE), bcmdhd)
   LIB_WIFI_HAL := libwifi-hal-bcm
 else ifeq ($(BOARD_WLAN_DEVICE), qcwcn)
-  # this is commented because none of the nexus devices
-  # that sport Qualcomm's wifi have support for HAL
-  # LIB_WIFI_HAL := libwifi-hal-qcom
+  LIB_WIFI_HAL := libwifi-hal-qcom
 else ifeq ($(BOARD_WLAN_DEVICE), mrvl)
   # this is commented because none of the nexus devices
   # that sport Marvell's wifi have support for HAL
@@ -57,46 +74,13 @@
   LIB_WIFI_HAL := libwifi-hal-mt66xx
 endif
 
-# Build the HalUtil
-# ============================================================
-
-include $(CLEAR_VARS)
-
-LOCAL_REQUIRED_MODULES := libandroid_runtime libhardware_legacy
-
-LOCAL_CFLAGS += -Wno-unused-parameter -Wno-int-to-pointer-cast
-LOCAL_CFLAGS += -Wno-maybe-uninitialized -Wno-parentheses
-LOCAL_CPPFLAGS += -Wno-conversion-null
-
-LOCAL_C_INCLUDES += \
-	$(call include-path-for, libhardware)/hardware \
-	$(call include-path-for, libhardware_legacy)/hardware_legacy \
-	libcore/include
-
-LOCAL_SHARED_LIBRARIES += \
-	libcutils \
-	libnl \
-	libandroid_runtime \
-	libutils
-
-LOCAL_STATIC_LIBRARIES += $(LIB_WIFI_HAL)
-
-LOCAL_SRC_FILES := \
-	tools/halutil/halutil.cpp
-
-LOCAL_MODULE := halutil
-
-include $(BUILD_EXECUTABLE)
-
 # Make the JNI part
 # ============================================================
 include $(CLEAR_VARS)
 
 LOCAL_REQUIRED_MODULES := libandroid_runtime libhardware_legacy
 
-LOCAL_CFLAGS += -Wno-unused-parameter -Wno-int-to-pointer-cast
-LOCAL_CFLAGS += -Wno-maybe-uninitialized -Wno-parentheses
-LOCAL_CPPFLAGS += -Wno-conversion-null
+LOCAL_CFLAGS += -Wno-unused-parameter
 
 LOCAL_C_INCLUDES += \
 	$(JNI_H_INCLUDE) \
@@ -111,8 +95,10 @@
 	libhardware \
 	libhardware_legacy \
 	libandroid_runtime \
-    libnl
+	libnl \
+    libdl
 
+LOCAL_STATIC_LIBRARIES += libwifi-hal-stub
 LOCAL_STATIC_LIBRARIES += $(LIB_WIFI_HAL)
 
 LOCAL_SRC_FILES := \
@@ -135,7 +121,7 @@
 
 LOCAL_JNI_SHARED_LIBRARIES := libandroid_runtime
 LOCAL_JAVA_LIBRARIES := bouncycastle conscrypt services
-LOCAL_STATIC_JAVA_LIBRARIES := ksoap2
+LOCAL_STATIC_JAVA_LIBRARIES := ksoap2 android-support-v4
 LOCAL_REQUIRED_MODULES := services
 LOCAL_MODULE_TAGS :=
 LOCAL_MODULE := wifi-service
diff --git a/service/java/com/android/server/wifi/ConfigurationMap.java b/service/java/com/android/server/wifi/ConfigurationMap.java
new file mode 100644
index 0000000..0fb56ea
--- /dev/null
+++ b/service/java/com/android/server/wifi/ConfigurationMap.java
@@ -0,0 +1,146 @@
+package com.android.server.wifi;
+
+import android.net.wifi.WifiConfiguration;
+import android.util.Log;
+
+import com.android.server.wifi.hotspot2.Utils;
+import com.android.server.wifi.hotspot2.pps.HomeSP;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+public class ConfigurationMap {
+    private final Map<Integer, WifiConfiguration> mPerID = new HashMap<>();
+    private final Map<Integer, WifiConfiguration> mPerConfigKey = new HashMap<>();
+    private final Map<String, Integer> mPerFQDN = new HashMap<>();
+
+    // RW methods:
+    public WifiConfiguration put(int netid, WifiConfiguration config) {
+        WifiConfiguration current = mPerID.put(netid, config);
+        mPerConfigKey.put(config.configKey().hashCode(), config);   // This is ridiculous...
+        if (config.FQDN != null && config.FQDN.length() > 0) {
+            mPerFQDN.put(config.FQDN, netid);
+        }
+        return current;
+    }
+
+    public void populatePasspointData(Collection<HomeSP> homeSPs, WifiNative wifiNative) {
+        mPerFQDN.clear();
+
+        for (HomeSP homeSp : homeSPs) {
+            String fqdn = homeSp.getFQDN();
+            Log.d(WifiConfigStore.TAG, "Looking for " + fqdn);
+            for (WifiConfiguration config : mPerID.values()) {
+                Log.d(WifiConfigStore.TAG, "Testing " + config.SSID);
+
+                String id_str = Utils.unquote(wifiNative.getNetworkVariable(
+                        config.networkId, WifiConfigStore.idStringVarName));
+                if (id_str != null && id_str.equals(fqdn) && config.enterpriseConfig != null) {
+                    Log.d(WifiConfigStore.TAG, "Matched " + id_str + " with " + config.networkId);
+                    config.FQDN = fqdn;
+                    config.providerFriendlyName = homeSp.getFriendlyName();
+
+                    HashSet<Long> roamingConsortiumIds = homeSp.getRoamingConsortiums();
+                    config.roamingConsortiumIds = new long[roamingConsortiumIds.size()];
+                    int i = 0;
+                    for (long id : roamingConsortiumIds) {
+                        config.roamingConsortiumIds[i] = id;
+                        i++;
+                    }
+                    IMSIParameter imsiParameter = homeSp.getCredential().getImsi();
+                    config.enterpriseConfig.setPlmn(
+                            imsiParameter != null ? imsiParameter.toString() : null);
+                    config.enterpriseConfig.setRealm(homeSp.getCredential().getRealm());
+                    mPerFQDN.put(fqdn, config.networkId);
+                }
+            }
+        }
+
+        Log.d(WifiConfigStore.TAG, "loaded " + mPerFQDN.size() + " passpoint configs");
+    }
+
+    public WifiConfiguration remove(int netID) {
+        WifiConfiguration config = mPerID.remove(netID);
+        if (config == null) {
+            return null;
+        }
+        mPerConfigKey.remove(config.configKey().hashCode());
+
+        Iterator<Map.Entry<String, Integer>> entries = mPerFQDN.entrySet().iterator();
+        while (entries.hasNext()) {
+            if (entries.next().getValue() == netID) {
+                entries.remove();
+                break;
+            }
+        }
+        return config;
+    }
+
+    public void clear() {
+        mPerID.clear();
+        mPerConfigKey.clear();
+        mPerFQDN.clear();
+    }
+
+    // RO methods:
+    public WifiConfiguration get(int netid) {
+        return mPerID.get(netid);
+    }
+
+    public int size() {
+        return mPerID.size();
+    }
+
+    public boolean isEmpty() {
+        return mPerID.size() == 0;
+    }
+
+    public WifiConfiguration getByFQDN(String fqdn) {
+        Integer id = mPerFQDN.get(fqdn);
+        return id != null ? mPerID.get(id) : null;
+    }
+
+    public WifiConfiguration getByConfigKey(String key) {
+        if (key == null) {
+            return null;
+        }
+        for (WifiConfiguration config : mPerID.values()) {
+            if (config.configKey().equals(key)) {
+                return config;
+            }
+        }
+        return null;
+    }
+
+    public WifiConfiguration getByConfigKeyID(int id) {
+        return mPerConfigKey.get(id);
+    }
+
+    public Collection<WifiConfiguration> getEnabledNetworks() {
+        List<WifiConfiguration> list = new ArrayList<>();
+        for (WifiConfiguration config : mPerID.values()) {
+            if (config.status != WifiConfiguration.Status.DISABLED) {
+                list.add(config);
+            }
+        }
+        return list;
+    }
+
+    public WifiConfiguration getEphemeral(String ssid) {
+        for (WifiConfiguration config : mPerID.values()) {
+            if (ssid.equals(config.SSID) && config.ephemeral) {
+                return config;
+            }
+        }
+        return null;
+    }
+
+    public Collection<WifiConfiguration> values() {
+        return mPerID.values();
+    }
+}
diff --git a/service/java/com/android/server/wifi/IMSIParameter.java b/service/java/com/android/server/wifi/IMSIParameter.java
new file mode 100644
index 0000000..deea870
--- /dev/null
+++ b/service/java/com/android/server/wifi/IMSIParameter.java
@@ -0,0 +1,102 @@
+package com.android.server.wifi;
+
+import java.io.IOException;
+
+public class IMSIParameter {
+    private final String mImsi;
+    private final boolean mPrefix;
+
+    public IMSIParameter(String imsi, boolean prefix) {
+        mImsi = imsi;
+        mPrefix = prefix;
+    }
+
+    public IMSIParameter(String imsi) throws IOException {
+        if (imsi == null || imsi.length() == 0) {
+            throw new IOException("Bad IMSI: '" + imsi + "'");
+        }
+
+        int nonDigit;
+        char stopChar = '\0';
+        for (nonDigit = 0; nonDigit < imsi.length(); nonDigit++) {
+            stopChar = imsi.charAt(nonDigit);
+            if (stopChar < '0' || stopChar > '9') {
+                break;
+            }
+        }
+
+        if (nonDigit == imsi.length()) {
+            mImsi = imsi;
+            mPrefix = false;
+        }
+        else if (nonDigit == imsi.length()-1 && stopChar == '*') {
+            mImsi = imsi.substring(0, nonDigit);
+            mPrefix = true;
+        }
+        else {
+            throw new IOException("Bad IMSI: '" + imsi + "'");
+        }
+    }
+
+    public boolean matches(String fullIMSI) {
+        if (mPrefix) {
+            return mImsi.regionMatches(false, 0, fullIMSI, 0, mImsi.length());
+        }
+        else {
+            return mImsi.equals(fullIMSI);
+        }
+    }
+
+    public boolean matchesMccMnc(String mccMnc) {
+        if (mPrefix) {
+            // For a prefix match, the entire prefix must match the mcc+mnc
+            return mImsi.regionMatches(false, 0, mccMnc, 0, mImsi.length());
+        }
+        else {
+            // For regular match, the entire length of mcc+mnc must match this IMSI
+            return mImsi.regionMatches(false, 0, mccMnc, 0, mccMnc.length());
+        }
+    }
+
+    public boolean isPrefix() {
+        return mPrefix;
+    }
+
+    public String getImsi() {
+        return mImsi;
+    }
+
+    public int prefixLength() {
+        return mImsi.length();
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        else if (thatObject == null || getClass() != thatObject.getClass()) {
+            return false;
+        }
+
+        IMSIParameter that = (IMSIParameter) thatObject;
+        return mPrefix == that.mPrefix && mImsi.equals(that.mImsi);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mImsi != null ? mImsi.hashCode() : 0;
+        result = 31 * result + (mPrefix ? 1 : 0);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        if (mPrefix) {
+            return mImsi + '*';
+        }
+        else {
+            return mImsi;
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/RttService.java b/service/java/com/android/server/wifi/RttService.java
index c447e28..cc7f549 100644
--- a/service/java/com/android/server/wifi/RttService.java
+++ b/service/java/com/android/server/wifi/RttService.java
@@ -28,8 +28,9 @@
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.Queue;
+import android.Manifest;
 
-class RttService extends SystemService {
+public final class RttService extends SystemService {
 
     public static final boolean DBG = true;
 
@@ -144,6 +145,16 @@
             Integer key;
             ClientInfo ci;
             RttManager.RttParams[] params;
+
+            @Override
+            public String toString() {
+                String str = getClass().getName() + "@" + Integer.toHexString(hashCode());
+                if(this.key != null) {
+                    return str + " key: " + this.key;
+                } else {
+                    return str + " key: " + " , null";
+                }
+            }
         }
 
         private class ClientInfo {
@@ -198,12 +209,14 @@
             }
 
             void reportAborted(int key) {
-                mChannel.sendMessage(RttManager.CMD_OP_ABORTED, key);
-                mRequests.remove(key);
+                mChannel.sendMessage(RttManager.CMD_OP_ABORTED, 0, key);
+                //All Queued RTT request will be cleaned
+                cleanup();
             }
 
             void cleanup() {
                 mRequests.clear();
+                mRequestQueue.clear();
             }
         }
 
@@ -271,20 +284,32 @@
                             transitionTo(mRequestPendingState);
                             break;
                         case RttManager.CMD_OP_START_RANGING: {
-                                RttManager.ParcelableRttParams params =
-                                        (RttManager.ParcelableRttParams)msg.obj;
-                                if (params == null) {
-                                    replyFailed(msg,
-                                            RttManager.REASON_INVALID_REQUEST, "No params");
-                                } else if (ci.addRttRequest(msg.arg2, params) == false) {
-                                    replyFailed(msg,
-                                            RttManager.REASON_INVALID_REQUEST, "Unspecified");
-                                } else {
-                                    sendMessage(CMD_ISSUE_NEXT_REQUEST);
-                                }
+                            //check permission
+                            if(DBG) Log.d(TAG, "UID is: " + msg.sendingUid);
+                            if (!enforcePermissionCheck(msg)) {
+                                Log.e(TAG, "UID: " + msg.sendingUid + " has no" +
+                                        " LOCATION_HARDWARE Permission");
+                                break;
                             }
+
+                            RttManager.ParcelableRttParams params =
+                                    (RttManager.ParcelableRttParams)msg.obj;
+                            if (params == null) {
+                                replyFailed(msg,
+                                        RttManager.REASON_INVALID_REQUEST, "No params");
+                            } else if (ci.addRttRequest(msg.arg2, params) == false) {
+                                replyFailed(msg,
+                                        RttManager.REASON_INVALID_REQUEST, "Unspecified");
+                            } else {
+                                sendMessage(CMD_ISSUE_NEXT_REQUEST);
+                            }
+                        }
                             break;
                         case RttManager.CMD_OP_STOP_RANGING:
+                            if(!enforcePermissionCheck(msg)) {
+                                break;
+                            }
+
                             for (Iterator<RttRequest> it = mRequestQueue.iterator();
                                     it.hasNext(); ) {
                                 RttRequest request = it.next();
@@ -312,6 +337,7 @@
                         case CMD_DRIVER_UNLOADED:
                             if (mOutstandingRequest != null) {
                                 WifiNative.cancelRtt(mOutstandingRequest.params);
+                                if (DBG) Log.d(TAG, "abort key: " + mOutstandingRequest.key);
                                 mOutstandingRequest.ci.reportAborted(mOutstandingRequest.key);
                                 mOutstandingRequest = null;
                             }
@@ -323,23 +349,38 @@
                                 if (mOutstandingRequest == null) {
                                     transitionTo(mEnabledState);
                                 }
+                                if(mOutstandingRequest != null) {
+                                    if (DBG) Log.d(TAG, "new mOutstandingRequest.key is: " +
+                                            mOutstandingRequest.key);
+                                } else {
+                                    if (DBG) Log.d(TAG,
+                                            "CMD_ISSUE_NEXT_REQUEST: mOutstandingRequest =null ");
+                                }
                             } else {
                                 /* just wait; we'll issue next request after
                                  * current one is finished */
-                                if (DBG) Log.d(TAG, "Ignoring CMD_ISSUE_NEXT_REQUEST");
+                                 if (DBG) Log.d(TAG, "Current mOutstandingRequest.key is: " +
+                                         mOutstandingRequest.key);
+                                 if (DBG) Log.d(TAG, "Ignoring CMD_ISSUE_NEXT_REQUEST");
                             }
                             break;
                         case CMD_RTT_RESPONSE:
-                            if (DBG) Log.d(TAG, "Received an RTT response");
+                            if (DBG) Log.d(TAG, "Received an RTT response from: " + msg.arg2);
                             mOutstandingRequest.ci.reportResult(
                                     mOutstandingRequest, (RttManager.RttResult[])msg.obj);
                             mOutstandingRequest = null;
                             sendMessage(CMD_ISSUE_NEXT_REQUEST);
                             break;
                         case RttManager.CMD_OP_STOP_RANGING:
+                            if(!enforcePermissionCheck(msg)) {
+                                Log.e(TAG, "UID: " + msg.sendingUid + " has no " +
+                                        "LOCATION_HARDWARE Permission");
+                                break;
+                            }
+
                             if (mOutstandingRequest != null
                                     && msg.arg2 == mOutstandingRequest.key) {
-                                if (DBG) Log.d(TAG, "Cancelling ongoing RTT");
+                                if (DBG) Log.d(TAG, "Cancelling ongoing RTT of: " + msg.arg2);
                                 WifiNative.cancelRtt(mOutstandingRequest.params);
                                 mOutstandingRequest.ci.reportAborted(mOutstandingRequest.key);
                                 mOutstandingRequest = null;
@@ -390,6 +431,17 @@
             }
         }
 
+        boolean enforcePermissionCheck(Message msg) {
+            try {
+                mContext.enforcePermission(Manifest.permission.LOCATION_HARDWARE,
+                         -1, msg.sendingUid, "LocationRTT");
+            } catch (SecurityException e) {
+                replyFailed(msg,RttManager.REASON_PERMISSION_DENIED, "No params");
+                return false;
+            }
+            return true;
+        }
+
         private WifiNative.RttEventHandler mEventHandler = new WifiNative.RttEventHandler() {
             @Override
             public void onRttResults(RttManager.RttResult[] result) {
@@ -401,12 +453,15 @@
             RttRequest request = null;
             while (mRequestQueue.isEmpty() == false) {
                 request = mRequestQueue.remove();
-                if (WifiNative.requestRtt(request.params, mEventHandler)) {
-                    if (DBG) Log.d(TAG, "Issued next RTT request");
-                    return request;
-                } else {
-                    request.ci.reportFailed(request,
-                            RttManager.REASON_UNSPECIFIED, "Failed to start");
+                if(request !=  null) {
+                    if (WifiNative.requestRtt(request.params, mEventHandler)) {
+                        if (DBG) Log.d(TAG, "Issued next RTT request with key: " + request.key);
+                        return request;
+                    } else {
+                        Log.e(TAG, "Fail to issue key at native layer");
+                        request.ci.reportFailed(request,
+                                RttManager.REASON_UNSPECIFIED, "Failed to start");
+                    }
                 }
             }
 
@@ -414,6 +469,10 @@
             if (DBG) Log.d(TAG, "No more requests left");
             return null;
         }
+        @Override
+        public RttManager.RttCapabilities getRttCapabilities() {
+            return WifiNative.getRttCapabilities();
+        }
     }
 
     private static final String TAG = "RttService";
@@ -442,4 +501,6 @@
             mImpl.startService(getContext());
         }
     }
+
+
 }
diff --git a/service/java/com/android/server/wifi/SIMAccessor.java b/service/java/com/android/server/wifi/SIMAccessor.java
new file mode 100644
index 0000000..e446436
--- /dev/null
+++ b/service/java/com/android/server/wifi/SIMAccessor.java
@@ -0,0 +1,32 @@
+package com.android.server.wifi;
+
+import android.content.Context;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SIMAccessor {
+    private final TelephonyManager mTelephonyManager;
+    private final SubscriptionManager mSubscriptionManager;
+
+    public SIMAccessor(Context context) {
+        mTelephonyManager = TelephonyManager.from(context);
+        mSubscriptionManager = SubscriptionManager.from(context);
+    }
+
+    public List<String> getMatchingImsis(IMSIParameter mccMnc) {
+        if (mccMnc == null) {
+            return null;
+        }
+        List<String> imsis = new ArrayList<>();
+        for (int subId : mSubscriptionManager.getActiveSubscriptionIdList()) {
+            String imsi = mTelephonyManager.getSubscriberId(subId);
+            if (mccMnc.matches(imsi)) {
+                imsis.add(imsi);
+            }
+        }
+        return imsis.isEmpty() ? null : imsis;
+    }
+}
diff --git a/service/java/com/android/server/wifi/ScanDetail.java b/service/java/com/android/server/wifi/ScanDetail.java
new file mode 100644
index 0000000..868a192
--- /dev/null
+++ b/service/java/com/android/server/wifi/ScanDetail.java
@@ -0,0 +1,157 @@
+package com.android.server.wifi;
+
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiSsid;
+
+import com.android.server.wifi.anqp.ANQPElement;
+import com.android.server.wifi.anqp.Constants;
+import com.android.server.wifi.anqp.HSFriendlyNameElement;
+import com.android.server.wifi.anqp.VenueNameElement;
+import com.android.server.wifi.hotspot2.NetworkDetail;
+import com.android.server.wifi.hotspot2.PasspointMatch;
+import com.android.server.wifi.hotspot2.PasspointMatchInfo;
+import com.android.server.wifi.hotspot2.Utils;
+import com.android.server.wifi.hotspot2.pps.HomeSP;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class ScanDetail {
+    private final ScanResult mScanResult;
+    private volatile NetworkDetail mNetworkDetail;
+    private final Map<HomeSP, PasspointMatch> mMatches;
+    private long mSeen = 0;
+
+    public ScanDetail(NetworkDetail networkDetail, WifiSsid wifiSsid, String BSSID,
+                      String caps, int level, int frequency, long tsf) {
+        mNetworkDetail = networkDetail;
+        mScanResult = new ScanResult(wifiSsid, BSSID, caps, level, frequency, tsf );
+        mSeen = System.currentTimeMillis();
+        //mScanResult.seen = mSeen;
+        mScanResult.channelWidth = networkDetail.getChannelWidth();
+        mScanResult.centerFreq0 = networkDetail.getCenterfreq0();
+        mScanResult.centerFreq1 = networkDetail.getCenterfreq1();
+        if (networkDetail.is80211McResponderSupport())
+            mScanResult.setFlag(ScanResult.FLAG_80211mc_RESPONDER);
+        mMatches = null;
+    }
+
+    public ScanDetail(WifiSsid wifiSsid, String BSSID, String caps, int level, int frequency,
+                      long tsf, long seen) {
+        mNetworkDetail = null;
+        mScanResult = new ScanResult(wifiSsid, BSSID, caps, level, frequency, tsf );
+        mSeen = seen;
+        //mScanResult.seen = mSeen;
+        mScanResult.channelWidth = 0;
+        mScanResult.centerFreq0 = 0;
+        mScanResult.centerFreq1 = 0;
+        mScanResult.flags = 0;
+        mMatches = null;
+    }
+
+    private ScanDetail(ScanResult scanResult, NetworkDetail networkDetail,
+                       Map<HomeSP, PasspointMatch> matches) {
+        mScanResult = scanResult;
+        mNetworkDetail = networkDetail;
+        mMatches = matches;
+    }
+
+    public void updateResults(NetworkDetail networkDetail, int level, WifiSsid wssid, String ssid,
+                              String flags, int freq, long tsf) {
+        mScanResult.level = level;
+        mScanResult.wifiSsid = wssid;
+        // Keep existing API
+        mScanResult.SSID = ssid;
+        mScanResult.capabilities = flags;
+        mScanResult.frequency = freq;
+        mScanResult.timestamp = tsf;
+        mSeen = System.currentTimeMillis();
+        //mScanResult.seen = mSeen;
+        mScanResult.channelWidth = networkDetail.getChannelWidth();
+        mScanResult.centerFreq0 = networkDetail.getCenterfreq0();
+        mScanResult.centerFreq1 = networkDetail.getCenterfreq1();
+        if (networkDetail.is80211McResponderSupport())
+            mScanResult.setFlag(ScanResult.FLAG_80211mc_RESPONDER);
+        if (networkDetail.isInterworking())
+            mScanResult.setFlag(ScanResult.FLAG_PASSPOINT_NETWORK);
+    }
+
+    public void propagateANQPInfo(Map<Constants.ANQPElementType, ANQPElement> anqpElements) {
+        if (anqpElements.isEmpty()) {
+            return;
+        }
+        mNetworkDetail = mNetworkDetail.complete(anqpElements);
+        HSFriendlyNameElement fne = (HSFriendlyNameElement)anqpElements.get(
+                Constants.ANQPElementType.HSFriendlyName);
+        // !!! Match with language
+        if (fne != null && !fne.getNames().isEmpty()) {
+            mScanResult.venueName = fne.getNames().get(0).getText();
+        } else {
+            VenueNameElement vne =
+                    (((VenueNameElement)anqpElements.get(Constants.ANQPElementType.ANQPVenueName)));
+            if (vne != null && !vne.getNames().isEmpty()) {
+                mScanResult.venueName = vne.getNames().get(0).getText();
+            }
+        }
+    }
+
+    public ScanResult getScanResult() {
+        return mScanResult;
+    }
+
+    public NetworkDetail getNetworkDetail() {
+        return mNetworkDetail;
+    }
+
+    public String getSSID() {
+        return mNetworkDetail == null ? mScanResult.SSID : mNetworkDetail.getSSID();
+    }
+
+    public String getBSSIDString() {
+        return  mNetworkDetail == null ? mScanResult.BSSID : mNetworkDetail.getBSSIDString();
+    }
+
+    public String toKeyString() {
+        NetworkDetail networkDetail = mNetworkDetail;
+        if (networkDetail != null) {
+            return networkDetail.toKeyString();
+        }
+        else {
+            return String.format("'%s':%012x", mScanResult.BSSID, Utils.parseMac(mScanResult.BSSID));
+        }
+    }
+
+    public long getSeen() {
+        return mSeen;
+    }
+
+    public long setSeen() {
+        mSeen = System.currentTimeMillis();
+        mScanResult.seen = mSeen;
+        return mSeen;
+    }
+
+    public List<PasspointMatchInfo> getMatchList() {
+        if (mMatches == null || mMatches.isEmpty()) {
+            return null;
+        }
+
+        List<PasspointMatchInfo> list = new ArrayList<>();
+        for (Map.Entry<HomeSP, PasspointMatch> entry : mMatches.entrySet()) {
+            new PasspointMatchInfo(entry.getValue(), this, entry.getKey());
+        }
+        return list;
+    }
+
+    @Override
+    public String toString() {
+        try {
+            return String.format("'%s'/%012x",
+                    mScanResult.SSID, Utils.parseMac(mScanResult.BSSID));
+        }
+        catch (IllegalArgumentException iae) {
+            return String.format("'%s'/----", mScanResult.BSSID);
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/ScanDetailCache.java b/service/java/com/android/server/wifi/ScanDetailCache.java
new file mode 100644
index 0000000..d246c55
--- /dev/null
+++ b/service/java/com/android/server/wifi/ScanDetailCache.java
@@ -0,0 +1,300 @@
+
+package com.android.server.wifi;
+
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.util.Log;
+import android.os.SystemClock;
+
+import com.android.server.wifi.ScanDetail;
+import com.android.server.wifi.hotspot2.PasspointMatch;
+import com.android.server.wifi.hotspot2.PasspointMatchInfo;
+import com.android.server.wifi.hotspot2.pps.HomeSP;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+
+class ScanDetailCache {
+
+    private static final String TAG = "ScanDetailCache";
+
+    private WifiConfiguration mConfig;
+    private HashMap<String, ScanDetail> mMap;
+    private HashMap<String, PasspointMatchInfo> mPasspointMatches;
+    private static boolean DBG=false;
+    ScanDetailCache(WifiConfiguration config) {
+        mConfig = config;
+        mMap = new HashMap();
+        mPasspointMatches = new HashMap();
+    }
+
+    void put(ScanDetail scanDetail) {
+        put(scanDetail, null, null);
+    }
+
+    void put(ScanDetail scanDetail, PasspointMatch match, HomeSP homeSp) {
+
+        mMap.put(scanDetail.getBSSIDString(), scanDetail);
+
+        if (match != null && homeSp != null) {
+            mPasspointMatches.put(scanDetail.getBSSIDString(),
+                    new PasspointMatchInfo(match, scanDetail, homeSp));
+        }
+    }
+
+    ScanResult get(String bssid) {
+        ScanDetail scanDetail = getScanDetail(bssid);
+        return scanDetail == null ? null : scanDetail.getScanResult();
+    }
+
+    ScanDetail getScanDetail(String bssid) {
+        return mMap.get(bssid);
+    }
+
+    void remove(String bssid) {
+        mMap.remove(bssid);
+    }
+
+    int size() {
+        return mMap.size();
+    }
+
+    boolean isEmpty() {
+        return size() == 0;
+    }
+
+    ScanDetail getFirst() {
+        Iterator<ScanDetail> it = mMap.values().iterator();
+        return it.hasNext() ? it.next() : null;
+    }
+
+    Collection<String> keySet() {
+        return mMap.keySet();
+    }
+
+    Collection<ScanDetail> values() {
+        return mMap.values();
+    }
+
+    public void trim(int num) {
+        int currentSize = mMap.size();
+        if (currentSize <= num) {
+            return; // Nothing to trim
+        }
+        ArrayList<ScanDetail> list = new ArrayList<ScanDetail>(mMap.values());
+        if (list.size() != 0) {
+            // Sort by descending timestamp
+            Collections.sort(list, new Comparator() {
+                public int compare(Object o1, Object o2) {
+                    ScanDetail a = (ScanDetail) o1;
+                    ScanDetail b = (ScanDetail) o2;
+                    if (a.getSeen() > b.getSeen()) {
+                        return 1;
+                    }
+                    if (a.getSeen() < b.getSeen()) {
+                        return -1;
+                    }
+                    return a.getBSSIDString().compareTo(b.getBSSIDString());
+                }
+            });
+        }
+        for (int i = 0; i < currentSize - num ; i++) {
+            // Remove oldest results from scan cache
+            ScanDetail result = list.get(i);
+            mMap.remove(result.getBSSIDString());
+            mPasspointMatches.remove(result.getBSSIDString());
+        }
+    }
+
+    /* @hide */
+    private ArrayList<ScanDetail> sort() {
+        ArrayList<ScanDetail> list = new ArrayList<ScanDetail>(mMap.values());
+        if (list.size() != 0) {
+            Collections.sort(list, new Comparator() {
+                public int compare(Object o1, Object o2) {
+                    ScanResult a = ((ScanDetail)o1).getScanResult();
+                    ScanResult b = ((ScanDetail)o2).getScanResult();
+                    if (a.numIpConfigFailures > b.numIpConfigFailures) {
+                        return 1;
+                    }
+                    if (a.numIpConfigFailures < b.numIpConfigFailures) {
+                        return -1;
+                    }
+                    if (a.seen > b.seen) {
+                        return -1;
+                    }
+                    if (a.seen < b.seen) {
+                        return 1;
+                    }
+                    if (a.level > b.level) {
+                        return -1;
+                    }
+                    if (a.level < b.level) {
+                        return 1;
+                    }
+                    return a.BSSID.compareTo(b.BSSID);
+                }
+            });
+        }
+        return list;
+    }
+
+    public WifiConfiguration.Visibility getVisibilityByRssi(long age) {
+        WifiConfiguration.Visibility status = new WifiConfiguration.Visibility();
+
+        long now_ms = System.currentTimeMillis();
+        long now_elapsed_ms = SystemClock.elapsedRealtime();
+        for(ScanDetail scanDetail : values()) {
+            ScanResult result = scanDetail.getScanResult();
+            if (scanDetail.getSeen() == 0)
+                continue;
+
+            if (result.is5GHz()) {
+                //strictly speaking: [4915, 5825]
+                //number of known BSSID on 5GHz band
+                status.num5 = status.num5 + 1;
+            } else if (result.is24GHz()) {
+                //strictly speaking: [2412, 2482]
+                //number of known BSSID on 2.4Ghz band
+                status.num24 = status.num24 + 1;
+            }
+
+            if (result.timestamp != 0) {
+                if (DBG) {
+                    Log.e("getVisibilityByRssi", " considering " + result.SSID + " " + result.BSSID
+                        + " elapsed=" + now_elapsed_ms + " timestamp=" + result.timestamp
+                        + " age = " + age);
+                }
+                if ((now_elapsed_ms - (result.timestamp/1000)) > age) continue;
+            } else {
+                // This check the time at which we have received the scan result from supplicant
+                if ((now_ms - result.seen) > age) continue;
+            }
+
+            if (result.is5GHz()) {
+                if (result.level > status.rssi5) {
+                    status.rssi5 = result.level;
+                    status.age5 = result.seen;
+                    status.BSSID5 = result.BSSID;
+                }
+            } else if (result.is24GHz()) {
+                if (result.level > status.rssi24) {
+                    status.rssi24 = result.level;
+                    status.age24 = result.seen;
+                    status.BSSID24 = result.BSSID;
+                }
+            }
+        }
+
+        return status;
+    }
+
+    public WifiConfiguration.Visibility getVisibilityByPasspointMatch(long age) {
+
+        long now_ms = System.currentTimeMillis();
+        PasspointMatchInfo pmiBest24 = null, pmiBest5 = null;
+
+        for(PasspointMatchInfo pmi : mPasspointMatches.values()) {
+            ScanDetail scanDetail = pmi.getScanDetail();
+            if (scanDetail == null) continue;
+            ScanResult result = scanDetail.getScanResult();
+            if (result == null) continue;
+
+            if (scanDetail.getSeen() == 0)
+                continue;
+
+            if ((now_ms - result.seen) > age) continue;
+
+            if (result.is5GHz()) {
+                if (pmiBest5 == null || pmiBest5.compareTo(pmi) < 0) {
+                    pmiBest5 = pmi;
+                }
+            } else if (result.is24GHz()) {
+                if (pmiBest24 == null || pmiBest24.compareTo(pmi) < 0) {
+                    pmiBest24 = pmi;
+                }
+            }
+        }
+
+        WifiConfiguration.Visibility status = new WifiConfiguration.Visibility();
+        String logMsg = "Visiblity by passpoint match returned ";
+        if (pmiBest5 != null) {
+            ScanResult result = pmiBest5.getScanDetail().getScanResult();
+            status.rssi5 = result.level;
+            status.age5 = result.seen;
+            status.BSSID5 = result.BSSID;
+            logMsg += "5 GHz BSSID of " + result.BSSID;
+        }
+        if (pmiBest24 != null) {
+            ScanResult result = pmiBest24.getScanDetail().getScanResult();
+            status.rssi24 = result.level;
+            status.age24 = result.seen;
+            status.BSSID24 = result.BSSID;
+            logMsg += "2.4 GHz BSSID of " + result.BSSID;
+        }
+
+        Log.d(TAG, logMsg);
+
+        return status;
+    }
+
+    public WifiConfiguration.Visibility getVisibility(long age) {
+        if (mConfig.isPasspoint()) {
+            return getVisibilityByPasspointMatch(age);
+        } else {
+            return getVisibilityByRssi(age);
+        }
+    }
+
+
+
+    @Override
+    public String toString() {
+        StringBuilder sbuf = new StringBuilder();
+        sbuf.append("Scan Cache:  ").append('\n');
+
+        ArrayList<ScanDetail> list = sort();
+        long now_ms = System.currentTimeMillis();
+        if (list.size() > 0) {
+            for (ScanDetail scanDetail : list) {
+                ScanResult result = scanDetail.getScanResult();
+                long milli = now_ms - scanDetail.getSeen();
+                long ageSec = 0;
+                long ageMin = 0;
+                long ageHour = 0;
+                long ageMilli = 0;
+                long ageDay = 0;
+                if (now_ms > scanDetail.getSeen() && scanDetail.getSeen() > 0) {
+                    ageMilli = milli % 1000;
+                    ageSec   = (milli / 1000) % 60;
+                    ageMin   = (milli / (60*1000)) % 60;
+                    ageHour  = (milli / (60*60*1000)) % 24;
+                    ageDay   = (milli / (24*60*60*1000));
+                }
+                sbuf.append("{").append(result.BSSID).append(",").append(result.frequency);
+                sbuf.append(",").append(String.format("%3d", result.level));
+                if (result.autoJoinStatus > 0) {
+                    sbuf.append(",st=").append(result.autoJoinStatus);
+                }
+                if (ageSec > 0 || ageMilli > 0) {
+                    sbuf.append(String.format(",%4d.%02d.%02d.%02d.%03dms", ageDay,
+                            ageHour, ageMin, ageSec, ageMilli));
+                }
+                if (result.numIpConfigFailures > 0) {
+                    sbuf.append(",ipfail=");
+                    sbuf.append(result.numIpConfigFailures);
+                }
+                sbuf.append("} ");
+            }
+            sbuf.append('\n');
+        }
+
+        return sbuf.toString();
+    }
+
+}
diff --git a/service/java/com/android/server/wifi/WifiApConfigStore.java b/service/java/com/android/server/wifi/WifiApConfigStore.java
index eb35db8..ed5ddf7 100644
--- a/service/java/com/android/server/wifi/WifiApConfigStore.java
+++ b/service/java/com/android/server/wifi/WifiApConfigStore.java
@@ -37,6 +37,7 @@
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.UUID;
 
 /**
@@ -51,7 +52,7 @@
     private static final String AP_CONFIG_FILE = Environment.getDataDirectory() +
         "/misc/wifi/softap.conf";
 
-    private static final int AP_CONFIG_FILE_VERSION = 1;
+    private static final int AP_CONFIG_FILE_VERSION = 2;
 
     private State mDefaultState = new DefaultState();
     private State mInactiveState = new InactiveState();
@@ -59,6 +60,7 @@
 
     private WifiConfiguration mWifiApConfig = null;
     private AsyncChannel mReplyChannel = new AsyncChannel();
+    public ArrayList <Integer> allowed2GChannel = null;
 
     WifiApConfigStore(Context context, Handler target) {
         super(TAG, target.getLooper());
@@ -69,6 +71,17 @@
             addState(mActiveState, mDefaultState);
 
         setInitialState(mInactiveState);
+        String ap2GChannelListStr = (mContext.getResources().getString(
+                R.string.config_wifi_framework_sap_2G_channel_list));
+        Log.d(TAG, "2G band allowed channels are:" + ap2GChannelListStr);
+
+        if (ap2GChannelListStr != null) {
+            allowed2GChannel = new ArrayList<Integer>();
+            String channelList[] = ap2GChannelListStr.split(",");
+            for (String tmp : channelList) {
+                allowed2GChannel.add(Integer.parseInt(tmp));
+            }
+        }
     }
 
     public static WifiApConfigStore makeWifiApConfigStore(Context context, Handler target) {
@@ -100,9 +113,9 @@
         public boolean processMessage(Message message) {
             switch (message.what) {
                 case WifiStateMachine.CMD_SET_AP_CONFIG:
-                    WifiConfiguration config = (WifiConfiguration) message.obj;
+                     WifiConfiguration config = (WifiConfiguration)message.obj;
                     if (config.SSID != null) {
-                        mWifiApConfig = (WifiConfiguration) message.obj;
+                        mWifiApConfig = config;
                         transitionTo(mActiveState);
                     } else {
                         Log.e(TAG, "Try to setup AP config without SSID: " + message);
@@ -150,17 +163,24 @@
                             AP_CONFIG_FILE)));
 
             int version = in.readInt();
-            if (version != 1) {
+            if ((version != 1) && (version != 2)) {
                 Log.e(TAG, "Bad version on hotspot configuration file, set defaults");
                 setDefaultApConfiguration();
                 return;
             }
             config.SSID = in.readUTF();
+
+            if (version >= 2) {
+                config.apBand = in.readInt();
+                config.apChannel = in.readInt();
+            }
+
             int authType = in.readInt();
             config.allowedKeyManagement.set(authType);
             if (authType != KeyMgmt.NONE) {
                 config.preSharedKey = in.readUTF();
             }
+
             mWifiApConfig = config;
         } catch (IOException ignore) {
             setDefaultApConfiguration();
@@ -185,6 +205,8 @@
 
             out.writeInt(AP_CONFIG_FILE_VERSION);
             out.writeUTF(config.SSID);
+            out.writeInt(config.apBand);
+            out.writeInt(config.apChannel);
             int authType = config.getAuthType();
             out.writeInt(authType);
             if(authType != KeyMgmt.NONE) {
diff --git a/service/java/com/android/server/wifi/WifiAutoJoinController.java b/service/java/com/android/server/wifi/WifiAutoJoinController.java
index c7da179..a1a9a82 100644
--- a/service/java/com/android/server/wifi/WifiAutoJoinController.java
+++ b/service/java/com/android/server/wifi/WifiAutoJoinController.java
@@ -20,17 +20,23 @@
 import android.net.NetworkKey;
 import android.net.NetworkScoreManager;
 import android.net.WifiKey;
-import android.net.wifi.*;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiConfiguration.KeyMgmt;
-import android.os.SystemClock;
-import android.provider.Settings;
+import android.net.wifi.WifiConnectionStatistics;
 import android.os.Process;
+import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
+import android.os.SystemClock;
 
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
 import java.util.ArrayList;
-import java.util.Iterator;
+import java.util.Arrays;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 
 /**
@@ -59,12 +65,13 @@
 
     private String mCurrentConfigurationKey = null; //used by autojoin
 
-    private HashMap<String, ScanResult> scanResultCache =
-            new HashMap<String, ScanResult>();
+    private final HashMap<String, ScanDetail> scanResultCache = new HashMap<>();
 
     private WifiConnectionStatistics mWifiConnectionStatistics;
 
-    /** Whether to allow connections to untrusted networks. */
+    /**
+     * Whether to allow connections to untrusted networks.
+     */
     private boolean mAllowUntrustedConnections = false;
 
     /* For debug purpose only: if the scored override a score */
@@ -75,7 +82,9 @@
     // Lose some temporary blacklisting after 30 minutes
     private final static long loseBlackListSoftMilli = 1000 * 60 * 30;
 
-    /** @see android.provider.Settings.Global#WIFI_EPHEMERAL_OUT_OF_RANGE_TIMEOUT_MS */
+    /**
+     * @see android.provider.Settings.Global#WIFI_EPHEMERAL_OUT_OF_RANGE_TIMEOUT_MS
+     */
     private static final long DEFAULT_EPHEMERAL_OUT_OF_RANGE_TIMEOUT_MS = 1000 * 60; // 1 minute
 
     public static final int AUTO_JOIN_IDLE = 0;
@@ -85,6 +94,8 @@
 
     public static final int HIGH_THRESHOLD_MODIFIER = 5;
 
+    public static final int MAX_RSSI_DELTA = 50;
+
     // Below are AutoJoin wide parameters indicating if we should be aggressive before joining
     // weak network. Note that we cannot join weak network that are going to be marked as unanted by
     // ConnectivityService because this will trigger link flapping.
@@ -123,7 +134,7 @@
     }
 
     void enableVerboseLogging(int verbose) {
-        if (verbose > 0 ) {
+        if (verbose > 0) {
             DBG = true;
             VDBG = true;
         } else {
@@ -134,7 +145,6 @@
 
     /**
      * Flush out scan results older than mScanResultMaximumAge
-     *
      */
     private void ageScanResultsOut(int delay) {
         if (delay <= 0) {
@@ -146,18 +156,67 @@
                     + Integer.valueOf(scanResultCache.size()) + " now " + Long.valueOf(milli));
         }
 
-        Iterator<HashMap.Entry<String,ScanResult>> iter = scanResultCache.entrySet().iterator();
+        Iterator<HashMap.Entry<String, ScanDetail>> iter = scanResultCache.entrySet().iterator();
         while (iter.hasNext()) {
-            HashMap.Entry<String,ScanResult> entry = iter.next();
-            ScanResult result = entry.getValue();
-
-            if ((result.seen + delay) < milli) {
+            HashMap.Entry<String, ScanDetail> entry = iter.next();
+            ScanDetail scanDetail = entry.getValue();
+            if ((scanDetail.getSeen() + delay) < milli) {
                 iter.remove();
             }
         }
     }
 
-    int addToScanCache(List<ScanResult> scanList) {
+
+    void averageRssiAndRemoveFromCache(ScanResult result) {
+        // Fetch the previous instance for this result
+        ScanDetail sd = scanResultCache.get(result.BSSID);
+        if (sd != null) {
+            ScanResult sr = sd.getScanResult();
+            if (mWifiConfigStore.scanResultRssiLevelPatchUp != 0
+                    && result.level == 0
+                    && sr.level < -20) {
+                // A 'zero' RSSI reading is most likely a chip problem which returns
+                // an unknown RSSI, hence ignore it
+                result.level = sr.level;
+            }
+
+            // If there was a previous cache result for this BSSID, average the RSSI values
+            result.averageRssi(sr.level, sr.seen, mScanResultMaximumAge);
+
+            // Remove the previous Scan Result - this is not necessary
+            scanResultCache.remove(result.BSSID);
+        } else if (mWifiConfigStore.scanResultRssiLevelPatchUp != 0 && result.level == 0) {
+            // A 'zero' RSSI reading is most likely a chip problem which returns
+            // an unknown RSSI, hence initialize it to a sane value
+            result.level = mWifiConfigStore.scanResultRssiLevelPatchUp;
+        }
+    }
+
+    void addToUnscoredNetworks(ScanResult result, List<NetworkKey> unknownScanResults) {
+        WifiKey wkey;
+        // Quoted SSIDs are the only one valid at this stage
+        try {
+            wkey = new WifiKey("\"" + result.SSID + "\"", result.BSSID);
+        } catch (IllegalArgumentException e) {
+            logDbg("AutoJoinController: received badly encoded SSID=[" + result.SSID +
+                    "] ->skipping this network");
+            wkey = null;
+        }
+        if (wkey != null) {
+            NetworkKey nkey = new NetworkKey(wkey);
+            //if we don't know this scan result then request a score from the scorer
+            unknownScanResults.add(nkey);
+        }
+        if (VDBG) {
+            String cap = "";
+            if (result.capabilities != null)
+                cap = result.capabilities;
+            logDbg(result.SSID + " " + result.BSSID + " rssi="
+                    + result.level + " cap " + cap + " tsf " + result.timestamp + " is not scored");
+        }
+    }
+
+    int addToScanCache(List<ScanDetail> scanList) {
         int numScanResultsKnown = 0; // Record number of scan results we knew about
         WifiConfiguration associatedConfig = null;
         boolean didAssociate = false;
@@ -165,56 +224,24 @@
 
         ArrayList<NetworkKey> unknownScanResults = new ArrayList<NetworkKey>();
 
-        for(ScanResult result: scanList) {
+        for (ScanDetail scanDetail : scanList) {
+            ScanResult result = scanDetail.getScanResult();
             if (result.SSID == null) continue;
 
-            // Make sure we record the last time we saw this result
-            result.seen = System.currentTimeMillis();
-
-            // Fetch the previous instance for this result
-            ScanResult sr = scanResultCache.get(result.BSSID);
-            if (sr != null) {
-                if (mWifiConfigStore.scanResultRssiLevelPatchUp != 0
-                        && result.level == 0
-                        && sr.level < -20) {
-                    // A 'zero' RSSI reading is most likely a chip problem which returns
-                    // an unknown RSSI, hence ignore it
-                    result.level = sr.level;
-                }
-
-                // If there was a previous cache result for this BSSID, average the RSSI values
-                result.averageRssi(sr.level, sr.seen, mScanResultMaximumAge);
-
-                // Remove the previous Scan Result - this is not necessary
-                scanResultCache.remove(result.BSSID);
-            } else if (mWifiConfigStore.scanResultRssiLevelPatchUp != 0 && result.level == 0) {
-                // A 'zero' RSSI reading is most likely a chip problem which returns
-                // an unknown RSSI, hence initialize it to a sane value
-                result.level = mWifiConfigStore.scanResultRssiLevelPatchUp;
+            if (VDBG) {
+                logDbg(" addToScanCache " + result.SSID + " " + result.BSSID
+                        + " tsf=" + result.timestamp
+                        + " now= " + now + " uptime=" + SystemClock.uptimeMillis()
+                        + " elapsed=" + SystemClock.elapsedRealtime());
             }
 
+            // Make sure we record the last time we saw this result
+            scanDetail.setSeen();
+
+            averageRssiAndRemoveFromCache(result);
+
             if (!mNetworkScoreCache.isScoredNetwork(result)) {
-                WifiKey wkey;
-                // Quoted SSIDs are the only one valid at this stage
-                try {
-                    wkey = new WifiKey("\"" + result.SSID + "\"", result.BSSID);
-                } catch (IllegalArgumentException e) {
-                    logDbg("AutoJoinController: received badly encoded SSID=[" + result.SSID +
-                            "] ->skipping this network");
-                    wkey = null;
-                }
-                if (wkey != null) {
-                    NetworkKey nkey = new NetworkKey(wkey);
-                    //if we don't know this scan result then request a score from the scorer
-                    unknownScanResults.add(nkey);
-                }
-                if (VDBG) {
-                    String cap = "";
-                    if (result.capabilities != null)
-                        cap = result.capabilities;
-                    logDbg(result.SSID + " " + result.BSSID + " rssi="
-                            + result.level + " cap " + cap + " is not scored");
-                }
+                addToUnscoredNetworks(result, unknownScanResults);
             } else {
                 if (VDBG) {
                     String cap = "";
@@ -227,29 +254,15 @@
             }
 
             // scanResultCache.put(result.BSSID, new ScanResult(result));
-            scanResultCache.put(result.BSSID, result);
+            scanResultCache.put(result.BSSID, scanDetail);
             // Add this BSSID to the scanResultCache of a Saved WifiConfiguration
-            didAssociate = mWifiConfigStore.updateSavedNetworkHistory(result);
+            didAssociate = mWifiConfigStore.updateSavedNetworkHistory(scanDetail);
 
             // If not successful, try to associate this BSSID to an existing Saved WifiConfiguration
             if (!didAssociate) {
                 // We couldn't associate the scan result to a Saved WifiConfiguration
                 // Hence it is untrusted
                 result.untrusted = true;
-                associatedConfig = mWifiConfigStore.associateWithConfiguration(result);
-                if (associatedConfig != null && associatedConfig.SSID != null) {
-                    if (VDBG) {
-                        logDbg("addToScanCache save associated config "
-                                + associatedConfig.SSID + " with " + result.SSID
-                                + " status " + associatedConfig.autoJoinStatus
-                                + " reason " + associatedConfig.disableReason
-                                + " tsp " + associatedConfig.blackListTimestamp
-                                + " was " + (now - associatedConfig.blackListTimestamp));
-                    }
-                    mWifiStateMachine.sendMessage(
-                            WifiStateMachine.CMD_AUTO_SAVE_NETWORK, associatedConfig);
-                    didAssociate = true;
-                }
             } else {
                 // If the scan result has been blacklisted fir 18 hours -> unblacklist
                 if ((now - result.blackListTimestamp) > loseBlackListHardMilli) {
@@ -258,7 +271,7 @@
             }
             if (didAssociate) {
                 numScanResultsKnown++;
-                result.isAutoJoinCandidate ++;
+                result.isAutoJoinCandidate++;
             } else {
                 result.isAutoJoinCandidate = 0;
             }
@@ -279,26 +292,26 @@
 
     void logDbg(String message, boolean stackTrace) {
         if (stackTrace) {
-            Log.e(TAG, message + " stack:"
+            Log.d(TAG, message + " stack:"
                     + Thread.currentThread().getStackTrace()[2].getMethodName() + " - "
                     + Thread.currentThread().getStackTrace()[3].getMethodName() + " - "
                     + Thread.currentThread().getStackTrace()[4].getMethodName() + " - "
                     + Thread.currentThread().getStackTrace()[5].getMethodName());
         } else {
-            Log.e(TAG, message);
+            Log.d(TAG, message);
         }
     }
 
     // Called directly from WifiStateMachine
     int newSupplicantResults(boolean doAutoJoin) {
         int numScanResultsKnown;
-        List<ScanResult> scanList = mWifiStateMachine.getScanResultsListNoCopyUnsync();
+        List<ScanDetail> scanList = mWifiStateMachine.getScanResultsListNoCopyUnsync();
         numScanResultsKnown = addToScanCache(scanList);
         ageScanResultsOut(mScanResultMaximumAge);
         if (DBG) {
             logDbg("newSupplicantResults size=" + Integer.valueOf(scanResultCache.size())
-                        + " known=" + numScanResultsKnown + " "
-                        + doAutoJoin);
+                    + " known=" + numScanResultsKnown + " "
+                    + doAutoJoin);
         }
         if (doAutoJoin) {
             attemptAutoJoin();
@@ -316,7 +329,7 @@
      * results as normal.
      */
     void newHalScanResults() {
-        List<ScanResult> scanList = null;//mWifiScanner.syncGetScanResultsList();
+        List<ScanDetail> scanList = null;//mWifiScanner.syncGetScanResultsList();
         String akm = WifiParser.parse_akm(null, null);
         logDbg(akm);
         addToScanCache(scanList);
@@ -326,7 +339,7 @@
     }
 
     /**
-     *  network link quality changed, called directly from WifiTrafficPoller,
+     * network link quality changed, called directly from WifiTrafficPoller,
      * or by listening to Link Quality intent
      */
     void linkQualitySignificantChange() {
@@ -350,7 +363,7 @@
         if (currentNetwork == null) {
             // Return any absurdly high score, if we are not connected there is no current
             // network to...
-           return 1000;
+            return 1000;
         }
 
         if (candidate.configKey(true).equals(currentNetwork.configKey(true))) {
@@ -375,7 +388,7 @@
             // above RSSI/scorer based selection of linked configuration (+/- 50)
             // by reducing order by -100
             order = order - 100;
-            if (VDBG)   {
+            if (VDBG) {
                 logDbg("     ...and prefers -100 " + currentNetwork.configKey()
                         + " over " + candidate.configKey()
                         + " because it is the last selected -> "
@@ -388,7 +401,7 @@
             // above RSSI/scorer based selection of linked configuration (+/- 50)
             // by increasing order by +100
             order = order + 100;
-            if (VDBG)   {
+            if (VDBG) {
                 logDbg("     ...and prefers +100 " + candidate.configKey()
                         + " over " + currentNetwork.configKey()
                         + " because it is the last selected -> "
@@ -408,7 +421,7 @@
      *
      * @param netId
      * @param userTriggered : if the update come from WiFiManager
-     * @param connect : if the update includes a connect
+     * @param connect       : if the update includes a connect
      */
     public void updateConfigurationHistory(int netId, boolean userTriggered, boolean connect) {
         WifiConfiguration selected = mWifiConfigStore.getWifiConfiguration(netId);
@@ -472,30 +485,17 @@
                         continue;
                     }
 
-                    // Compare RSSI values so as to evaluate the strength of the user preference
-                    int order = compareWifiConfigurationsRSSI(config, selected, null);
-
-                    if (order < -30) {
-                        // Selected configuration is worse than the visible configuration
-                        // hence register a strong choice so as autojoin cannot override this
-                        // for instance, the user has select a network
-                        // with 1 bar over a network with 3 bars...
-                        choice = 60;
-                    } else if (order < -20) {
-                        choice = 50;
-                    } else if (order < -10) {
-                        choice = 40;
-                    } else if (order < 20) {
-                        // Selected configuration is about same or has a slightly better RSSI
-                        // hence register a weaker choice, here a difference of at least +/-30 in
-                        // RSSI comparison triggered by autoJoin will override the choice
-                        choice = 30;
-                    } else {
-                        // Selected configuration is better than the visible configuration
-                        // hence we do not know if the user prefers this configuration strongly
-                        choice = 20;
+                    // If the selection was made while config was visible with reasonably good RSSI
+                    // then register the user preference, else ignore
+                    if (config.visibility == null ||
+                            (config.visibility.rssi24 < mWifiConfigStore.thresholdBadRssi24.get()
+                            && config.visibility.rssi5 < mWifiConfigStore.thresholdBadRssi5.get())
+                    ) {
+                        continue;
                     }
 
+                    choice = MAX_RSSI_DELTA + 10; // Make sure the choice overrides the RSSI diff
+
                     // The selected configuration was preferred over a recently seen config
                     // hence remember the user's choice:
                     // add the recently seen config to the selected's connectChoices array
@@ -508,11 +508,6 @@
                             + " over " + config.configKey(true)
                             + " choice " + Integer.toString(choice));
 
-                    Integer currentChoice = selected.connectChoices.get(config.configKey(true));
-                    if (currentChoice != null) {
-                        // User has made this choice multiple time in a row, so bump up a lot
-                        choice += currentChoice.intValue();
-                    }
                     // Add the visible config to the selected's connect choice list
                     selected.connectChoices.put(config.configKey(true), choice);
 
@@ -525,21 +520,21 @@
                         config.connectChoices.remove(selected.configKey(true));
 
                         if (selected.linkedConfigurations != null) {
-                           // Remove the selected's linked configuration from the
-                           // recently seen config's connectChoice list
-                           for (String key : selected.linkedConfigurations.keySet()) {
-                               config.connectChoices.remove(key);
-                           }
+                            // Remove the selected's linked configuration from the
+                            // recently seen config's connectChoice list
+                            for (String key : selected.linkedConfigurations.keySet()) {
+                                config.connectChoices.remove(key);
+                            }
                         }
                     }
                 }
                 if (found == false) {
-                     // We haven't found the configuration that the user just selected in our
-                     // scan cache.
-                     // In that case we will need a new scan before attempting to connect to this
-                     // configuration anyhow and thus we can process the scan results then.
-                     logDbg("updateConfigurationHistory try to connect to an old network!! : "
-                             + selected.configKey());
+                    // We haven't found the configuration that the user just selected in our
+                    // scan cache.
+                    // In that case we will need a new scan before attempting to connect to this
+                    // configuration anyhow and thus we can process the scan results then.
+                    logDbg("updateConfigurationHistory try to connect to an old network!! : "
+                            + selected.configKey());
                 }
 
                 if (selected.connectChoices != null) {
@@ -556,39 +551,44 @@
         }
     }
 
-    int getConnectChoice(WifiConfiguration source, WifiConfiguration target) {
-        Integer choice = null;
+    int getConnectChoice(WifiConfiguration source, WifiConfiguration target, boolean strict) {
+        int choice = 0;
         if (source == null || target == null) {
             return 0;
         }
 
         if (source.connectChoices != null
                 && source.connectChoices.containsKey(target.configKey(true))) {
-            choice = source.connectChoices.get(target.configKey(true));
+            Integer val = source.connectChoices.get(target.configKey(true));
+            if (val != null) {
+                choice = val;
+            }
         } else if (source.linkedConfigurations != null) {
             for (String key : source.linkedConfigurations.keySet()) {
                 WifiConfiguration config = mWifiConfigStore.getWifiConfiguration(key);
                 if (config != null) {
                     if (config.connectChoices != null) {
-                        choice = config.connectChoices.get(target.configKey(true));
+                        Integer val = config.connectChoices.get(target.configKey(true));
+                        if (val != null) {
+                            choice = val;
+                        }
                     }
                 }
             }
         }
 
-        if (choice == null) {
-            //We didn't find the connect choice
-            return 0;
-        } else {
-            if (choice.intValue() < 0) {
-                choice = 20; // Compatibility with older files
-            }
-            return choice.intValue();
+        if (!strict && choice == 0) {
+            // We didn't find the connect choice; fallback to some default choices
+            int sourceScore = getSecurityScore(source);
+            int targetScore = getSecurityScore(target);
+            choice = sourceScore - targetScore;
         }
+
+        return choice;
     }
 
-    int compareWifiConfigurationsFromVisibility(WifiConfiguration a, int aRssiBoost,
-             WifiConfiguration b, int bRssiBoost) {
+    int compareWifiConfigurationsFromVisibility(WifiConfiguration.Visibility a, int aRssiBoost,
+                                                String dbgA, WifiConfiguration.Visibility b, int bRssiBoost, String dbgB) {
 
         int aRssiBoost5 = 0; // 5GHz RSSI boost to apply for purpose band selection (5GHz pref)
         int bRssiBoost5 = 0; // 5GHz RSSI boost to apply for purpose band selection (5GHz pref)
@@ -606,17 +606,17 @@
          * This implements band preference where we prefer 5GHz if RSSI5 is good enough, whereas
          * we prefer 2.4GHz otherwise.
          */
-        aRssiBoost5 = rssiBoostFrom5GHzRssi(a.visibility.rssi5, a.configKey() + "->");
-        bRssiBoost5 = rssiBoostFrom5GHzRssi(b.visibility.rssi5, b.configKey() + "->");
+        aRssiBoost5 = rssiBoostFrom5GHzRssi(a.rssi5, dbgA + "->");
+        bRssiBoost5 = rssiBoostFrom5GHzRssi(b.rssi5, dbgB + "->");
 
         // Select which band to use for a
-        if (a.visibility.rssi5 + aRssiBoost5 > a.visibility.rssi24) {
+        if (a.rssi5 + aRssiBoost5 > a.rssi24) {
             // Prefer a's 5GHz
             aPrefers5GHz = true;
         }
 
         // Select which band to use for b
-        if (b.visibility.rssi5 + bRssiBoost5 > b.visibility.rssi24) {
+        if (b.rssi5 + bRssiBoost5 > b.rssi24) {
             // Prefer b's 5GHz
             bPrefers5GHz = true;
         }
@@ -626,13 +626,13 @@
                 // If both a and b are on 5GHz then we don't apply the 5GHz RSSI boost to either
                 // one, but directly compare the RSSI values, this improves stability,
                 // since the 5GHz RSSI boost can introduce large fluctuations
-                aScore = a.visibility.rssi5 + aRssiBoost;
+                aScore = a.rssi5 + aRssiBoost;
             } else {
                 // If only a is on 5GHz, then apply the 5GHz preference boost to a
-                aScore = a.visibility.rssi5 + aRssiBoost + aRssiBoost5;
+                aScore = a.rssi5 + aRssiBoost + aRssiBoost5;
             }
         } else {
-            aScore = a.visibility.rssi24 + aRssiBoost;
+            aScore = a.rssi24 + aRssiBoost;
         }
 
         if (bPrefers5GHz) {
@@ -640,29 +640,30 @@
                 // If both a and b are on 5GHz then we don't apply the 5GHz RSSI boost to either
                 // one, but directly compare the RSSI values, this improves stability,
                 // since the 5GHz RSSI boost can introduce large fluctuations
-                bScore = b.visibility.rssi5 + bRssiBoost;
+                bScore = b.rssi5 + bRssiBoost;
             } else {
                 // If only b is on 5GHz, then apply the 5GHz preference boost to b
-                bScore = b.visibility.rssi5 + bRssiBoost + bRssiBoost5;
+                bScore = b.rssi5 + bRssiBoost + bRssiBoost5;
             }
         } else {
-            bScore = b.visibility.rssi24 + bRssiBoost;
+            bScore = b.rssi24 + bRssiBoost;
         }
+
         if (VDBG) {
-            logDbg("        " + a.configKey() + " is5=" + aPrefers5GHz + " score=" + aScore
-                    + " " + b.configKey() + " is5=" + bPrefers5GHz + " score=" + bScore);
+            logDbg("        " + dbgA + " is5=" + aPrefers5GHz + " score=" + aScore
+                    + " " + dbgB + " is5=" + bPrefers5GHz + " score=" + bScore);
         }
 
         // Debug only, record RSSI comparison parameters
-        if (a.visibility != null) {
-            a.visibility.score = aScore;
-            a.visibility.currentNetworkBoost = aRssiBoost;
-            a.visibility.bandPreferenceBoost = aRssiBoost5;
+        if (a != null) {
+            a.score = aScore;
+            a.currentNetworkBoost = aRssiBoost;
+            a.bandPreferenceBoost = aRssiBoost5;
         }
-        if (b.visibility != null) {
-            b.visibility.score = bScore;
-            b.visibility.currentNetworkBoost = bRssiBoost;
-            b.visibility.bandPreferenceBoost = bRssiBoost5;
+        if (b != null) {
+            b.score = bScore;
+            b.currentNetworkBoost = bRssiBoost;
+            b.bandPreferenceBoost = bRssiBoost5;
         }
 
         // Compare a and b
@@ -686,9 +687,6 @@
         int aRssiBoost = 0;
         int bRssiBoost = 0;
 
-        int scoreA;
-        int scoreB;
-
         // Retrieve the visibility
         WifiConfiguration.Visibility astatus = a.visibility;
         WifiConfiguration.Visibility bstatus = b.visibility;
@@ -707,23 +705,25 @@
             }
         }
 
-        if (VDBG)  {
+        if (VDBG) {
             logDbg("    compareWifiConfigurationsRSSI: " + a.configKey()
-                    + " rssi=" + Integer.toString(astatus.rssi24)
-                    + "," + Integer.toString(astatus.rssi5)
-                    + " boost=" + Integer.toString(aRssiBoost)
-                    + " " + b.configKey() + " rssi="
-                    + Integer.toString(bstatus.rssi24) + ","
-                    + Integer.toString(bstatus.rssi5)
-                    + " boost=" + Integer.toString(bRssiBoost)
+                            + " rssi=" + Integer.toString(astatus.rssi24)
+                            + "," + Integer.toString(astatus.rssi5)
+                            + " boost=" + Integer.toString(aRssiBoost)
+                            + " " + b.configKey() + " rssi="
+                            + Integer.toString(bstatus.rssi24) + ","
+                            + Integer.toString(bstatus.rssi5)
+                            + " boost=" + Integer.toString(bRssiBoost)
             );
         }
 
-        order = compareWifiConfigurationsFromVisibility(a, aRssiBoost, b, bRssiBoost);
+        order = compareWifiConfigurationsFromVisibility(
+                a.visibility, aRssiBoost, a.configKey(),
+                b.visibility, bRssiBoost, b.configKey());
 
-        // Normalize the order to [-50, +50]
-        if (order > 50) order = 50;
-        else if (order < -50) order = -50;
+        // Normalize the order to [-50, +50] = [ -MAX_RSSI_DELTA, MAX_RSSI_DELTA]
+        if (order > MAX_RSSI_DELTA) order = MAX_RSSI_DELTA;
+        else if (order < -MAX_RSSI_DELTA) order = -MAX_RSSI_DELTA;
 
         if (VDBG) {
             String prefer = " = ";
@@ -750,64 +750,85 @@
 
     /**
      * b/18490330 only use scorer for untrusted networks
-     *
-    int compareWifiConfigurationsWithScorer(WifiConfiguration a, WifiConfiguration b) {
-
-        boolean aIsActive = false;
-        boolean bIsActive = false;
-
-        // Apply Hysteresis : boost RSSI of current configuration before
-        // looking up the score
-        if (null != mCurrentConfigurationKey) {
-            if (a.configKey().equals(mCurrentConfigurationKey)) {
-                aIsActive = true;
-            } else if (b.configKey().equals(mCurrentConfigurationKey)) {
-                bIsActive = true;
-            }
-        }
-        int scoreA = getConfigNetworkScore(a, mScanResultAutoJoinAge, aIsActive);
-        int scoreB = getConfigNetworkScore(b, mScanResultAutoJoinAge, bIsActive);
-
-        // Both configurations need to have a score for the scorer to be used
-        // ...and the scores need to be different:-)
-        if (scoreA == WifiNetworkScoreCache.INVALID_NETWORK_SCORE
-                || scoreB == WifiNetworkScoreCache.INVALID_NETWORK_SCORE) {
-            if (VDBG)  {
-                logDbg("    compareWifiConfigurationsWithScorer no-scores: "
-                        + a.configKey()
-                        + " "
-                        + b.configKey());
-            }
-            return 0;
-        }
-
-        if (VDBG) {
-            String prefer = " = ";
-            if (scoreA < scoreB) {
-                prefer = " < ";
-            } if (scoreA > scoreB) {
-                prefer = " > ";
-            }
-            logDbg("    compareWifiConfigurationsWithScorer " + a.configKey()
-                    + " rssi=(" + a.visibility.rssi24
-                    + "," + a.visibility.rssi5
-                    + ") num=(" + a.visibility.num24
-                    + "," + a.visibility.num5 + ")"
-                    + " sc=" + scoreA
-                    + prefer + b.configKey()
-                    + " rssi=(" + b.visibility.rssi24
-                    + "," + b.visibility.rssi5
-                    + ") num=(" + b.visibility.num24
-                    + "," + b.visibility.num5 + ")"
-                    + " sc=" + scoreB
-                    + " -> " + Integer.toString(scoreB - scoreA));
-        }
-
-        // If scoreA > scoreB, the comparison is descending hence the return value is negative
-        return scoreB - scoreA;
-    }
+     * <p/>
+     * int compareWifiConfigurationsWithScorer(WifiConfiguration a, WifiConfiguration b) {
+     * <p/>
+     * boolean aIsActive = false;
+     * boolean bIsActive = false;
+     * <p/>
+     * // Apply Hysteresis : boost RSSI of current configuration before
+     * // looking up the score
+     * if (null != mCurrentConfigurationKey) {
+     * if (a.configKey().equals(mCurrentConfigurationKey)) {
+     * aIsActive = true;
+     * } else if (b.configKey().equals(mCurrentConfigurationKey)) {
+     * bIsActive = true;
+     * }
+     * }
+     * int scoreA = getConfigNetworkScore(a, mScanResultAutoJoinAge, aIsActive);
+     * int scoreB = getConfigNetworkScore(b, mScanResultAutoJoinAge, bIsActive);
+     * <p/>
+     * // Both configurations need to have a score for the scorer to be used
+     * // ...and the scores need to be different:-)
+     * if (scoreA == WifiNetworkScoreCache.INVALID_NETWORK_SCORE
+     * || scoreB == WifiNetworkScoreCache.INVALID_NETWORK_SCORE) {
+     * if (VDBG)  {
+     * logDbg("    compareWifiConfigurationsWithScorer no-scores: "
+     * + a.configKey()
+     * + " "
+     * + b.configKey());
+     * }
+     * return 0;
+     * }
+     * <p/>
+     * if (VDBG) {
+     * String prefer = " = ";
+     * if (scoreA < scoreB) {
+     * prefer = " < ";
+     * } if (scoreA > scoreB) {
+     * prefer = " > ";
+     * }
+     * logDbg("    compareWifiConfigurationsWithScorer " + a.configKey()
+     * + " rssi=(" + a.visibility.rssi24
+     * + "," + a.visibility.rssi5
+     * + ") num=(" + a.visibility.num24
+     * + "," + a.visibility.num5 + ")"
+     * + " sc=" + scoreA
+     * + prefer + b.configKey()
+     * + " rssi=(" + b.visibility.rssi24
+     * + "," + b.visibility.rssi5
+     * + ") num=(" + b.visibility.num24
+     * + "," + b.visibility.num5 + ")"
+     * + " sc=" + scoreB
+     * + " -> " + Integer.toString(scoreB - scoreA));
+     * }
+     * <p/>
+     * // If scoreA > scoreB, the comparison is descending hence the return value is negative
+     * return scoreB - scoreA;
+     * }
      */
 
+    int getSecurityScore(WifiConfiguration config) {
+
+        if (TextUtils.isEmpty(config.SSID) == false) {
+            if (config.allowedKeyManagement.get(KeyMgmt.WPA_EAP)
+                    || config.allowedKeyManagement.get(KeyMgmt.WPA_PSK)
+                    || config.allowedKeyManagement.get(KeyMgmt.WPA2_PSK)) {
+                /* enterprise or PSK networks get highest score */
+                return 100;
+            } else if (config.allowedKeyManagement.get(KeyMgmt.NONE)) {
+                /* open networks have lowest score */
+                return 33;
+            }
+        } else if (TextUtils.isEmpty(config.FQDN) == false) {
+            /* passpoint networks have medium preference */
+            return 66;
+        }
+
+        /* bad network */
+        return 0;
+    }
+
     int compareWifiConfigurations(WifiConfiguration a, WifiConfiguration b) {
         int order = 0;
         boolean linked = false;
@@ -845,7 +866,7 @@
         if (!linked) {
             int choice;
 
-            choice = getConnectChoice(a, b);
+            choice = getConnectChoice(a, b, false);
             if (choice > 0) {
                 // a is of higher priority - descending
                 order = order - choice;
@@ -861,7 +882,7 @@
                 }
             }
 
-            choice = getConnectChoice(b, a);
+            choice = getConnectChoice(b, a, false);
             if (choice > 0) {
                 // a is of lower priority - ascending
                 order = order + choice;
@@ -948,13 +969,13 @@
             return 0;
         }
         if (rssi
-                > mWifiConfigStore.bandPreferenceBoostThreshold5) {
+                > mWifiConfigStore.bandPreferenceBoostThreshold5.get()) {
             // Boost by 2 dB for each point
             //    Start boosting at -65
             //    Boost by 20 if above -55
             //    Boost by 40 if abore -45
             int boost = mWifiConfigStore.bandPreferenceBoostFactor5
-                    *(rssi - mWifiConfigStore.bandPreferenceBoostThreshold5);
+                    * (rssi - mWifiConfigStore.bandPreferenceBoostThreshold5.get());
             if (boost > 50) {
                 // 50 dB boost allows jumping from 2.4 to 5GHz
                 // consistently
@@ -967,38 +988,43 @@
         }
 
         if (rssi
-                < mWifiConfigStore.bandPreferencePenaltyThreshold5) {
+                < mWifiConfigStore.bandPreferencePenaltyThreshold5.get()) {
             // penalize if < -75
             int boost = mWifiConfigStore.bandPreferencePenaltyFactor5
-                    *(rssi - mWifiConfigStore.bandPreferencePenaltyThreshold5);
+                    * (rssi - mWifiConfigStore.bandPreferencePenaltyThreshold5.get());
             return boost;
         }
         return 0;
     }
-        /**
-         * attemptRoam() function implements the core of the same SSID switching algorithm
-         *
-         * Run thru all recent scan result of a WifiConfiguration and select the
-         * best one.
-         */
+
+    /**
+     * attemptRoam() function implements the core of the same SSID switching algorithm
+     * <p/>
+     * Run thru all recent scan result of a WifiConfiguration and select the
+     * best one.
+     */
     public ScanResult attemptRoam(ScanResult a,
                                   WifiConfiguration current, int age, String currentBSSID) {
         if (current == null) {
-            if (VDBG)   {
+            if (VDBG) {
                 logDbg("attemptRoam not associated");
             }
             return a;
         }
-        if (current.scanResultCache == null) {
-            if (VDBG)   {
+
+        ScanDetailCache scanDetailCache =
+                mWifiConfigStore.getScanDetailCache(current);
+
+        if (scanDetailCache == null) {
+            if (VDBG) {
                 logDbg("attemptRoam no scan cache");
             }
             return a;
         }
-        if (current.scanResultCache.size() > 6) {
-            if (VDBG)   {
+        if (scanDetailCache.size() > 6) {
+            if (VDBG) {
                 logDbg("attemptRoam scan cache size "
-                        + current.scanResultCache.size() + " --> bail");
+                        + scanDetailCache.size() + " --> bail");
             }
             // Implement same SSID roaming only for configurations
             // that have less than 4 BSSIDs
@@ -1006,7 +1032,7 @@
         }
 
         if (current.BSSID != null && !current.BSSID.equals("any")) {
-            if (DBG)   {
+            if (DBG) {
                 logDbg("attemptRoam() BSSID is set "
                         + current.BSSID + " -> bail");
             }
@@ -1017,13 +1043,14 @@
         // relative strength of 5 and 2.4 GHz BSSIDs
         long nowMs = System.currentTimeMillis();
 
-        for (ScanResult b : current.scanResultCache.values()) {
+        for (ScanDetail sd : scanDetailCache.values()) {
+            ScanResult b = sd.getScanResult();
             int bRssiBoost5 = 0;
             int aRssiBoost5 = 0;
             int bRssiBoost = 0;
             int aRssiBoost = 0;
-            if ((b.seen == 0) || (b.BSSID == null)
-                    || ((nowMs - b.seen) > age)
+            if ((sd.getSeen() == 0) || (b.BSSID == null)
+                    || ((nowMs - sd.getSeen()) > age)
                     || b.autoJoinStatus != ScanResult.ENABLED
                     || b.numIpConfigFailures > 8) {
                 continue;
@@ -1038,10 +1065,10 @@
             if (b.numIpConfigFailures < (a.numIpConfigFailures - 1)) {
                 // Prefer a BSSID that doesn't have less number of Ip config failures
                 logDbg("attemptRoam: "
-                        + b.BSSID + " rssi=" + b.level + " ipfail=" +b.numIpConfigFailures
+                        + b.BSSID + " rssi=" + b.level + " ipfail=" + b.numIpConfigFailures
                         + " freq=" + b.frequency
                         + " > "
-                        + a.BSSID + " rssi=" + a.level + " ipfail=" +a.numIpConfigFailures
+                        + a.BSSID + " rssi=" + a.level + " ipfail=" + a.numIpConfigFailures
                         + " freq=" + a.frequency);
                 a = b;
                 continue;
@@ -1050,14 +1077,14 @@
             // Apply hysteresis: we favor the currentBSSID by giving it a boost
             if (currentBSSID != null && currentBSSID.equals(b.BSSID)) {
                 // Reduce the benefit of hysteresis if RSSI <= -75
-                if (b.level <= mWifiConfigStore.bandPreferencePenaltyThreshold5) {
+                if (b.level <= mWifiConfigStore.bandPreferencePenaltyThreshold5.get()) {
                     bRssiBoost = mWifiConfigStore.associatedHysteresisLow;
                 } else {
                     bRssiBoost = mWifiConfigStore.associatedHysteresisHigh;
                 }
             }
             if (currentBSSID != null && currentBSSID.equals(a.BSSID)) {
-                if (a.level <= mWifiConfigStore.bandPreferencePenaltyThreshold5) {
+                if (a.level <= mWifiConfigStore.bandPreferencePenaltyThreshold5.get()) {
                     // Reduce the benefit of hysteresis if RSSI <= -75
                     aRssiBoost = mWifiConfigStore.associatedHysteresisLow;
                 } else {
@@ -1082,9 +1109,9 @@
                 aRssiBoost5 = rssiBoostFrom5GHzRssi(a.level + aRssiBoost, a.BSSID);
             }
 
-            if (VDBG)  {
+            if (VDBG) {
                 String comp = " < ";
-                if (b.level + bRssiBoost + bRssiBoost5 > a.level +aRssiBoost + aRssiBoost5) {
+                if (b.level + bRssiBoost + bRssiBoost5 > a.level + aRssiBoost + aRssiBoost5) {
                     comp = " > ";
                 }
                 logDbg("attemptRoam: "
@@ -1097,13 +1124,13 @@
 
             // Compare the RSSIs after applying the hysteresis boost and the 5GHz
             // boost if applicable
-            if (b.level + bRssiBoost + bRssiBoost5 > a.level +aRssiBoost + aRssiBoost5) {
+            if (b.level + bRssiBoost + bRssiBoost5 > a.level + aRssiBoost + aRssiBoost5) {
                 // b is the better BSSID
                 a = b;
             }
         }
         if (a != null) {
-            if (VDBG)  {
+            if (VDBG) {
                 StringBuilder sb = new StringBuilder();
                 sb.append("attemptRoam: " + current.configKey() +
                         " Found " + a.BSSID + " rssi=" + a.level + " freq=" + a.frequency);
@@ -1119,9 +1146,9 @@
 
     /**
      * getNetworkScore()
-     *
+     * <p/>
      * if scorer is present, get the network score of a WifiConfiguration
-     *
+     * <p/>
      * Note: this should be merge with setVisibility
      *
      * @param config
@@ -1136,7 +1163,8 @@
             }
             return WifiNetworkScoreCache.INVALID_NETWORK_SCORE;
         }
-        if (config.scanResultCache == null) {
+
+        if (mWifiConfigStore.getScanDetailCache(config) == null) {
             if (VDBG) {
                 logDbg("       getConfigNetworkScore for " + config.configKey()
                         + " -> no scan cache");
@@ -1150,8 +1178,9 @@
         int startScore = -10000;
 
         // Run thru all cached scan results
-        for (ScanResult result : config.scanResultCache.values()) {
-            if ((nowMs - result.seen) < age) {
+        for (ScanDetail sd : mWifiConfigStore.getScanDetailCache(config).values()) {
+            ScanResult result = sd.getScanResult();
+            if ((nowMs - sd.getSeen()) < age) {
                 int sc = mNetworkScoreCache.getNetworkScore(result, isActive);
                 if (sc > startScore) {
                     startScore = sc;
@@ -1204,8 +1233,9 @@
         // Otherwise, we stop here if the currently selected network has a score. If it doesn't, we
         // keep going - it could be that another BSSID is in range (has been seen recently) which
         // has a score, even if the one we're immediately connected to doesn't.
-        ScanResult currentScanResult =  mWifiStateMachine.getCurrentScanResult();
-        boolean currentNetworkHasScoreCurve = mNetworkScoreCache.hasScoreCurve(currentScanResult);
+        ScanResult currentScanResult = mWifiStateMachine.getCurrentScanResult();
+        boolean currentNetworkHasScoreCurve = currentScanResult != null
+                && mNetworkScoreCache.hasScoreCurve(currentScanResult);
         if (ephemeralOutOfRangeTimeoutMs <= 0 || currentNetworkHasScoreCurve) {
             if (DBG) {
                 if (currentNetworkHasScoreCurve) {
@@ -1218,14 +1248,16 @@
             return currentNetworkHasScoreCurve;
         }
 
-        if (config.scanResultCache == null || config.scanResultCache.isEmpty()) {
+        if (mWifiConfigStore.getScanDetailCache(config) == null
+                || mWifiConfigStore.getScanDetailCache(config).isEmpty()) {
             return false;
         }
 
         long currentTimeMs = System.currentTimeMillis();
-        for (ScanResult result : config.scanResultCache.values()) {
-            if (currentTimeMs > result.seen
-                    && currentTimeMs - result.seen < ephemeralOutOfRangeTimeoutMs
+        for (ScanDetail sd : mWifiConfigStore.getScanDetailCache(config).values()) {
+            ScanResult result = sd.getScanResult();
+            if (currentTimeMs > sd.getSeen()
+                    && currentTimeMs - sd.getSeen() < ephemeralOutOfRangeTimeoutMs
                     && mNetworkScoreCache.hasScoreCurve(result)) {
                 if (DBG) {
                     logDbg("Found scored BSSID, keeping network: " + result.BSSID);
@@ -1240,6 +1272,163 @@
         return false;
     }
 
+    // After WifiStateMachine ask the supplicant to associate or reconnect
+    // we might still obtain scan results from supplicant
+    // however the supplicant state in the mWifiInfo and supplicant state tracker
+    // are updated when we get the supplicant state change message which can be
+    // processed after the SCAN_RESULT message, so at this point the framework doesn't
+    // know that supplicant is ASSOCIATING.
+    // A good fix for this race condition would be for the WifiStateMachine to add
+    // a new transient state where it expects to get the supplicant message indicating
+    // that it started the association process and within which critical operations
+    // like autojoin should be deleted.
+
+    // This transient state would remove the need for the roam Wathchdog which
+    // basically does that.
+
+    // At the moment, we just query the supplicant state synchronously with the
+    // mWifiNative.status() command, which allow us to know that
+    // supplicant has started association process, even though we didnt yet get the
+    // SUPPLICANT_STATE_CHANGE message.
+
+    private static final List<String> ASSOC_STATES = Arrays.asList(
+            "ASSOCIATING",
+            "ASSOCIATED",
+            "FOUR_WAY_HANDSHAKE",
+            "GROUP_KEY_HANDSHAKE");
+
+    private int getNetID(String wpaStatus) {
+        if (VDBG) {
+            logDbg("attemptAutoJoin() status=" + wpaStatus);
+        }
+
+        try {
+            int id = WifiConfiguration.INVALID_NETWORK_ID;
+            String state = null;
+            BufferedReader br = new BufferedReader(new StringReader(wpaStatus));
+            String line;
+            while ((line = br.readLine()) != null) {
+                int split = line.indexOf('=');
+                if (split < 0) {
+                    continue;
+                }
+
+                String name = line.substring(0, split);
+                if (name.equals("id")) {
+                    try {
+                        id = Integer.parseInt(line.substring(split + 1));
+                        if (state != null) {
+                            break;
+                        }
+                    } catch (NumberFormatException nfe) {
+                        return WifiConfiguration.INVALID_NETWORK_ID;
+                    }
+                } else if (name.equals("wpa_state")) {
+                    state = line.substring(split + 1);
+                    if (ASSOC_STATES.contains(state)) {
+                        return WifiConfiguration.INVALID_NETWORK_ID;
+                    } else if (id >= 0) {
+                        break;
+                    }
+                }
+            }
+            return id;
+        } catch (IOException ioe) {
+            return WifiConfiguration.INVALID_NETWORK_ID;    // Won't happen
+        }
+    }
+
+    private boolean setCurrentConfigurationKey(WifiConfiguration currentConfig,
+                                               int supplicantNetId) {
+        if (currentConfig != null) {
+            if (supplicantNetId != currentConfig.networkId
+                    // https://b.corp.google.com/issue?id=16484607
+                    // mark this condition as an error only if the mismatched networkId are valid
+                    && supplicantNetId != WifiConfiguration.INVALID_NETWORK_ID
+                    && currentConfig.networkId != WifiConfiguration.INVALID_NETWORK_ID) {
+                logDbg("attemptAutoJoin() ERROR wpa_supplicant out of sync nid="
+                        + Integer.toString(supplicantNetId) + " WifiStateMachine="
+                        + Integer.toString(currentConfig.networkId));
+                mWifiStateMachine.disconnectCommand();
+                return false;
+            } else if (currentConfig.ephemeral && (!mAllowUntrustedConnections ||
+                    !haveRecentlySeenScoredBssid(currentConfig))) {
+                // The current connection is untrusted (the framework added it), but we're either
+                // no longer allowed to connect to such networks, the score has been nullified
+                // since we connected, or the scored BSSID has gone out of range.
+                // Drop the current connection and perform the rest of autojoin.
+                logDbg("attemptAutoJoin() disconnecting from unwanted ephemeral network");
+                mWifiStateMachine.disconnectCommand(Process.WIFI_UID,
+                        mAllowUntrustedConnections ? 1 : 0);
+                return false;
+            } else {
+                mCurrentConfigurationKey = currentConfig.configKey();
+                return true;
+            }
+        } else {
+            // If not invalid, then maybe in the process of associating, skip this attempt
+            return supplicantNetId == WifiConfiguration.INVALID_NETWORK_ID;
+        }
+    }
+
+    private void updateBlackListStatus(WifiConfiguration config, long now) {
+        // Wait for 5 minutes before reenabling config that have known,
+        // repeated connection or DHCP failures
+        if (config.disableReason == WifiConfiguration.DISABLED_DHCP_FAILURE
+                || config.disableReason
+                == WifiConfiguration.DISABLED_ASSOCIATION_REJECT
+                || config.disableReason
+                == WifiConfiguration.DISABLED_AUTH_FAILURE) {
+            if (config.blackListTimestamp == 0
+                    || (config.blackListTimestamp > now)) {
+                // Sanitize the timestamp
+                config.blackListTimestamp = now;
+            }
+            if ((now - config.blackListTimestamp) >
+                    mWifiConfigStore.wifiConfigBlacklistMinTimeMilli) {
+                // Re-enable the WifiConfiguration
+                config.status = WifiConfiguration.Status.ENABLED;
+
+                // Reset the blacklist condition
+                config.numConnectionFailures = 0;
+                config.numIpConfigFailures = 0;
+                config.numAuthFailures = 0;
+                config.setAutoJoinStatus(WifiConfiguration.AUTO_JOIN_ENABLED);
+
+                config.dirty = true;
+            } else {
+                if (VDBG) {
+                    long delay = mWifiConfigStore.wifiConfigBlacklistMinTimeMilli
+                            - (now - config.blackListTimestamp);
+                    logDbg("attemptautoJoin " + config.configKey()
+                            + " dont unblacklist yet, waiting for "
+                            + delay + " ms");
+                }
+            }
+        }
+        // Avoid networks disabled because of AUTH failure altogether
+        if (DBG) {
+            logDbg("attemptAutoJoin skip candidate due to auto join status "
+                    + Integer.toString(config.autoJoinStatus) + " key "
+                    + config.configKey(true)
+                    + " reason " + config.disableReason);
+        }
+    }
+
+    boolean underSoftThreshold(WifiConfiguration config) {
+        return config.visibility.rssi24 < mWifiConfigStore.thresholdUnblacklistThreshold24Soft.get()
+                && config.visibility.rssi5 < mWifiConfigStore.thresholdUnblacklistThreshold5Soft.get();
+    }
+
+    boolean underHardThreshold(WifiConfiguration config) {
+        return config.visibility.rssi24 < mWifiConfigStore.thresholdUnblacklistThreshold24Hard.get()
+                && config.visibility.rssi5 < mWifiConfigStore.thresholdUnblacklistThreshold5Hard.get();
+    }
+
+    boolean underThreshold(WifiConfiguration config, int rssi24, int rssi5) {
+        return config.visibility.rssi24 < rssi24 && config.visibility.rssi5 < rssi5;
+    }
+
     /**
      * attemptAutoJoin() function implements the core of the a network switching algorithm
      * Return false if no acceptable networks were found.
@@ -1249,79 +1438,32 @@
         didOverride = false;
         didBailDueToWeakRssi = false;
         int networkSwitchType = AUTO_JOIN_IDLE;
+        int age = mScanResultAutoJoinAge;
 
         long now = System.currentTimeMillis();
 
         String lastSelectedConfiguration = mWifiConfigStore.getLastSelectedConfiguration();
-
+        if (lastSelectedConfiguration != null) {
+            age = 14000;
+        }
         // Reset the currentConfiguration Key, and set it only if WifiStateMachine and
         // supplicant agree
         mCurrentConfigurationKey = null;
         WifiConfiguration currentConfiguration = mWifiStateMachine.getCurrentWifiConfiguration();
 
         WifiConfiguration candidate = null;
-
         // Obtain the subset of recently seen networks
         List<WifiConfiguration> list =
-                mWifiConfigStore.getRecentConfiguredNetworks(mScanResultAutoJoinAge, false);
+                mWifiConfigStore.getRecentConfiguredNetworks(age, false);
         if (list == null) {
-            if (VDBG)  logDbg("attemptAutoJoin nothing known=" +
-                    mWifiConfigStore.getconfiguredNetworkSize());
+            if (VDBG) logDbg("attemptAutoJoin nothing known=" +
+                    mWifiConfigStore.getConfiguredNetworksSize());
             return false;
         }
 
         // Find the currently connected network: ask the supplicant directly
-        String val = mWifiNative.status(true);
-        String status[] = val.split("\\r?\\n");
-        if (VDBG) {
-            logDbg("attemptAutoJoin() status=" + val + " split="
-                    + Integer.toString(status.length));
-        }
+        int supplicantNetId = getNetID(mWifiNative.status(true));
 
-        int supplicantNetId = -1;
-        for (String key : status) {
-            if (key.regionMatches(0, "id=", 0, 3)) {
-                int idx = 3;
-                supplicantNetId = 0;
-                while (idx < key.length()) {
-                    char c = key.charAt(idx);
-
-                    if ((c >= 0x30) && (c <= 0x39)) {
-                        supplicantNetId *= 10;
-                        supplicantNetId += c - 0x30;
-                        idx++;
-                    } else {
-                        break;
-                    }
-                }
-            } else if (key.contains("wpa_state=ASSOCIATING")
-                    || key.contains("wpa_state=ASSOCIATED")
-                    || key.contains("wpa_state=FOUR_WAY_HANDSHAKE")
-                    || key.contains("wpa_state=GROUP_KEY_HANDSHAKE")) {
-                if (DBG) {
-                    logDbg("attemptAutoJoin: bail out due to sup state " + key);
-                }
-                // After WifiStateMachine ask the supplicant to associate or reconnect
-                // we might still obtain scan results from supplicant
-                // however the supplicant state in the mWifiInfo and supplicant state tracker
-                // are updated when we get the supplicant state change message which can be
-                // processed after the SCAN_RESULT message, so at this point the framework doesn't
-                // know that supplicant is ASSOCIATING.
-                // A good fix for this race condition would be for the WifiStateMachine to add
-                // a new transient state where it expects to get the supplicant message indicating
-                // that it started the association process and within which critical operations
-                // like autojoin should be deleted.
-
-                // This transient state would remove the need for the roam Wathchdog which
-                // basically does that.
-
-                // At the moment, we just query the supplicant state synchronously with the
-                // mWifiNative.status() command, which allow us to know that
-                // supplicant has started association process, even though we didnt yet get the
-                // SUPPLICANT_STATE_CHANGE message.
-                return false;
-            }
-        }
         if (DBG) {
             String conf = "";
             String last = "";
@@ -1336,35 +1478,8 @@
                     + " ---> suppNetId=" + Integer.toString(supplicantNetId));
         }
 
-        if (currentConfiguration != null) {
-            if (supplicantNetId != currentConfiguration.networkId
-                    // https://b.corp.google.com/issue?id=16484607
-                    // mark this condition as an error only if the mismatched networkId are valid
-                    && supplicantNetId != WifiConfiguration.INVALID_NETWORK_ID
-                    && currentConfiguration.networkId != WifiConfiguration.INVALID_NETWORK_ID) {
-                logDbg("attemptAutoJoin() ERROR wpa_supplicant out of sync nid="
-                        + Integer.toString(supplicantNetId) + " WifiStateMachine="
-                        + Integer.toString(currentConfiguration.networkId));
-                mWifiStateMachine.disconnectCommand();
-                return false;
-            } else if (currentConfiguration.ephemeral && (!mAllowUntrustedConnections ||
-                    !haveRecentlySeenScoredBssid(currentConfiguration))) {
-                // The current connection is untrusted (the framework added it), but we're either
-                // no longer allowed to connect to such networks, the score has been nullified
-                // since we connected, or the scored BSSID has gone out of range.
-                // Drop the current connection and perform the rest of autojoin.
-                logDbg("attemptAutoJoin() disconnecting from unwanted ephemeral network");
-                mWifiStateMachine.disconnectCommand(Process.WIFI_UID,
-                        mAllowUntrustedConnections ? 1 : 0);
-                return false;
-            } else {
-                mCurrentConfigurationKey = currentConfiguration.configKey();
-            }
-        } else {
-            if (supplicantNetId != WifiConfiguration.INVALID_NETWORK_ID) {
-                // Maybe in the process of associating, skip this attempt
-                return false;
-            }
+        if (!setCurrentConfigurationKey(currentConfiguration, supplicantNetId)) {
+            return false;
         }
 
         int currentNetId = -1;
@@ -1384,48 +1499,17 @@
                 continue;
             }
 
-            if (config.autoJoinStatus >=
-                    WifiConfiguration.AUTO_JOIN_DISABLED_ON_AUTH_FAILURE) {
-                // Wait for 5 minutes before reenabling config that have known,
-                // repeated connection or DHCP failures
-                if (config.disableReason == WifiConfiguration.DISABLED_DHCP_FAILURE
-                        || config.disableReason
-                        == WifiConfiguration.DISABLED_ASSOCIATION_REJECT
-                        || config.disableReason
-                        == WifiConfiguration.DISABLED_AUTH_FAILURE) {
-                    if (config.blackListTimestamp == 0
-                            || (config.blackListTimestamp > now)) {
-                        // Sanitize the timestamp
-                        config.blackListTimestamp = now;
-                    }
-                    if ((now - config.blackListTimestamp) >
-                            mWifiConfigStore.wifiConfigBlacklistMinTimeMilli) {
-                        // Re-enable the WifiConfiguration
-                        config.status = WifiConfiguration.Status.ENABLED;
+            if (config.autoJoinStatus >= WifiConfiguration.AUTO_JOIN_DISABLED_ON_AUTH_FAILURE) {
+                updateBlackListStatus(config, now);
+                continue;
+            }
 
-                        // Reset the blacklist condition
-                        config.numConnectionFailures = 0;
-                        config.numIpConfigFailures = 0;
-                        config.numAuthFailures = 0;
-                        config.setAutoJoinStatus(WifiConfiguration.AUTO_JOIN_ENABLED);
-
-                        config.dirty = true;
-                    } else {
-                        if (VDBG) {
-                            long delay = mWifiConfigStore.wifiConfigBlacklistMinTimeMilli
-                                    - (now - config.blackListTimestamp);
-                            logDbg("attemptautoJoin " + config.configKey()
-                                    + " dont unblacklist yet, waiting for "
-                                    + delay + " ms");
-                        }
-                    }
-                }
-                // Avoid networks disabled because of AUTH failure altogether
+            if (config.userApproved == WifiConfiguration.USER_PENDING ||
+                    config.userApproved == WifiConfiguration.USER_BANNED) {
                 if (DBG) {
-                    logDbg("attemptAutoJoin skip candidate due to auto join status "
-                            + Integer.toString(config.autoJoinStatus) + " key "
-                            + config.configKey(true)
-                            + " reason " + config.disableReason);
+                    logDbg("attemptAutoJoin skip candidate due to user approval status "
+                            + WifiConfiguration.userApprovedAsString(config.userApproved) + " key "
+                            + config.configKey(true));
                 }
                 continue;
             }
@@ -1450,42 +1534,29 @@
                 }
             }
 
+            if (config.visibility == null) {
+                continue;
+            }
+
             // Try to unblacklist based on good visibility
-            if (config.visibility.rssi5 < mWifiConfigStore.thresholdUnblacklistThreshold5Soft
-                    && config.visibility.rssi24
-                    < mWifiConfigStore.thresholdUnblacklistThreshold24Soft) {
+            if (underSoftThreshold(config)) {
                 if (DBG) {
-                    logDbg("attemptAutoJoin do not unblacklist due to low visibility "
-                            + config.autoJoinStatus
-                            + " key " + config.configKey(true)
-                            + " rssi=(" + config.visibility.rssi24
-                            + "," + config.visibility.rssi5
-                            + ") num=(" + config.visibility.num24
-                            + "," + config.visibility.num5 + ")");
+                    logDbg("attemptAutoJoin do not unblacklist due to low visibility " +
+                            config.configKey() + " status=" + config.autoJoinStatus);
                 }
-            } else if (config.visibility.rssi5 < mWifiConfigStore.thresholdUnblacklistThreshold5Hard
-                    && config.visibility.rssi24
-                    < mWifiConfigStore.thresholdUnblacklistThreshold24Hard) {
+            } else if (underHardThreshold(config)) {
                 // If the network is simply temporary disabled, don't allow reconnect until
                 // RSSI becomes good enough
                 config.setAutoJoinStatus(config.autoJoinStatus - 1);
                 if (DBG) {
-                    logDbg("attemptAutoJoin good candidate seen, bumped soft -> status="
-                            + config.autoJoinStatus
-                            + " " + config.configKey(true) + " rssi=("
-                            + config.visibility.rssi24 + "," + config.visibility.rssi5
-                            + ") num=(" + config.visibility.num24
-                            + "," + config.visibility.num5 + ")");
+                    logDbg("attemptAutoJoin good candidate seen, bumped soft -> status=" +
+                            config.configKey() + " status=" + config.autoJoinStatus);
                 }
             } else {
                 config.setAutoJoinStatus(config.autoJoinStatus - 3);
                 if (DBG) {
-                    logDbg("attemptAutoJoin good candidate seen, bumped hard -> status="
-                            + config.autoJoinStatus
-                            + " " + config.configKey(true) + " rssi=("
-                            + config.visibility.rssi24 + "," + config.visibility.rssi5
-                            + ") num=(" + config.visibility.num24
-                            + "," + config.visibility.num5 + ")");
+                    logDbg("attemptAutoJoin good candidate seen, bumped hard -> status=" +
+                            config.configKey() + " status=" + config.autoJoinStatus);
                 }
             }
 
@@ -1493,12 +1564,8 @@
                     WifiConfiguration.AUTO_JOIN_TEMPORARY_DISABLED) {
                 // Network is blacklisted, skip
                 if (DBG) {
-                    logDbg("attemptAutoJoin skip blacklisted -> status="
-                            + config.autoJoinStatus
-                            + " " + config.configKey(true) + " rssi=("
-                            + config.visibility.rssi24 + "," + config.visibility.rssi5
-                            + ") num=(" + config.visibility.num24
-                            + "," + config.visibility.num5 + ")");
+                    logDbg("attemptAutoJoin skip blacklisted -> status=" +
+                            config.configKey() + " status=" + config.autoJoinStatus);
                 }
                 continue;
             }
@@ -1517,10 +1584,6 @@
                 isLastSelected = true;
             }
 
-            if (config.visibility == null) {
-                continue;
-            }
-
             if (config.lastRoamingFailure != 0
                     && currentConfiguration != null
                     && (lastSelectedConfiguration == null
@@ -1544,17 +1607,12 @@
             }
 
             int boost = config.autoJoinUseAggressiveJoinAttemptThreshold + weakRssiBailCount;
-            if ((config.visibility.rssi5 + boost)
-                        < mWifiConfigStore.thresholdInitialAutoJoinAttemptMin5RSSI
-                        && (config.visibility.rssi24 + boost)
-                        < mWifiConfigStore.thresholdInitialAutoJoinAttemptMin24RSSI) {
+            if (underThreshold(config,
+                    mWifiConfigStore.thresholdInitialAutoJoinAttemptMin24RSSI.get() - boost,
+                    mWifiConfigStore.thresholdInitialAutoJoinAttemptMin5RSSI.get() - boost)) {
+
                 if (DBG) {
-                    logDbg("attemptAutoJoin skip due to low visibility -> status="
-                            + config.autoJoinStatus
-                            + " key " + config.configKey(true) + " rssi="
-                            + config.visibility.rssi24 + ", " + config.visibility.rssi5
-                            + " num=" + config.visibility.num24
-                            + ", " + config.visibility.num5);
+                    logDbg("attemptAutoJoin skip due to low visibility " + config.configKey());
                 }
 
                 // Don't try to autojoin a network that is too far but
@@ -1572,6 +1630,7 @@
                     }
                 }
             }
+            // NOTE: If this condition is updated, update NETWORK_STATUS_UNWANTED_DISABLE_AUTOJOIN.
             if (config.numNoInternetAccessReports > 0
                     && !isLastSelected
                     && !config.validatedInternetAccess) {
@@ -1601,11 +1660,15 @@
             if (candidate == null) {
                 candidate = config;
             } else {
-                if (VDBG)  {
+                if (VDBG) {
                     logDbg("attemptAutoJoin will compare candidate  " + candidate.configKey()
                             + " with " + config.configKey());
                 }
+
                 int order = compareWifiConfigurations(candidate, config);
+                if (VDBG) {
+                    logDbg("attemptAutoJoin compareWifiConfigurations returned " + order);
+                }
 
                 // The lastSelectedConfiguration is the configuration the user has manually selected
                 // thru WifiPicker, or that a 3rd party app asked us to connect to via the
@@ -1619,7 +1682,7 @@
                     // above RSSI/scorer based selection of linked configuration (+/- 50)
                     // by reducing order by -100
                     order = order - 100;
-                    if (VDBG)   {
+                    if (VDBG) {
                         logDbg("     ...and prefers -100 " + candidate.configKey()
                                 + " over " + config.configKey()
                                 + " because it is the last selected -> "
@@ -1632,7 +1695,7 @@
                     // above RSSI/scorer based selection of linked configuration (+/- 50)
                     // by increasing order by +100
                     order = order + 100;
-                    if (VDBG)   {
+                    if (VDBG) {
                         logDbg("     ...and prefers +100 " + config.configKey()
                                 + " over " + candidate.configKey()
                                 + " because it is the last selected -> "
@@ -1660,10 +1723,11 @@
             long nowMs = System.currentTimeMillis();
             int currentScore = -10000;
             // The untrusted network with highest score
-            ScanResult untrustedCandidate = null;
+            ScanDetail untrustedCandidate = null;
             // Look for untrusted scored network only if the current candidate is bad
             if (isBadCandidate(rssi24, rssi5)) {
-                for (ScanResult result : scanResultCache.values()) {
+                for (ScanDetail scanDetail : scanResultCache.values()) {
+                    ScanResult result = scanDetail.getScanResult();
                     // We look only at untrusted networks with a valid SSID
                     // A trusted result would have been looked at thru it's Wificonfiguration
                     if (TextUtils.isEmpty(result.SSID) || !result.untrusted ||
@@ -1686,13 +1750,13 @@
                                 && score > currentScore) {
                             // Highest score: Select this candidate
                             currentScore = score;
-                            untrustedCandidate = result;
+                            untrustedCandidate = scanDetail;
                             if (VDBG) {
                                 logDbg("AutoJoinController: found untrusted candidate "
                                         + result.SSID
-                                + " RSSI=" + result.level
-                                + " freq=" + result.frequency
-                                + " score=" + score);
+                                        + " RSSI=" + result.level
+                                        + " freq=" + result.frequency
+                                        + " score=" + score);
                             }
                         }
                     }
@@ -1705,6 +1769,7 @@
                         mWifiConfigStore.wifiConfigurationFromScanResult(untrustedCandidate);
                 candidate.allowedKeyManagement.set(KeyMgmt.NONE);
                 candidate.ephemeral = true;
+                candidate.dirty = true;
             }
         }
 
@@ -1716,7 +1781,7 @@
                 && currentConfiguration == null
                 && didBailDueToWeakRssi
                 && (mWifiConfigStore.lastUnwantedNetworkDisconnectTimestamp == 0
-                    || lastUnwanted > (1000 * 60 * 60 * 24 * 7))
+                || lastUnwanted > (1000 * 60 * 60 * 24 * 7))
                 ) {
             // We are bailing out of autojoin although we are seeing a weak configuration, and
             // - we didn't find another valid candidate
@@ -1751,7 +1816,7 @@
                     + candidate.configKey()
                     + current
                     + " linked=" + (currentConfiguration != null
-                            && currentConfiguration.isLinked(candidate))
+                    && currentConfiguration.isLinked(candidate))
                     + " : delta="
                     + Integer.toString(networkDelta) + " "
                     + doSwitch);
@@ -1762,7 +1827,7 @@
          * if user is currently streaming voice traffic,
          * then we should not be allowed to switch regardless of the delta
          */
-        if (mWifiStateMachine.shouldSwitchNetwork(networkDelta)) {
+        if (mWifiStateMachine.shouldSwitchNetwork(networkDelta)) {      // !!! JNo: Here!
             if (mStaStaSupported) {
                 logDbg("mStaStaSupported --> error do nothing now ");
             } else {
@@ -1818,39 +1883,16 @@
                     }
                 }
                 mWifiStateMachine.sendMessage(WifiStateMachine.CMD_AUTO_CONNECT,
-                            candidate.networkId, networkSwitchType, candidate);
+                        candidate.networkId, networkSwitchType, candidate);
                 found = true;
             }
         }
 
-        if (networkSwitchType == AUTO_JOIN_IDLE) {
+        if (networkSwitchType == AUTO_JOIN_IDLE && !mWifiConfigStore.enableHalBasedPno.get()) {
             String currentBSSID = mWifiStateMachine.getCurrentBSSID();
             // Attempt same WifiConfiguration roaming
             ScanResult roamCandidate =
                     attemptRoam(null, currentConfiguration, mScanResultAutoJoinAge, currentBSSID);
-            /**
-             *  TODO: (post L initial release)
-             *  consider handling linked configurations roaming (i.e. extended Roaming)
-             *  thru the attemptRoam function which makes use of the RSSI roaming threshold.
-             *  At the moment, extended roaming is only handled thru the attemptAutoJoin()
-             *  function which compare configurations.
-             *
-             *  The advantage of making use of attemptRoam function is that this function
-             *  will looks at all the BSSID of each configurations, instead of only looking
-             *  at WifiConfiguration.visibility which keeps trackonly of the RSSI/band of the
-             *  two highest BSSIDs.
-             */
-            // Attempt linked WifiConfiguration roaming
-            /* if (currentConfiguration != null
-                    && currentConfiguration.linkedConfigurations != null) {
-                for (String key : currentConfiguration.linkedConfigurations.keySet()) {
-                    WifiConfiguration link = mWifiConfigStore.getWifiConfiguration(key);
-                    if (link != null) {
-                        roamCandidate = attemptRoam(roamCandidate, link, mScanResultAutoJoinAge,
-                                currentBSSID);
-                    }
-                }
-            }*/
             if (roamCandidate != null && currentBSSID != null
                     && currentBSSID.equals(roamCandidate.BSSID)) {
                 roamCandidate = null;
@@ -1867,12 +1909,87 @@
                 mWifiConnectionStatistics.numAutoRoamAttempt++;
 
                 mWifiStateMachine.sendMessage(WifiStateMachine.CMD_AUTO_ROAM,
-                            currentConfiguration.networkId, 1, roamCandidate);
+                        currentConfiguration.networkId, 1, roamCandidate);
                 found = true;
             }
         }
         if (VDBG) logDbg("Done attemptAutoJoin status=" + Integer.toString(networkSwitchType));
         return found;
     }
+
+    private void logDenial(String reason, WifiConfiguration config) {
+        if (!DBG) {
+            return;
+        }
+        logDbg(reason + config.toString());
+    }
+
+    WifiConfiguration getWifiConfiguration(WifiNative.WifiPnoNetwork network) {
+        if (network.configKey != null) {
+            return mWifiConfigStore.getWifiConfiguration(network.configKey);
+        }
+        return null;
+    }
+
+    ArrayList<WifiNative.WifiPnoNetwork> getPnoList(WifiConfiguration current) {
+        int size = -1;
+        ArrayList<WifiNative.WifiPnoNetwork> list = new ArrayList<WifiNative.WifiPnoNetwork>();
+
+        if (mWifiConfigStore.mCachedPnoList != null) {
+            size = mWifiConfigStore.mCachedPnoList.size();
+        }
+
+        if (DBG) {
+            String s = "";
+            if (current != null) {
+                s = " for: " + current.configKey();
+            }
+            Log.e(TAG, " get Pno List total size:" + size + s);
+        }
+        if (current != null) {
+            String configKey = current.configKey();
+            /**
+             * If we are currently associated to a WifiConfiguration then include
+             * only those networks that have a higher priority
+             */
+            for (WifiNative.WifiPnoNetwork network : mWifiConfigStore.mCachedPnoList) {
+                WifiConfiguration config = getWifiConfiguration(network);
+                if (config == null) {
+                    continue;
+                }
+                if (config.autoJoinStatus
+                        >= WifiConfiguration.AUTO_JOIN_DISABLED_NO_CREDENTIALS) {
+                     continue;
+                }
+
+                if (!configKey.equals(network.configKey)) {
+                    int choice = getConnectChoice(config, current, true);
+                    if (choice > 0) {
+                        // config is of higher priority
+                        if (DBG) {
+                            Log.e(TAG, " Pno List adding:" + network.configKey
+                                    + " choice " + choice);
+                        }
+                        list.add(network);
+                        network.rssi_threshold = mWifiConfigStore.thresholdGoodRssi24.get();
+                    }
+                }
+            }
+        } else {
+            for (WifiNative.WifiPnoNetwork network : mWifiConfigStore.mCachedPnoList) {
+                WifiConfiguration config = getWifiConfiguration(network);
+                if (config == null) {
+                    continue;
+                }
+                if (config.autoJoinStatus
+                        >= WifiConfiguration.AUTO_JOIN_DISABLED_NO_CREDENTIALS) {
+                    continue;
+                }
+                list.add(network);
+                network.rssi_threshold = mWifiConfigStore.thresholdGoodRssi24.get();
+            }
+        }
+        return list;
+    }
 }
 
diff --git a/service/java/com/android/server/wifi/WifiConfigStore.java b/service/java/com/android/server/wifi/WifiConfigStore.java
index 94752d3..dd47b08 100644
--- a/service/java/com/android/server/wifi/WifiConfigStore.java
+++ b/service/java/com/android/server/wifi/WifiConfigStore.java
@@ -16,32 +16,34 @@
 
 package com.android.server.wifi;
 
+import android.app.AppGlobals;
+import android.app.admin.DeviceAdminInfo;
+import android.app.admin.DevicePolicyManagerInternal;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 import android.net.IpConfiguration;
 import android.net.IpConfiguration.IpAssignment;
 import android.net.IpConfiguration.ProxySettings;
-import android.net.LinkAddress;
 import android.net.NetworkInfo.DetailedState;
 import android.net.ProxyInfo;
-import android.net.RouteInfo;
 import android.net.StaticIpConfiguration;
+import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiConfiguration.KeyMgmt;
 import android.net.wifi.WifiConfiguration.Status;
-import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID;
-
 import android.net.wifi.WifiEnterpriseConfig;
+import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiSsid;
 import android.net.wifi.WpsInfo;
 import android.net.wifi.WpsResult;
-import android.net.wifi.ScanResult;
-import android.net.wifi.WifiInfo;
-
 import android.os.Environment;
 import android.os.FileObserver;
 import android.os.Process;
+import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -53,12 +55,24 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import com.android.server.LocalServices;
+import com.android.internal.R;
 import com.android.server.net.DelayedDiskWrite;
 import com.android.server.net.IpConfigStore;
-import com.android.internal.R;
+import com.android.server.wifi.anqp.ANQPElement;
+import com.android.server.wifi.anqp.Constants;
+import com.android.server.wifi.hotspot2.ANQPData;
+import com.android.server.wifi.hotspot2.AnqpCache;
+import com.android.server.wifi.hotspot2.NetworkDetail;
+import com.android.server.wifi.hotspot2.PasspointMatch;
+import com.android.server.wifi.hotspot2.SupplicantBridge;
+import com.android.server.wifi.hotspot2.Utils;
+import com.android.server.wifi.hotspot2.omadm.MOManager;
+import com.android.server.wifi.hotspot2.pps.Credential;
+import com.android.server.wifi.hotspot2.pps.HomeSP;
 
-import java.io.BufferedReader;
 import java.io.BufferedInputStream;
+import java.io.BufferedReader;
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
 import java.io.EOFException;
@@ -69,19 +83,31 @@
 import java.io.FileReader;
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.math.BigInteger;
-import java.net.InetAddress;
-import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
 import java.security.PrivateKey;
 import java.security.cert.Certificate;
 import java.security.cert.CertificateException;
-import java.text.SimpleDateFormat;
-import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
-import java.util.*;
-import java.util.zip.Checksum;
 import java.util.zip.CRC32;
+import java.util.zip.Checksum;
+
+import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID;
+
 
 /**
  * This class provides the API to manage configured
@@ -131,16 +157,17 @@
 public class WifiConfigStore extends IpConfigStore {
 
     private Context mContext;
-    private static final String TAG = "WifiConfigStore";
+    public static final String TAG = "WifiConfigStore";
     private static final boolean DBG = true;
     private static boolean VDBG = false;
     private static boolean VVDBG = false;
 
     private static final String SUPPLICANT_CONFIG_FILE = "/data/misc/wifi/wpa_supplicant.conf";
+    private static final String SUPPLICANT_CONFIG_FILE_BACKUP = SUPPLICANT_CONFIG_FILE + ".tmp";
+    private static final String PPS_FILE = "/data/misc/wifi/PerProviderSubscription.conf";
 
     /* configured networks with network id as the key */
-    private HashMap<Integer, WifiConfiguration> mConfiguredNetworks =
-            new HashMap<Integer, WifiConfiguration>();
+    private final ConfigurationMap mConfiguredNetworks = new ConfigurationMap();
 
     /* A network id is a unique identifier for a network configured in the
      * supplicant. Network ids are generated when the supplicant reads
@@ -149,8 +176,9 @@
      * that is generated from SSID and security type of the network. A mapping
      * from the generated unique id to network id of the network is needed to
      * map supplicant config to IP configuration. */
-    private HashMap<Integer, Integer> mNetworkIds =
-            new HashMap<Integer, Integer>();
+
+    /* Stores a map of NetworkId to ScanCache */
+    private HashMap<Integer, ScanDetailCache> mScanDetailCaches;
 
     /**
      * Framework keeps a list of (the CRC32 hashes of) all SSIDs that where deleted by user,
@@ -180,176 +208,159 @@
             "/misc/wifi/autojoinconfig.txt";
 
     /* Network History Keys */
-    private static final String SSID_KEY = "SSID:  ";
-    private static final String CONFIG_KEY = "CONFIG:  ";
-    private static final String CHOICE_KEY = "CHOICE:  ";
-    private static final String LINK_KEY = "LINK:  ";
-    private static final String BSSID_KEY = "BSSID:  ";
-    private static final String BSSID_KEY_END = "/BSSID:  ";
-    private static final String RSSI_KEY = "RSSI:  ";
-    private static final String FREQ_KEY = "FREQ:  ";
-    private static final String DATE_KEY = "DATE:  ";
-    private static final String MILLI_KEY = "MILLI:  ";
-    private static final String BLACKLIST_MILLI_KEY = "BLACKLIST_MILLI:  ";
-    private static final String NETWORK_ID_KEY = "ID:  ";
-    private static final String PRIORITY_KEY = "PRIORITY:  ";
-    private static final String DEFAULT_GW_KEY = "DEFAULT_GW:  ";
-    private static final String AUTH_KEY = "AUTH:  ";
-    private static final String SEPARATOR_KEY = "\n";
-    private static final String STATUS_KEY = "AUTO_JOIN_STATUS:  ";
-    private static final String BSSID_STATUS_KEY = "BSSID_STATUS:  ";
-    private static final String SELF_ADDED_KEY = "SELF_ADDED:  ";
-    private static final String FAILURE_KEY = "FAILURE:  ";
-    private static final String DID_SELF_ADD_KEY = "DID_SELF_ADD:  ";
-    private static final String PEER_CONFIGURATION_KEY = "PEER_CONFIGURATION:  ";
-    private static final String CREATOR_UID_KEY = "CREATOR_UID_KEY:  ";
-    private static final String CONNECT_UID_KEY = "CONNECT_UID_KEY:  ";
-    private static final String UPDATE_UID_KEY = "UPDATE_UID:  ";
-    private static final String SUPPLICANT_STATUS_KEY = "SUP_STATUS:  ";
-    private static final String SUPPLICANT_DISABLE_REASON_KEY = "SUP_DIS_REASON:  ";
-    private static final String FQDN_KEY = "FQDN:  ";
-    private static final String NUM_CONNECTION_FAILURES_KEY = "CONNECT_FAILURES:  ";
-    private static final String NUM_IP_CONFIG_FAILURES_KEY = "IP_CONFIG_FAILURES:  ";
-    private static final String NUM_AUTH_FAILURES_KEY = "AUTH_FAILURES:  ";
-    private static final String SCORER_OVERRIDE_KEY = "SCORER_OVERRIDE:  ";
-    private static final String SCORER_OVERRIDE_AND_SWITCH_KEY = "SCORER_OVERRIDE_AND_SWITCH:  ";
-    private static final String VALIDATED_INTERNET_ACCESS_KEY = "VALIDATED_INTERNET_ACCESS:  ";
-    private static final String NO_INTERNET_ACCESS_REPORTS_KEY = "NO_INTERNET_ACCESS_REPORTS :   ";
-    private static final String EPHEMERAL_KEY = "EPHEMERAL:   ";
-    private static final String NUM_ASSOCIATION_KEY = "NUM_ASSOCIATION:  ";
-    private static final String DELETED_CRC32_KEY = "DELETED_CRC32:  ";
-    private static final String DELETED_EPHEMERAL_KEY = "DELETED_EPHEMERAL:  ";
+    private static final String SSID_KEY = "SSID";
+    private static final String CONFIG_KEY = "CONFIG";
+    private static final String CHOICE_KEY = "CHOICE";
+    private static final String LINK_KEY = "LINK";
+    private static final String BSSID_KEY = "BSSID";
+    private static final String BSSID_KEY_END = "/BSSID";
+    private static final String RSSI_KEY = "RSSI";
+    private static final String FREQ_KEY = "FREQ";
+    private static final String DATE_KEY = "DATE";
+    private static final String MILLI_KEY = "MILLI";
+    private static final String BLACKLIST_MILLI_KEY = "BLACKLIST_MILLI";
+    private static final String NETWORK_ID_KEY = "ID";
+    private static final String PRIORITY_KEY = "PRIORITY";
+    private static final String DEFAULT_GW_KEY = "DEFAULT_GW";
+    private static final String AUTH_KEY = "AUTH";
+    private static final String STATUS_KEY = "AUTO_JOIN_STATUS";
+    private static final String BSSID_STATUS_KEY = "BSSID_STATUS";
+    private static final String SELF_ADDED_KEY = "SELF_ADDED";
+    private static final String FAILURE_KEY = "FAILURE";
+    private static final String DID_SELF_ADD_KEY = "DID_SELF_ADD";
+    private static final String PEER_CONFIGURATION_KEY = "PEER_CONFIGURATION";
+    private static final String CREATOR_UID_KEY = "CREATOR_UID_KEY";
+    private static final String CONNECT_UID_KEY = "CONNECT_UID_KEY";
+    private static final String UPDATE_UID_KEY = "UPDATE_UID";
+    private static final String SUPPLICANT_STATUS_KEY = "SUP_STATUS";
+    private static final String SUPPLICANT_DISABLE_REASON_KEY = "SUP_DIS_REASON";
+    private static final String FQDN_KEY = "FQDN";
+    private static final String NUM_CONNECTION_FAILURES_KEY = "CONNECT_FAILURES";
+    private static final String NUM_IP_CONFIG_FAILURES_KEY = "IP_CONFIG_FAILURES";
+    private static final String NUM_AUTH_FAILURES_KEY = "AUTH_FAILURES";
+    private static final String SCORER_OVERRIDE_KEY = "SCORER_OVERRIDE";
+    private static final String SCORER_OVERRIDE_AND_SWITCH_KEY = "SCORER_OVERRIDE_AND_SWITCH";
+    private static final String VALIDATED_INTERNET_ACCESS_KEY = "VALIDATED_INTERNET_ACCESS";
+    private static final String NO_INTERNET_ACCESS_REPORTS_KEY = "NO_INTERNET_ACCESS_REPORTS";
+    private static final String EPHEMERAL_KEY = "EPHEMERAL";
+    private static final String NUM_ASSOCIATION_KEY = "NUM_ASSOCIATION";
+    private static final String DELETED_CRC32_KEY = "DELETED_CRC32";
+    private static final String DELETED_EPHEMERAL_KEY = "DELETED_EPHEMERAL";
+    private static final String JOIN_ATTEMPT_BOOST_KEY = "JOIN_ATTEMPT_BOOST";
+    private static final String CREATOR_NAME_KEY = "CREATOR_NAME";
+    private static final String UPDATE_NAME_KEY = "UPDATE_NAME";
+    private static final String USER_APPROVED_KEY = "USER_APPROVED";
+    private static final String CREATION_TIME_KEY = "CREATION_TIME";
+    private static final String UPDATE_TIME_KEY = "UPDATE_TIME";
 
-    private static final String JOIN_ATTEMPT_BOOST_KEY = "JOIN_ATTEMPT_BOOST:  ";
+    private static final String SEPARATOR = ":  ";
+    private static final String NL = "\n";
+
     private static final String THRESHOLD_INITIAL_AUTO_JOIN_ATTEMPT_RSSI_MIN_5G_KEY
-            = "THRESHOLD_INITIAL_AUTO_JOIN_ATTEMPT_RSSI_MIN_5G:  ";
+            = "THRESHOLD_INITIAL_AUTO_JOIN_ATTEMPT_RSSI_MIN_5G";
     private static final String THRESHOLD_INITIAL_AUTO_JOIN_ATTEMPT_RSSI_MIN_24G_KEY
-            = "THRESHOLD_INITIAL_AUTO_JOIN_ATTEMPT_RSSI_MIN_24G:  ";
+            = "THRESHOLD_INITIAL_AUTO_JOIN_ATTEMPT_RSSI_MIN_24G";
     private static final String THRESHOLD_UNBLACKLIST_HARD_5G_KEY
-            = "THRESHOLD_UNBLACKLIST_HARD_5G:  ";
+            = "THRESHOLD_UNBLACKLIST_HARD_5G";
     private static final String THRESHOLD_UNBLACKLIST_SOFT_5G_KEY
-            = "THRESHOLD_UNBLACKLIST_SOFT_5G:  ";
+            = "THRESHOLD_UNBLACKLIST_SOFT_5G";
     private static final String THRESHOLD_UNBLACKLIST_HARD_24G_KEY
-            = "THRESHOLD_UNBLACKLIST_HARD_24G:  ";
+            = "THRESHOLD_UNBLACKLIST_HARD_24G";
     private static final String THRESHOLD_UNBLACKLIST_SOFT_24G_KEY
-            = "THRESHOLD_UNBLACKLIST_SOFT_24G:  ";
+            = "THRESHOLD_UNBLACKLIST_SOFT_24G";
     private static final String THRESHOLD_GOOD_RSSI_5_KEY
-            = "THRESHOLD_GOOD_RSSI_5:  ";
+            = "THRESHOLD_GOOD_RSSI_5";
     private static final String THRESHOLD_LOW_RSSI_5_KEY
-            = "THRESHOLD_LOW_RSSI_5:  ";
+            = "THRESHOLD_LOW_RSSI_5";
     private static final String THRESHOLD_BAD_RSSI_5_KEY
-            = "THRESHOLD_BAD_RSSI_5:  ";
+            = "THRESHOLD_BAD_RSSI_5";
     private static final String THRESHOLD_GOOD_RSSI_24_KEY
-            = "THRESHOLD_GOOD_RSSI_24:  ";
+            = "THRESHOLD_GOOD_RSSI_24";
     private static final String THRESHOLD_LOW_RSSI_24_KEY
-            = "THRESHOLD_LOW_RSSI_24:  ";
+            = "THRESHOLD_LOW_RSSI_24";
     private static final String THRESHOLD_BAD_RSSI_24_KEY
-            = "THRESHOLD_BAD_RSSI_24:  ";
+            = "THRESHOLD_BAD_RSSI_24";
 
     private static final String THRESHOLD_MAX_TX_PACKETS_FOR_NETWORK_SWITCHING_KEY
-            = "THRESHOLD_MAX_TX_PACKETS_FOR_NETWORK_SWITCHING:   ";
+            = "THRESHOLD_MAX_TX_PACKETS_FOR_NETWORK_SWITCHING";
     private static final String THRESHOLD_MAX_RX_PACKETS_FOR_NETWORK_SWITCHING_KEY
-            = "THRESHOLD_MAX_RX_PACKETS_FOR_NETWORK_SWITCHING:   ";
+            = "THRESHOLD_MAX_RX_PACKETS_FOR_NETWORK_SWITCHING";
 
     private static final String THRESHOLD_MAX_TX_PACKETS_FOR_FULL_SCANS_KEY
-            = "THRESHOLD_MAX_TX_PACKETS_FOR_FULL_SCANS:   ";
+            = "THRESHOLD_MAX_TX_PACKETS_FOR_FULL_SCANS";
     private static final String THRESHOLD_MAX_RX_PACKETS_FOR_FULL_SCANS_KEY
-            = "THRESHOLD_MAX_RX_PACKETS_FOR_FULL_SCANS:   ";
+            = "THRESHOLD_MAX_RX_PACKETS_FOR_FULL_SCANS";
 
     private static final String THRESHOLD_MAX_TX_PACKETS_FOR_PARTIAL_SCANS_KEY
-            = "THRESHOLD_MAX_TX_PACKETS_FOR_PARTIAL_SCANS:   ";
+            = "THRESHOLD_MAX_TX_PACKETS_FOR_PARTIAL_SCANS";
     private static final String THRESHOLD_MAX_RX_PACKETS_FOR_PARTIAL_SCANS_KEY
-            = "THRESHOLD_MAX_RX_PACKETS_FOR_PARTIAL_SCANS:   ";
+            = "THRESHOLD_MAX_RX_PACKETS_FOR_PARTIAL_SCANS";
 
     private static final String MAX_NUM_ACTIVE_CHANNELS_FOR_PARTIAL_SCANS_KEY
-            = "MAX_NUM_ACTIVE_CHANNELS_FOR_PARTIAL_SCANS:   ";
+            = "MAX_NUM_ACTIVE_CHANNELS_FOR_PARTIAL_SCANS";
     private static final String MAX_NUM_PASSIVE_CHANNELS_FOR_PARTIAL_SCANS_KEY
-            = "MAX_NUM_PASSIVE_CHANNELS_FOR_PARTIAL_SCANS:   ";
+            = "MAX_NUM_PASSIVE_CHANNELS_FOR_PARTIAL_SCANS";
 
     private static final String A_BAND_PREFERENCE_RSSI_THRESHOLD_LOW_KEY =
-            "A_BAND_PREFERENCE_RSSI_THRESHOLD_LOW:   ";
+            "A_BAND_PREFERENCE_RSSI_THRESHOLD_LOW";
     private static final String A_BAND_PREFERENCE_RSSI_THRESHOLD_KEY =
-            "A_BAND_PREFERENCE_RSSI_THRESHOLD:   ";
+            "A_BAND_PREFERENCE_RSSI_THRESHOLD";
     private static final String G_BAND_PREFERENCE_RSSI_THRESHOLD_KEY =
-            "G_BAND_PREFERENCE_RSSI_THRESHOLD:   ";
+            "G_BAND_PREFERENCE_RSSI_THRESHOLD";
 
     private static final String ENABLE_AUTOJOIN_WHILE_ASSOCIATED_KEY
             = "ENABLE_AUTOJOIN_WHILE_ASSOCIATED:   ";
 
     private static final String ASSOCIATED_PARTIAL_SCAN_PERIOD_KEY
-            = "ASSOCIATED_PARTIAL_SCAN_PERIOD:   ";
+            = "ASSOCIATED_PARTIAL_SCAN_PERIOD";
     private static final String ASSOCIATED_FULL_SCAN_BACKOFF_KEY
-            = "ASSOCIATED_FULL_SCAN_BACKOFF_PERIOD:   ";
+            = "ASSOCIATED_FULL_SCAN_BACKOFF_PERIOD";
     private static final String ALWAYS_ENABLE_SCAN_WHILE_ASSOCIATED_KEY
-            = "ALWAYS_ENABLE_SCAN_WHILE_ASSOCIATED:   ";
+            = "ALWAYS_ENABLE_SCAN_WHILE_ASSOCIATED";
     private static final String ONLY_LINK_SAME_CREDENTIAL_CONFIGURATIONS_KEY
-            = "ONLY_LINK_SAME_CREDENTIAL_CONFIGURATIONS:   ";
+            = "ONLY_LINK_SAME_CREDENTIAL_CONFIGURATIONS";
 
     private static final String ENABLE_FULL_BAND_SCAN_WHEN_ASSOCIATED_KEY
-            = "ENABLE_FULL_BAND_SCAN_WHEN_ASSOCIATED:   ";
+            = "ENABLE_FULL_BAND_SCAN_WHEN_ASSOCIATED";
+
+    private static final String ENABLE_HAL_BASED_PNO
+            = "ENABLE_HAL_BASED_PNO";
 
     // The three below configurations are mainly for power stats and CPU usage tracking
     // allowing to incrementally disable framework features
-    private static final String ENABLE_AUTO_JOIN_SCAN_WHILE_ASSOCIATED_KEY
-            = "ENABLE_AUTO_JOIN_SCAN_WHILE_ASSOCIATED:   ";
     private static final String ENABLE_AUTO_JOIN_WHILE_ASSOCIATED_KEY
-            = "ENABLE_AUTO_JOIN_WHILE_ASSOCIATED:   ";
+            = "ENABLE_AUTO_JOIN_WHILE_ASSOCIATED";
     private static final String ENABLE_CHIP_WAKE_UP_WHILE_ASSOCIATED_KEY
-            = "ENABLE_CHIP_WAKE_UP_WHILE_ASSOCIATED:   ";
+            = "ENABLE_CHIP_WAKE_UP_WHILE_ASSOCIATED";
     private static final String ENABLE_RSSI_POLL_WHILE_ASSOCIATED_KEY
-            = "ENABLE_RSSI_POLL_WHILE_ASSOCIATED_KEY:   ";
+            = "ENABLE_RSSI_POLL_WHILE_ASSOCIATED_KEY";
+
+    public static final String idStringVarName = "id_str";
 
     // The Wifi verbose log is provided as a way to persist the verbose logging settings
     // for testing purpose.
     // It is not intended for normal use.
     private static final String WIFI_VERBOSE_LOGS_KEY
-            = "WIFI_VERBOSE_LOGS:   ";
+            = "WIFI_VERBOSE_LOGS";
 
     // As we keep deleted PSK WifiConfiguration for a while, the PSK of
     // those deleted WifiConfiguration is set to this random unused PSK
     private static final String DELETED_CONFIG_PSK = "Mjkd86jEMGn79KhKll298Uu7-deleted";
 
-    public boolean enableAutoJoinScanWhenAssociated = true;
-    public boolean enableAutoJoinWhenAssociated = true;
-    public boolean enableChipWakeUpWhenAssociated = true;
-    public boolean enableRssiPollWhenAssociated = true;
-
-    public int maxTxPacketForNetworkSwitching = 40;
-    public int maxRxPacketForNetworkSwitching = 80;
-
     public int maxTxPacketForFullScans = 8;
     public int maxRxPacketForFullScans = 16;
 
     public int maxTxPacketForPartialScans = 40;
     public int maxRxPacketForPartialScans = 80;
 
-    public boolean enableFullBandScanWhenAssociated = true;
-
-    public int thresholdInitialAutoJoinAttemptMin5RSSI
-            = WifiConfiguration.INITIAL_AUTO_JOIN_ATTEMPT_MIN_5;
-    public int thresholdInitialAutoJoinAttemptMin24RSSI
-            = WifiConfiguration.INITIAL_AUTO_JOIN_ATTEMPT_MIN_24;
-
-    public int thresholdBadRssi5 = WifiConfiguration.BAD_RSSI_5;
-    public int thresholdLowRssi5 = WifiConfiguration.LOW_RSSI_5;
-    public int thresholdGoodRssi5 = WifiConfiguration.GOOD_RSSI_5;
-    public int thresholdBadRssi24 = WifiConfiguration.BAD_RSSI_24;
-    public int thresholdLowRssi24 = WifiConfiguration.LOW_RSSI_24;
-    public int thresholdGoodRssi24 = WifiConfiguration.GOOD_RSSI_24;
-
-    public int associatedFullScanBackoff = 12; // Will be divided by 8 by WifiStateMachine
     public int associatedFullScanMaxIntervalMilli = 300000;
 
-    public int associatedPartialScanPeriodMilli;
-
     // Sane value for roam blacklisting (not switching to a network if already associated)
     // 2 days
     public int networkSwitchingBlackListPeriodMilli = 2 * 24 * 60 * 60 * 1000;
 
     public int bandPreferenceBoostFactor5 = 5; // Boost by 5 dB per dB above threshold
     public int bandPreferencePenaltyFactor5 = 2; // Penalize by 2 dB per dB below threshold
-    public int bandPreferencePenaltyThreshold5 = WifiConfiguration.G_BAND_PREFERENCE_RSSI_THRESHOLD;
-    public int bandPreferenceBoostThreshold5 = WifiConfiguration.A_BAND_PREFERENCE_RSSI_THRESHOLD;
 
     public int badLinkSpeed24 = 6;
     public int badLinkSpeed5 = 12;
@@ -367,22 +378,8 @@
     public int associatedHysteresisHigh = +14;
     public int associatedHysteresisLow = +8;
 
-    public int thresholdUnblacklistThreshold5Hard
-            = WifiConfiguration.UNBLACKLIST_THRESHOLD_5_HARD;
-    public int thresholdUnblacklistThreshold5Soft
-            = WifiConfiguration.UNBLACKLIST_THRESHOLD_5_SOFT;
-    public int thresholdUnblacklistThreshold24Hard
-            = WifiConfiguration.UNBLACKLIST_THRESHOLD_24_HARD;
-    public int thresholdUnblacklistThreshold24Soft
-            = WifiConfiguration.UNBLACKLIST_THRESHOLD_24_SOFT;
-    public int enableVerboseLogging = 0;
     boolean showNetworks = true; // TODO set this back to false, used for debugging 17516271
 
-    public int alwaysEnableScansWhileAssociated = 0;
-
-    public int maxNumActiveChannelsForPartialScans = 6;
-    public int maxNumPassiveChannelsForPartialScans = 2;
-
     public boolean roamOnAny = false;
     public boolean onlyLinkSameCredentialConfigurations = true;
 
@@ -395,6 +392,51 @@
 
     public static final int maxNumScanCacheEntries = 128;
 
+    public final AtomicBoolean enableHalBasedPno = new AtomicBoolean(false);
+    public final AtomicBoolean enableSsidWhitelist = new AtomicBoolean(false);
+    public final AtomicBoolean enableAutoJoinWhenAssociated = new AtomicBoolean(true);
+    public final AtomicBoolean enableFullBandScanWhenAssociated = new AtomicBoolean(true);
+    public final AtomicBoolean enableChipWakeUpWhenAssociated = new AtomicBoolean(true);
+    public final AtomicBoolean enableRssiPollWhenAssociated = new AtomicBoolean(true);
+    public final AtomicInteger thresholdInitialAutoJoinAttemptMin5RSSI =
+            new AtomicInteger(WifiConfiguration.INITIAL_AUTO_JOIN_ATTEMPT_MIN_5);
+    public final AtomicInteger thresholdInitialAutoJoinAttemptMin24RSSI =
+            new AtomicInteger(WifiConfiguration.INITIAL_AUTO_JOIN_ATTEMPT_MIN_24);
+    public final AtomicInteger thresholdUnblacklistThreshold5Hard
+            = new AtomicInteger(WifiConfiguration.UNBLACKLIST_THRESHOLD_5_HARD);
+    public final AtomicInteger thresholdUnblacklistThreshold5Soft
+            = new AtomicInteger(WifiConfiguration.UNBLACKLIST_THRESHOLD_5_SOFT);
+    public final AtomicInteger thresholdUnblacklistThreshold24Hard
+            = new AtomicInteger(WifiConfiguration.UNBLACKLIST_THRESHOLD_24_HARD);
+    public final AtomicInteger thresholdUnblacklistThreshold24Soft
+            = new AtomicInteger(WifiConfiguration.UNBLACKLIST_THRESHOLD_24_SOFT);
+    public final AtomicInteger thresholdGoodRssi5 =
+            new AtomicInteger(WifiConfiguration.GOOD_RSSI_5);
+    public final AtomicInteger thresholdLowRssi5 = new AtomicInteger(WifiConfiguration.LOW_RSSI_5);
+    public final AtomicInteger thresholdBadRssi5 = new AtomicInteger(WifiConfiguration.BAD_RSSI_5);
+    public final AtomicInteger thresholdGoodRssi24 =
+            new AtomicInteger(WifiConfiguration.GOOD_RSSI_24);
+    public final AtomicInteger thresholdLowRssi24 = new AtomicInteger(WifiConfiguration.LOW_RSSI_24);
+    public final AtomicInteger thresholdBadRssi24 = new AtomicInteger(WifiConfiguration.BAD_RSSI_24);
+    public final AtomicInteger maxTxPacketForNetworkSwitching = new AtomicInteger(40);
+    public final AtomicInteger maxRxPacketForNetworkSwitching = new AtomicInteger(80);
+    public final AtomicInteger enableVerboseLogging = new AtomicInteger(0);
+    public final AtomicInteger bandPreferenceBoostThreshold5 =
+            new AtomicInteger(WifiConfiguration.A_BAND_PREFERENCE_RSSI_THRESHOLD);
+    public final AtomicInteger associatedFullScanBackoff =
+            new AtomicInteger(12); // Will be divided by 8 by WifiStateMachine
+    public final AtomicInteger bandPreferencePenaltyThreshold5 =
+            new AtomicInteger(WifiConfiguration.G_BAND_PREFERENCE_RSSI_THRESHOLD);
+    public final AtomicInteger alwaysEnableScansWhileAssociated = new AtomicInteger(0);
+    public final AtomicInteger maxNumPassiveChannelsForPartialScans = new AtomicInteger(2);
+    public final AtomicInteger maxNumActiveChannelsForPartialScans = new AtomicInteger(6);
+    public final AtomicInteger wifiDisconnectedShortScanIntervalMilli = new AtomicInteger(15000);
+    public final AtomicInteger wifiDisconnectedLongScanIntervalMilli = new AtomicInteger(120000);
+    public final AtomicInteger wifiAssociatedShortScanIntervalMilli = new AtomicInteger(20000);
+    public final AtomicInteger wifiAssociatedLongScanIntervalMilli = new AtomicInteger(180000);
+
+    private static final Map<String, Object> sKeyMap = new HashMap<>();
+
     /**
      * Regex pattern for extracting a connect choice.
      * Matches a strings like the following:
@@ -426,7 +468,9 @@
             WifiEnterpriseConfig.PASSWORD_KEY, WifiEnterpriseConfig.CLIENT_CERT_KEY,
             WifiEnterpriseConfig.CA_CERT_KEY, WifiEnterpriseConfig.SUBJECT_MATCH_KEY,
             WifiEnterpriseConfig.ENGINE_KEY, WifiEnterpriseConfig.ENGINE_ID_KEY,
-            WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY };
+            WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY, WifiEnterpriseConfig.ALTSUBJECT_MATCH_KEY,
+            WifiEnterpriseConfig.DOM_SUFFIX_MATCH_KEY
+    };
 
 
     /**
@@ -460,9 +504,70 @@
      */
     private String lastSelectedConfiguration = null;
 
-    WifiConfigStore(Context c, WifiNative wn) {
+    /**
+     * Cached PNO list, it is updated when WifiConfiguration changes due to user input.
+     */
+    ArrayList<WifiNative.WifiPnoNetwork> mCachedPnoList
+            = new ArrayList<WifiNative.WifiPnoNetwork>();
+
+    /*
+     * BSSID blacklist, i.e. list of BSSID we want to avoid
+     */
+    HashSet<String> mBssidBlacklist = new HashSet<String>();
+
+    /*
+     * Lost config list, whenever we read a config from networkHistory.txt that was not in
+     * wpa_supplicant.conf
+     */
+    HashSet<String> mLostConfigsDbg = new HashSet<String>();
+
+    private final AnqpCache mAnqpCache;
+    private final SupplicantBridge mSupplicantBridge;
+    private final MOManager mMOManager;
+    private final SIMAccessor mSIMAccessor;
+
+    private WifiStateMachine mWifiStateMachine;
+
+    WifiConfigStore(Context c,  WifiStateMachine w, WifiNative wn) {
         mContext = c;
         mWifiNative = wn;
+        mWifiStateMachine = w;
+
+        // A map for value setting in readAutoJoinConfig() - replacing the replicated code.
+        sKeyMap.put(ENABLE_AUTO_JOIN_WHILE_ASSOCIATED_KEY, enableAutoJoinWhenAssociated);
+        sKeyMap.put(ENABLE_FULL_BAND_SCAN_WHEN_ASSOCIATED_KEY, enableFullBandScanWhenAssociated);
+        sKeyMap.put(ENABLE_CHIP_WAKE_UP_WHILE_ASSOCIATED_KEY, enableChipWakeUpWhenAssociated);
+        sKeyMap.put(ENABLE_RSSI_POLL_WHILE_ASSOCIATED_KEY, enableRssiPollWhenAssociated);
+        sKeyMap.put(THRESHOLD_INITIAL_AUTO_JOIN_ATTEMPT_RSSI_MIN_5G_KEY, thresholdInitialAutoJoinAttemptMin5RSSI);
+        sKeyMap.put(THRESHOLD_INITIAL_AUTO_JOIN_ATTEMPT_RSSI_MIN_24G_KEY, thresholdInitialAutoJoinAttemptMin24RSSI);
+        sKeyMap.put(THRESHOLD_UNBLACKLIST_HARD_5G_KEY, thresholdUnblacklistThreshold5Hard);
+        sKeyMap.put(THRESHOLD_UNBLACKLIST_SOFT_5G_KEY, thresholdUnblacklistThreshold5Soft);
+        sKeyMap.put(THRESHOLD_UNBLACKLIST_HARD_24G_KEY, thresholdUnblacklistThreshold24Hard);
+        sKeyMap.put(THRESHOLD_UNBLACKLIST_SOFT_24G_KEY, thresholdUnblacklistThreshold24Soft);
+        sKeyMap.put(THRESHOLD_GOOD_RSSI_5_KEY, thresholdGoodRssi5);
+        sKeyMap.put(THRESHOLD_LOW_RSSI_5_KEY, thresholdLowRssi5);
+        sKeyMap.put(THRESHOLD_BAD_RSSI_5_KEY, thresholdBadRssi5);
+        sKeyMap.put(THRESHOLD_GOOD_RSSI_24_KEY, thresholdGoodRssi24);
+        sKeyMap.put(THRESHOLD_LOW_RSSI_24_KEY, thresholdLowRssi24);
+        sKeyMap.put(THRESHOLD_BAD_RSSI_24_KEY, thresholdBadRssi24);
+        sKeyMap.put(THRESHOLD_MAX_TX_PACKETS_FOR_NETWORK_SWITCHING_KEY, maxTxPacketForNetworkSwitching);
+        sKeyMap.put(THRESHOLD_MAX_RX_PACKETS_FOR_NETWORK_SWITCHING_KEY, maxRxPacketForNetworkSwitching);
+        sKeyMap.put(THRESHOLD_MAX_TX_PACKETS_FOR_FULL_SCANS_KEY, maxTxPacketForNetworkSwitching);
+        sKeyMap.put(THRESHOLD_MAX_RX_PACKETS_FOR_FULL_SCANS_KEY, maxRxPacketForNetworkSwitching);
+        sKeyMap.put(THRESHOLD_MAX_TX_PACKETS_FOR_PARTIAL_SCANS_KEY, maxTxPacketForNetworkSwitching);
+        sKeyMap.put(THRESHOLD_MAX_RX_PACKETS_FOR_PARTIAL_SCANS_KEY, maxRxPacketForNetworkSwitching);
+        sKeyMap.put(WIFI_VERBOSE_LOGS_KEY, enableVerboseLogging);
+        sKeyMap.put(A_BAND_PREFERENCE_RSSI_THRESHOLD_KEY, bandPreferenceBoostThreshold5);
+        sKeyMap.put(ASSOCIATED_PARTIAL_SCAN_PERIOD_KEY, wifiAssociatedShortScanIntervalMilli);
+        sKeyMap.put(ASSOCIATED_PARTIAL_SCAN_PERIOD_KEY, wifiAssociatedShortScanIntervalMilli);
+
+        sKeyMap.put(ASSOCIATED_FULL_SCAN_BACKOFF_KEY, associatedFullScanBackoff);
+        sKeyMap.put(G_BAND_PREFERENCE_RSSI_THRESHOLD_KEY, bandPreferencePenaltyThreshold5);
+        sKeyMap.put(ALWAYS_ENABLE_SCAN_WHILE_ASSOCIATED_KEY, alwaysEnableScansWhileAssociated);
+        sKeyMap.put(MAX_NUM_PASSIVE_CHANNELS_FOR_PARTIAL_SCANS_KEY, maxNumPassiveChannelsForPartialScans);
+        sKeyMap.put(MAX_NUM_ACTIVE_CHANNELS_FOR_PARTIAL_SCANS_KEY, maxNumActiveChannelsForPartialScans);
+        sKeyMap.put(ENABLE_HAL_BASED_PNO, enableHalBasedPno);
+        sKeyMap.put(ENABLE_HAL_BASED_PNO, enableSsidWhitelist);
 
         if (showNetworks) {
             mLocalLog = mWifiNative.getLocalLog();
@@ -473,20 +578,25 @@
             mFileObserver = null;
         }
 
-        associatedPartialScanPeriodMilli = mContext.getResources().getInteger(
-                R.integer.config_wifi_framework_associated_scan_interval);
-        loge("associatedPartialScanPeriodMilli set to " + associatedPartialScanPeriodMilli);
+        wifiAssociatedShortScanIntervalMilli.set(mContext.getResources().getInteger(
+                R.integer.config_wifi_associated_short_scan_interval));
+        wifiAssociatedLongScanIntervalMilli.set(mContext.getResources().getInteger(
+                R.integer.config_wifi_associated_short_scan_interval));
+        wifiDisconnectedShortScanIntervalMilli.set(mContext.getResources().getInteger(
+                R.integer.config_wifi_disconnected_short_scan_interval));
+        wifiDisconnectedLongScanIntervalMilli.set(mContext.getResources().getInteger(
+                R.integer.config_wifi_disconnected_long_scan_interval));
 
         onlyLinkSameCredentialConfigurations = mContext.getResources().getBoolean(
                 R.bool.config_wifi_only_link_same_credential_configurations);
-        maxNumActiveChannelsForPartialScans = mContext.getResources().getInteger(
-                R.integer.config_wifi_framework_associated_partial_scan_max_num_active_channels);
-        maxNumPassiveChannelsForPartialScans = mContext.getResources().getInteger(
-                R.integer.config_wifi_framework_associated_partial_scan_max_num_passive_channels);
+        maxNumActiveChannelsForPartialScans.set(mContext.getResources().getInteger(
+                R.integer.config_wifi_framework_associated_partial_scan_max_num_active_channels));
+        maxNumPassiveChannelsForPartialScans.set(mContext.getResources().getInteger(
+                R.integer.config_wifi_framework_associated_partial_scan_max_num_passive_channels));
         associatedFullScanMaxIntervalMilli = mContext.getResources().getInteger(
                 R.integer.config_wifi_framework_associated_full_scan_max_interval);
-        associatedFullScanBackoff = mContext.getResources().getInteger(
-                R.integer.config_wifi_framework_associated_full_scan_backoff);
+        associatedFullScanBackoff.set(mContext.getResources().getInteger(
+                R.integer.config_wifi_framework_associated_full_scan_backoff));
         enableLinkDebouncing = mContext.getResources().getBoolean(
                 R.bool.config_wifi_enable_disconnection_debounce);
 
@@ -498,28 +608,28 @@
         bandPreferencePenaltyFactor5 = mContext.getResources().getInteger(
                 R.integer.config_wifi_framework_5GHz_preference_penalty_factor);
 
-        bandPreferencePenaltyThreshold5 = mContext.getResources().getInteger(
-                R.integer.config_wifi_framework_5GHz_preference_penalty_threshold);
-        bandPreferenceBoostThreshold5 = mContext.getResources().getInteger(
-                R.integer.config_wifi_framework_5GHz_preference_boost_threshold);
+        bandPreferencePenaltyThreshold5.set(mContext.getResources().getInteger(
+                R.integer.config_wifi_framework_5GHz_preference_penalty_threshold));
+        bandPreferenceBoostThreshold5.set(mContext.getResources().getInteger(
+                R.integer.config_wifi_framework_5GHz_preference_boost_threshold));
 
         associatedHysteresisHigh = mContext.getResources().getInteger(
                 R.integer.config_wifi_framework_current_association_hysteresis_high);
         associatedHysteresisLow = mContext.getResources().getInteger(
                 R.integer.config_wifi_framework_current_association_hysteresis_low);
 
-        thresholdBadRssi5 = mContext.getResources().getInteger(
-                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz);
-        thresholdLowRssi5 = mContext.getResources().getInteger(
-                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_5GHz);
-        thresholdGoodRssi5 = mContext.getResources().getInteger(
-                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_5GHz);
-        thresholdBadRssi24 = mContext.getResources().getInteger(
-                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz);
-        thresholdLowRssi24 = mContext.getResources().getInteger(
-                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_24GHz);
-        thresholdGoodRssi24 = mContext.getResources().getInteger(
-                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz);
+        thresholdBadRssi5.set(mContext.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz));
+        thresholdLowRssi5.set(mContext.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_5GHz));
+        thresholdGoodRssi5.set(mContext.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_5GHz));
+        thresholdBadRssi24.set(mContext.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz));
+        thresholdLowRssi24.set(mContext.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_24GHz));
+        thresholdGoodRssi24.set(mContext.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz));
 
         enableWifiCellularHandoverUserTriggeredAdjustment = mContext.getResources().getBoolean(
                 R.bool.config_wifi_framework_cellular_handover_enable_user_triggered_adjustment);
@@ -540,12 +650,8 @@
         wifiConfigBlacklistMinTimeMilli = mContext.getResources().getInteger(
                 R.integer.config_wifi_framework_network_black_list_min_time_milli);
 
-
-        enableAutoJoinScanWhenAssociated = mContext.getResources().getBoolean(
-                R.bool.config_wifi_framework_enable_associated_autojoin_scan);
-
-        enableAutoJoinWhenAssociated = mContext.getResources().getBoolean(
-                R.bool.config_wifi_framework_enable_associated_network_selection);
+        enableAutoJoinWhenAssociated.set(mContext.getResources().getBoolean(
+                R.bool.config_wifi_framework_enable_associated_network_selection));
 
         currentNetworkBoost = mContext.getResources().getInteger(
                 R.integer.config_wifi_framework_current_network_boost);
@@ -555,10 +661,33 @@
 
         networkSwitchingBlackListPeriodMilli = mContext.getResources().getInteger(
                 R.integer.config_wifi_network_switching_blacklist_time);
+
+        enableHalBasedPno.set(mContext.getResources().getBoolean(
+                        R.bool.config_wifi_hal_pno_enable));
+
+        enableSsidWhitelist.set(mContext.getResources().getBoolean(
+                R.bool.config_wifi_ssid_white_list_enable));
+        if (!enableHalBasedPno.get() && enableSsidWhitelist.get()) {
+            enableSsidWhitelist.set(false);
+        }
+
+        boolean hs2on = mContext.getResources().getBoolean(R.bool.config_wifi_hotspot2_enabled);
+        Log.d(Utils.hs2LogTag(getClass()), "Passpoint is " + (hs2on ? "enabled" : "disabled"));
+
+        mMOManager = new MOManager(new File(PPS_FILE), hs2on);
+        mAnqpCache = new AnqpCache();
+        mSupplicantBridge = new SupplicantBridge(mWifiNative, this);
+        mScanDetailCaches = new HashMap<>();
+
+        mSIMAccessor = new SIMAccessor(mContext);
+    }
+
+    public void trimANQPCache(boolean all) {
+        mAnqpCache.clear(all, DBG);
     }
 
     void enableVerboseLogging(int verbose) {
-        enableVerboseLogging = verbose;
+        enableVerboseLogging.set(verbose);
         if (verbose > 0) {
             VDBG = true;
             showNetworks = true;
@@ -602,7 +731,8 @@
         return mConfiguredNetworks.size();
     }
 
-    private List<WifiConfiguration> getConfiguredNetworks(Map<String, String> pskMap) {
+    private List<WifiConfiguration>
+    getConfiguredNetworks(Map<String, String> pskMap) {
         List<WifiConfiguration> networks = new ArrayList<>();
         for(WifiConfiguration config : mConfiguredNetworks.values()) {
             WifiConfiguration newConfig = new WifiConfiguration(config);
@@ -627,6 +757,19 @@
     }
 
     /**
+     * This function returns all configuration, and is used for cebug and creating bug reports.
+     */
+    private List<WifiConfiguration>
+    getAllConfiguredNetworks() {
+        List<WifiConfiguration> networks = new ArrayList<>();
+        for(WifiConfiguration config : mConfiguredNetworks.values()) {
+            WifiConfiguration newConfig = new WifiConfiguration(config);
+            networks.add(newConfig);
+        }
+        return networks;
+    }
+
+    /**
      * Fetch the list of currently configured networks
      * @return List of networks
      */
@@ -644,6 +787,27 @@
     }
 
     /**
+     * Find matching network for this scanResult
+     */
+    WifiConfiguration getMatchingConfig(ScanResult scanResult) {
+        if (scanResult == null) {
+            return null;
+        }
+        for (Map.Entry entry : mScanDetailCaches.entrySet()) {
+            Integer netId = (Integer) entry.getKey();
+            ScanDetailCache cache = (ScanDetailCache) entry.getValue();
+            WifiConfiguration config = getWifiConfiguration(netId);
+            if (config == null)
+                continue;
+            if (cache.get(scanResult.BSSID) != null) {
+                return config;
+            }
+        }
+
+        return null;
+    }
+
+    /**
      * Fetch the preSharedKeys for all networks.
      * @return a map from Ssid to preSharedKey.
      */
@@ -651,12 +815,6 @@
         return readNetworkVariablesFromSupplicantFile("psk");
     }
 
-    int getconfiguredNetworkSize() {
-        if (mConfiguredNetworks == null)
-            return 0;
-        return mConfiguredNetworks.size();
-    }
-
     /**
      * Fetch the list of currently configured networks that were recently seen
      *
@@ -674,7 +832,11 @@
             }
 
             // Calculate the RSSI for scan results that are more recent than milli
-            config.setVisibility(milli);
+            ScanDetailCache cache = getScanDetailCache(config);
+            if (cache == null) {
+                continue;
+            }
+            config.setVisibility(cache.getVisibility(milli));
             if (config.visibility == null) {
                 continue;
             }
@@ -696,14 +858,15 @@
      */
     void updateConfiguration(WifiInfo info) {
         WifiConfiguration config = getWifiConfiguration(info.getNetworkId());
-        if (config != null && config.scanResultCache != null) {
-            ScanResult result = config.scanResultCache.get(info.getBSSID());
-            if (result != null) {
+        if (config != null && getScanDetailCache(config) != null) {
+            ScanDetail scanDetail = getScanDetailCache(config).getScanDetail(info.getBSSID());
+            if (scanDetail != null) {
+                ScanResult result = scanDetail.getScanResult();
                 long previousSeen = result.seen;
                 int previousRssi = result.level;
 
                 // Update the scan result
-                result.seen = System.currentTimeMillis();
+                scanDetail.setSeen();
                 result.level = info.getRssi();
 
                 // Average the RSSI value
@@ -725,8 +888,6 @@
      * @return Wificonfiguration
      */
     WifiConfiguration getWifiConfiguration(int netId) {
-        if (mConfiguredNetworks == null)
-            return null;
         return mConfiguredNetworks.get(netId);
     }
 
@@ -735,16 +896,7 @@
      * @return Wificonfiguration
      */
     WifiConfiguration getWifiConfiguration(String key) {
-        if (key == null)
-            return null;
-        int hash = key.hashCode();
-        if (mNetworkIds == null)
-            return null;
-        Integer n = mNetworkIds.get(hash);
-        if (n == null)
-            return null;
-        int netId = n.intValue();
-        return getWifiConfiguration(netId);
+        return mConfiguredNetworks.getByConfigKey(key);
     }
 
     /**
@@ -786,6 +938,7 @@
                     config.setAutoJoinStatus(WifiConfiguration.AUTO_JOIN_ENABLED);
                 } else {
                     loge("Enable network failed on " + config.networkId);
+
                 }
             }
         }
@@ -796,6 +949,26 @@
         }
     }
 
+    private boolean setNetworkPriorityNative(int netId, int priority) {
+        return mWifiNative.setNetworkVariable(netId,
+                WifiConfiguration.priorityVarName, Integer.toString(priority));
+    }
+
+    private boolean setSSIDNative(int netId, String ssid) {
+        return mWifiNative.setNetworkVariable(netId, WifiConfiguration.ssidVarName,
+                encodeSSID(ssid));
+    }
+
+    public boolean updateLastConnectUid(WifiConfiguration config, int uid) {
+        if (config != null) {
+            if (config.lastConnectUid != uid) {
+                config.lastConnectUid = uid;
+                config.dirty = true;
+                return true;
+            }
+        }
+        return false;
+    }
 
     /**
      * Selects the specified network for connection. This involves
@@ -806,34 +979,61 @@
      * a call to enableAllNetworks() needs to be issued upon a connection
      * or a failure event from supplicant
      *
-     * @param netId network to select for connection
+     * @param config network to select for connection
+     * @param updatePriorities makes config highest priority network
      * @return false if the network id is invalid
      */
-    boolean selectNetwork(int netId) {
-        if (VDBG) localLog("selectNetwork", netId);
-        if (netId == INVALID_NETWORK_ID) return false;
+    boolean selectNetwork(WifiConfiguration config, boolean updatePriorities, int uid) {
+        if (VDBG) localLog("selectNetwork", config.networkId);
+        if (config.networkId == INVALID_NETWORK_ID) return false;
 
         // Reset the priority of each network at start or if it goes too high.
         if (mLastPriority == -1 || mLastPriority > 1000000) {
-            for(WifiConfiguration config : mConfiguredNetworks.values()) {
-                if (config.networkId != INVALID_NETWORK_ID) {
-                    config.priority = 0;
-                    addOrUpdateNetworkNative(config, -1);
+            for(WifiConfiguration config2 : mConfiguredNetworks.values()) {
+                if (updatePriorities) {
+                    if (config2.networkId != INVALID_NETWORK_ID) {
+                        config2.priority = 0;
+                        setNetworkPriorityNative(config2.networkId, config.priority);
+                    }
                 }
             }
             mLastPriority = 0;
         }
 
         // Set to the highest priority and save the configuration.
-        WifiConfiguration config = new WifiConfiguration();
-        config.networkId = netId;
-        config.priority = ++mLastPriority;
+        if (updatePriorities) {
+            config.priority = ++mLastPriority;
+            setNetworkPriorityNative(config.networkId, config.priority);
+            buildPnoList();
+        }
 
-        addOrUpdateNetworkNative(config, -1);
-        mWifiNative.saveConfig();
+        if (config.isPasspoint()) {
+            /* need to slap on the SSID of selected bssid to work */
+            if (getScanDetailCache(config).size() != 0) {
+                ScanDetail result = getScanDetailCache(config).getFirst();
+                if (result == null) {
+                    loge("Could not find scan result for " + config.BSSID);
+                } else {
+                    log("Setting SSID for " + config.networkId + " to" + result.getSSID());
+                    setSSIDNative(config.networkId, result.getSSID());
+                    config.SSID = result.getSSID();
+                }
+
+            } else {
+                loge("Could not find bssid for " + config);
+            }
+        }
+
+        if (updatePriorities)
+            mWifiNative.saveConfig();
+        else
+            mWifiNative.selectNetwork(config.networkId);
+
+        updateLastConnectUid(config, uid);
+        writeKnownNetworkHistory(false);
 
         /* Enable the given network while disabling all other networks */
-        enableNetworkWithoutBroadcast(netId, true);
+        enableNetworkWithoutBroadcast(config.networkId, true);
 
        /* Avoid saving the config & sending a broadcast to prevent settings
         * from displaying a disabled list of networks */
@@ -922,11 +1122,11 @@
         if (info != null
             && info.getBSSID() != null
             && ScanResult.is5GHz(info.getFrequency())
-            && info.getRssi() > (bandPreferenceBoostThreshold5 + 3)) {
+            && info.getRssi() > (bandPreferenceBoostThreshold5.get() + 3)) {
             WifiConfiguration config = getWifiConfiguration(info.getNetworkId());
             if (config != null) {
-                if (config.scanResultCache != null) {
-                    ScanResult result = config.scanResultCache.get(info.getBSSID());
+                if (getScanDetailCache(config) != null) {
+                    ScanResult result = getScanDetailCache(config).get(info.getBSSID());
                     if (result != null) {
                         result.setAutoJoinStatus(ScanResult.AUTO_ROAM_DISABLED + 1);
                     }
@@ -1017,16 +1217,13 @@
             return null;
         }
 
-        WifiConfiguration foundConfig = null;
+        WifiConfiguration foundConfig = mConfiguredNetworks.getEphemeral(SSID);
 
         mDeletedEphemeralSSIDs.add(SSID);
         loge("Forget ephemeral SSID " + SSID + " num=" + mDeletedEphemeralSSIDs.size());
 
-        for (WifiConfiguration config : mConfiguredNetworks.values()) {
-            if (SSID.equals(config.SSID) && config.ephemeral) {
-                loge("Found ephemeral config in disableEphemeralNetwork: " + config.networkId);
-                foundConfig = config;
-            }
+        if (foundConfig != null) {
+            loge("Found ephemeral config in disableEphemeralNetwork: " + foundConfig.networkId);
         }
 
         // Force a write, because the mDeletedEphemeralSSIDs list has changed even though the
@@ -1045,13 +1242,18 @@
     boolean forgetNetwork(int netId) {
         if (showNetworks) localLog("forgetNetwork", netId);
 
+        WifiConfiguration config = mConfiguredNetworks.get(netId);
         boolean remove = removeConfigAndSendBroadcastIfNeeded(netId);
         if (!remove) {
             //success but we dont want to remove the network from supplicant conf file
             return true;
         }
         if (mWifiNative.removeNetwork(netId)) {
+            if (config != null && config.isPasspoint()) {
+                writePasspointConfigs(config.FQDN, null);
+            }
             mWifiNative.saveConfig();
+            writeKnownNetworkHistory(true);
             return true;
         } else {
             loge("Failed to remove network " + netId);
@@ -1074,6 +1276,14 @@
         Log.e(TAG, " key=" + config.configKey() + " netId=" + Integer.toString(config.networkId)
                 + " uid=" + Integer.toString(config.creatorUid)
                 + "/" + Integer.toString(config.lastUpdateUid));
+
+        if (config.isPasspoint()) {
+            /* create a temporary SSID with providerFriendlyName */
+            Long csum = getChecksum(config.FQDN);
+            config.SSID = csum.toString();
+            config.enterpriseConfig.setDomainSuffixMatch(config.FQDN);
+        }
+
         NetworkUpdateResult result = addOrUpdateNetworkNative(config, uid);
         if (result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID) {
             WifiConfiguration conf = mConfiguredNetworks.get(result.getNetworkId());
@@ -1083,9 +1293,103 @@
                             WifiManager.CHANGE_REASON_CONFIG_CHANGE);
             }
         }
+
         return result.getNetworkId();
     }
 
+
+    /**
+     * Get the Wifi PNO list
+     *
+     * @return list of WifiNative.WifiPnoNetwork
+     */
+    private void buildPnoList() {
+        mCachedPnoList = new ArrayList<WifiNative.WifiPnoNetwork>();
+
+        ArrayList<WifiConfiguration> sortedWifiConfigurations
+                = new ArrayList<WifiConfiguration>(getConfiguredNetworks());
+        Log.e(TAG, "buildPnoList sortedWifiConfigurations size " + sortedWifiConfigurations.size());
+        if (sortedWifiConfigurations.size() != 0) {
+            // Sort by descending priority
+            Collections.sort(sortedWifiConfigurations, new Comparator<WifiConfiguration>() {
+                public int compare(WifiConfiguration a, WifiConfiguration b) {
+                    return a.priority >= b.priority ? 1 : -1;
+                }
+            });
+        }
+
+        for (WifiConfiguration config : sortedWifiConfigurations) {
+            // Initialize the RSSI threshold with sane value:
+            // Use the 2.4GHz threshold since most WifiConfigurations are dual bands
+            // There is very little penalty with triggering too soon, i.e. if PNO finds a network
+            // that has an RSSI too low for us to attempt joining it.
+            int threshold = thresholdInitialAutoJoinAttemptMin24RSSI.get();
+            Log.e(TAG, "found sortedWifiConfigurations : " + config.configKey());
+            WifiNative.WifiPnoNetwork network = mWifiNative.new WifiPnoNetwork(config, threshold);
+            mCachedPnoList.add(network);
+        }
+    }
+
+    String[] getWhiteListedSsids(WifiConfiguration config) {
+        int num_ssids = 0;
+        String nonQuoteSSID;
+        int length;
+        if (enableSsidWhitelist.get() == false)
+            return null;
+        List<String> list = new ArrayList<String>();
+        if (config == null)
+            return null;
+        if (config.linkedConfigurations == null) {
+            return null;
+        }
+        if (config.SSID == null || TextUtils.isEmpty(config.SSID)) {
+            return null;
+        }
+        for (String configKey : config.linkedConfigurations.keySet()) {
+
+            // Sanity check that the linked configuration is still valid
+            WifiConfiguration link = getWifiConfiguration(configKey);
+            if (link == null) {
+                continue;
+            }
+
+            if (link.autoJoinStatus != WifiConfiguration.AUTO_JOIN_ENABLED) {
+                continue;
+            }
+
+            if (link.hiddenSSID == true) {
+                continue;
+            }
+
+            if (link.SSID == null || TextUtils.isEmpty(link.SSID)) {
+                continue;
+            }
+
+            length = link.SSID.length();
+            if (length > 2 && (link.SSID.charAt(0) == '"') && link.SSID.charAt(length - 1) == '"') {
+                nonQuoteSSID = link.SSID.substring(1, length - 1);
+            } else {
+                nonQuoteSSID = link.SSID;
+            }
+
+            list.add(nonQuoteSSID);
+        }
+
+        if (list.size() != 0) {
+            length = config.SSID.length();
+            if (length > 2 && (config.SSID.charAt(0) == '"')
+                    && config.SSID.charAt(length - 1) == '"') {
+                nonQuoteSSID = config.SSID.substring(1, length - 1);
+            } else {
+                nonQuoteSSID = config.SSID;
+            }
+
+            list.add(nonQuoteSSID);
+        }
+
+        return (String[])list.toArray(new String[0]);
+    }
+
     /**
      * Remove a network. Note that there is no saveConfig operation.
      * This function is retained for compatibility with the public
@@ -1097,13 +1401,24 @@
      */
     boolean removeNetwork(int netId) {
         if (showNetworks) localLog("removeNetwork", netId);
+        WifiConfiguration config = mConfiguredNetworks.get(netId);
         boolean ret = mWifiNative.removeNetwork(netId);
         if (ret) {
             removeConfigAndSendBroadcastIfNeeded(netId);
+            if (config != null && config.isPasspoint()) {
+                writePasspointConfigs(config.FQDN, null);
+            }
         }
         return ret;
     }
 
+
+    static private Long getChecksum(String source) {
+        Checksum csum = new CRC32();
+        csum.update(source.getBytes(), 0, source.getBytes().length);
+        return csum.getValue();
+    }
+
     private boolean removeConfigAndSendBroadcastIfNeeded(int netId) {
         WifiConfiguration config = mConfiguredNetworks.get(netId);
         if (config != null) {
@@ -1126,20 +1441,23 @@
                     || config.allowedKeyManagement.get(KeyMgmt.WPA_PSK)) {
                 if (!TextUtils.isEmpty(config.SSID)) {
                     /* Remember that we deleted this PSK SSID */
-                    Checksum csum = new CRC32();
                     if (config.SSID != null) {
-                        csum.update(config.SSID.getBytes(), 0, config.SSID.getBytes().length);
-                        mDeletedSSIDs.add(csum.getValue());
+                        Long csum = getChecksum(config.SSID);
+                        mDeletedSSIDs.add(csum);
+                        loge("removeNetwork " + Integer.toString(netId)
+                                + " key=" + config.configKey()
+                                + " config.id=" + Integer.toString(config.networkId)
+                                + "  crc=" + csum);
+                    } else {
+                        loge("removeNetwork " + Integer.toString(netId)
+                                + " key=" + config.configKey()
+                                + " config.id=" + Integer.toString(config.networkId));
                     }
-                    loge("removeNetwork " + Integer.toString(netId)
-                            + " key=" + config.configKey()
-                            + " config.id=" + Integer.toString(config.networkId)
-                            + "  crc=" + csum.getValue());
                 }
             }
 
             mConfiguredNetworks.remove(netId);
-            mNetworkIds.remove(configKey(config));
+            mScanDetailCaches.remove(netId);
 
             writeIpAndProxyConfigurations();
             sendConfiguredNetworksChangedBroadcast(config, WifiManager.CHANGE_REASON_REMOVED);
@@ -1148,6 +1466,57 @@
         return true;
     }
 
+    /*
+     * Remove all networks associated with an application
+     *
+     * @param packageName name of the package of networks to remove
+     * @return {@code true} if all networks removed successfully, {@code false} otherwise
+     */
+    boolean removeNetworksForApp(ApplicationInfo app) {
+        if (app == null || app.packageName == null) {
+            return false;
+        }
+
+        boolean success = true;
+
+        WifiConfiguration [] copiedConfigs =
+                mConfiguredNetworks.values().toArray(new WifiConfiguration[0]);
+        for (WifiConfiguration config : copiedConfigs) {
+            if (app.uid != config.creatorUid || !app.packageName.equals(config.creatorName)) {
+                continue;
+            }
+            if (showNetworks) {
+                localLog("Removing network " + config.SSID
+                         + ", application \"" + app.packageName + "\" uninstalled"
+                         + " from user " + UserHandle.getUserId(app.uid));
+            }
+            success &= removeNetwork(config.networkId);
+        }
+
+        mWifiNative.saveConfig();
+
+        return success;
+    }
+
+    boolean removeNetworksForUser(int userId) {
+        boolean success = true;
+
+        WifiConfiguration[] copiedConfigs =
+                mConfiguredNetworks.values().toArray(new WifiConfiguration[0]);
+        for (WifiConfiguration config : copiedConfigs) {
+            if (userId != UserHandle.getUserId(config.creatorUid)) {
+                continue;
+            }
+            success &= removeNetwork(config.networkId);
+            if (showNetworks) {
+                localLog("Removing network " + config.SSID
+                        + ", user " + userId + " removed");
+            }
+        }
+
+        return success;
+    }
+
     /**
      * Enable a network. Note that there is no saveConfig operation.
      * This function is retained for compatibility with the public
@@ -1157,15 +1526,17 @@
      * @param netId network to be enabled
      * @return {@code true} if it succeeds, {@code false} otherwise
      */
-    boolean enableNetwork(int netId, boolean disableOthers) {
+    boolean enableNetwork(int netId, boolean disableOthers, int uid) {
         boolean ret = enableNetworkWithoutBroadcast(netId, disableOthers);
         if (disableOthers) {
-            if (VDBG) localLog("enableNetwork(disableOthers=true) ", netId);
+            if (VDBG) localLog("enableNetwork(disableOthers=true, uid=" + uid + ") ", netId);
+            updateLastConnectUid(getWifiConfiguration(netId), uid);
+            writeKnownNetworkHistory(false);
             sendConfiguredNetworksChangedBroadcast();
         } else {
             if (VDBG) localLog("enableNetwork(disableOthers=false) ", netId);
-            WifiConfiguration enabledNetwork = null;
-            synchronized(mConfiguredNetworks) {
+            WifiConfiguration enabledNetwork;
+            synchronized(mConfiguredNetworks) {                     // !!! Useless synchronization!
                 enabledNetwork = mConfiguredNetworks.get(netId);
             }
             // check just in case the network was removed by someone else.
@@ -1192,14 +1563,12 @@
     void disableAllNetworks() {
         if (VDBG) localLog("disableAllNetworks");
         boolean networkDisabled = false;
-        for(WifiConfiguration config : mConfiguredNetworks.values()) {
-            if(config != null && config.status != Status.DISABLED) {
-                if(mWifiNative.disableNetwork(config.networkId)) {
-                    networkDisabled = true;
-                    config.status = Status.DISABLED;
-                } else {
-                    loge("Disable network failed on " + config.networkId);
-                }
+        for (WifiConfiguration enabled : mConfiguredNetworks.getEnabledNetworks()) {
+            if(mWifiNative.disableNetwork(enabled.networkId)) {
+                networkDisabled = true;
+                enabled.status = Status.DISABLED;
+            } else {
+                loge("Disable network failed on " + enabled.networkId);
             }
         }
 
@@ -1213,7 +1582,11 @@
      * @return {@code true} if it succeeds, {@code false} otherwise
      */
     boolean disableNetwork(int netId) {
-        return disableNetwork(netId, WifiConfiguration.DISABLED_UNKNOWN_REASON);
+        boolean ret = disableNetwork(netId, WifiConfiguration.DISABLED_UNKNOWN_REASON);
+        if (ret) {
+            mWifiStateMachine.registerNetworkDisabled(netId);
+        }
+        return ret;
     }
 
     /**
@@ -1359,7 +1732,7 @@
 
     /**
      * Fetch the proxy properties for a given network id
-     * @param network id
+     * @param netId id
      * @return ProxyInfo for the network id
      */
     ProxyInfo getProxyProperties(int netId) {
@@ -1372,7 +1745,7 @@
 
     /**
      * Return if the specified network is using static IP
-     * @param network id
+     * @param netId id
      * @return {@code true} if using static ip for netId
      */
     boolean isUsingStaticIp(int netId) {
@@ -1383,6 +1756,11 @@
         return false;
     }
 
+    boolean isEphemeral(int netId) {
+        WifiConfiguration config = mConfiguredNetworks.get(netId);
+        return config != null && config.ephemeral;
+    }
+
     /**
      * Should be called when a single network configuration is made.
      * @param network The network configuration that changed.
@@ -1414,7 +1792,6 @@
         mLastPriority = 0;
 
         mConfiguredNetworks.clear();
-        mNetworkIds.clear();
 
         int last_id = -1;
         boolean done = false;
@@ -1474,12 +1851,11 @@
                 config.setIpAssignment(IpAssignment.DHCP);
                 config.setProxySettings(ProxySettings.NONE);
 
-                if (mNetworkIds.containsKey(configKey(config))) {
+                if (mConfiguredNetworks.getByConfigKey(config.configKey()) != null) {
                     // That SSID is already known, just ignore this duplicate entry
                     if (showNetworks) localLog("discarded duplicate network ", config.networkId);
-                } else if(config.isValid()){
+                } else if(WifiServiceImpl.isValid(config)){
                     mConfiguredNetworks.put(config.networkId, config);
-                    mNetworkIds.put(configKey(config), config.networkId);
                     if (showNetworks) localLog("loaded configured network", config.networkId);
                 } else {
                     if (showNetworks) log("Ignoring loaded configured for network " + config.networkId
@@ -1490,40 +1866,48 @@
             done = (lines.length == 1);
         }
 
+        readPasspointConfig();
         readIpAndProxyConfigurations();
         readNetworkHistory();
         readAutoJoinConfig();
 
+        buildPnoList();
+
         sendConfiguredNetworksChangedBroadcast();
 
-        if (showNetworks) localLog("loadConfiguredNetworks loaded " + mNetworkIds.size() + " networks");
+        if (showNetworks) localLog("loadConfiguredNetworks loaded " + mConfiguredNetworks.size() + " networks");
 
-        if (mNetworkIds.size() == 0) {
-            // no networks? Lets log if the wpa_supplicant.conf file contents
-            BufferedReader reader = null;
+        if (mConfiguredNetworks.isEmpty()) {
+            // no networks? Lets log if the file contents
+            logKernelTime();
+            logContents(SUPPLICANT_CONFIG_FILE);
+            logContents(SUPPLICANT_CONFIG_FILE_BACKUP);
+            logContents(networkHistoryConfigFile);
+        }
+    }
+
+    private void logContents(String file) {
+        localLog("--- Begin " + file + " ---", true);
+        BufferedReader reader = null;
+        try {
+            reader = new BufferedReader(new FileReader(file));
+            for (String line = reader.readLine(); line != null; line = reader.readLine()) {
+                localLog(line, true);
+            }
+        } catch (FileNotFoundException e) {
+            localLog("Could not open " + file + ", " + e, true);
+        } catch (IOException e) {
+            localLog("Could not read " + file + ", " + e, true);
+        } finally {
             try {
-                reader = new BufferedReader(new FileReader(SUPPLICANT_CONFIG_FILE));
-                if (DBG) {
-                    localLog("--- Begin wpa_supplicant.conf Contents ---", true);
-                    for (String line = reader.readLine(); line != null; line = reader.readLine()) {
-                        localLog(line, true);
-                    }
-                    localLog("--- End wpa_supplicant.conf Contents ---", true);
+                if (reader != null) {
+                    reader.close();
                 }
-            } catch (FileNotFoundException e) {
-                localLog("Could not open " + SUPPLICANT_CONFIG_FILE + ", " + e, true);
             } catch (IOException e) {
-                localLog("Could not read " + SUPPLICANT_CONFIG_FILE + ", " + e, true);
-            } finally {
-                try {
-                    if (reader != null) {
-                        reader.close();
-                    }
-                } catch (IOException e) {
-                    // Just ignore the fact that we couldn't close
-                }
+                // Just ignore the fact that we couldn't close
             }
         }
+        localLog("--- End " + file + " Contents ---", true);
     }
 
     private Map<String, String> readNetworkVariablesFromSupplicantFile(String key) {
@@ -1627,6 +2011,37 @@
         return false;
     }
 
+    void readPasspointConfig() {
+
+        List<HomeSP> homeSPs;
+        try {
+            homeSPs = mMOManager.loadAllSPs();
+        } catch (IOException e) {
+            loge("Could not read " + PPS_FILE + " : " + e);
+            return;
+        }
+
+        mConfiguredNetworks.populatePasspointData(homeSPs, mWifiNative);
+    }
+
+    public void writePasspointConfigs(final String fqdn, final HomeSP homeSP) {
+        mWriter.write(PPS_FILE, new DelayedDiskWrite.Writer() {
+            @Override
+            public void onWriteCalled(DataOutputStream out) throws IOException {
+                try {
+                    if (homeSP != null) {
+                        mMOManager.addSP(homeSP);
+                    }
+                    else {
+                        mMOManager.removeSP(fqdn);
+                    }
+                } catch (IOException e) {
+                    loge("Could not write " + PPS_FILE + " : " + e);
+                }
+            }
+        }, false);
+    }
+
     public void writeKnownNetworkHistory(boolean force) {
         boolean needUpdate = force;
 
@@ -1675,7 +2090,7 @@
                                 + " nid:" + Integer.toString(config.networkId));
                     }
 
-                    if (config.isValid() == false)
+                    if (!WifiServiceImpl.isValid(config))
                         continue;
 
                     if (config.SSID == null) {
@@ -1687,133 +2102,142 @@
                     if (VDBG) {
                         loge("writeKnownNetworkHistory write config " + config.configKey());
                     }
-                    out.writeUTF(CONFIG_KEY + config.configKey() + SEPARATOR_KEY);
+                    out.writeUTF(CONFIG_KEY + SEPARATOR + config.configKey() + NL);
 
-                    out.writeUTF(SSID_KEY + config.SSID + SEPARATOR_KEY);
-                    out.writeUTF(FQDN_KEY + config.FQDN + SEPARATOR_KEY);
-
-                    out.writeUTF(PRIORITY_KEY + Integer.toString(config.priority) + SEPARATOR_KEY);
-                    out.writeUTF(STATUS_KEY + Integer.toString(config.autoJoinStatus)
-                            + SEPARATOR_KEY);
-                    out.writeUTF(SUPPLICANT_STATUS_KEY + Integer.toString(config.status)
-                            + SEPARATOR_KEY);
-                    out.writeUTF(SUPPLICANT_DISABLE_REASON_KEY
-                            + Integer.toString(config.disableReason)
-                            + SEPARATOR_KEY);
-                    out.writeUTF(NETWORK_ID_KEY + Integer.toString(config.networkId)
-                            + SEPARATOR_KEY);
-                    out.writeUTF(SELF_ADDED_KEY + Boolean.toString(config.selfAdded)
-                            + SEPARATOR_KEY);
-                    out.writeUTF(DID_SELF_ADD_KEY + Boolean.toString(config.didSelfAdd)
-                            + SEPARATOR_KEY);
-                    out.writeUTF(NO_INTERNET_ACCESS_REPORTS_KEY
-                            + Integer.toString(config.numNoInternetAccessReports)
-                            + SEPARATOR_KEY);
-                    out.writeUTF(VALIDATED_INTERNET_ACCESS_KEY
-                            + Boolean.toString(config.validatedInternetAccess)
-                            + SEPARATOR_KEY);
-                    out.writeUTF(EPHEMERAL_KEY
-                            + Boolean.toString(config.ephemeral)
-                            + SEPARATOR_KEY);
-                    if (config.peerWifiConfiguration != null) {
-                        out.writeUTF(PEER_CONFIGURATION_KEY + config.peerWifiConfiguration
-                                + SEPARATOR_KEY);
+                    if (config.SSID != null) {
+                        out.writeUTF(SSID_KEY + SEPARATOR + config.SSID + NL);
                     }
-                    out.writeUTF(NUM_CONNECTION_FAILURES_KEY
-                            + Integer.toString(config.numConnectionFailures)
-                            + SEPARATOR_KEY);
-                    out.writeUTF(NUM_AUTH_FAILURES_KEY
-                            + Integer.toString(config.numAuthFailures)
-                            + SEPARATOR_KEY);
-                    out.writeUTF(NUM_IP_CONFIG_FAILURES_KEY
-                            + Integer.toString(config.numIpConfigFailures)
-                            + SEPARATOR_KEY);
-                    out.writeUTF(SCORER_OVERRIDE_KEY + Integer.toString(config.numScorerOverride)
-                            + SEPARATOR_KEY);
-                    out.writeUTF(SCORER_OVERRIDE_AND_SWITCH_KEY
-                            + Integer.toString(config.numScorerOverrideAndSwitchedNetwork)
-                            + SEPARATOR_KEY);
-                    out.writeUTF(NUM_ASSOCIATION_KEY
-                            + Integer.toString(config.numAssociation)
-                            + SEPARATOR_KEY);
-                    out.writeUTF(JOIN_ATTEMPT_BOOST_KEY
-                            + Integer.toString(config.autoJoinUseAggressiveJoinAttemptThreshold)
-                            + SEPARATOR_KEY);
-                    //out.writeUTF(BLACKLIST_MILLI_KEY + Long.toString(config.blackListTimestamp)
-                    //        + SEPARATOR_KEY);
-                    out.writeUTF(CREATOR_UID_KEY + Integer.toString(config.creatorUid)
-                            + SEPARATOR_KEY);
-                    out.writeUTF(CONNECT_UID_KEY + Integer.toString(config.lastConnectUid)
-                            + SEPARATOR_KEY);
-                    out.writeUTF(UPDATE_UID_KEY + Integer.toString(config.lastUpdateUid)
-                            + SEPARATOR_KEY);
+                    if (config.FQDN != null) {
+                        out.writeUTF(FQDN_KEY + SEPARATOR + config.FQDN + NL);
+                    }
+
+                    out.writeUTF(PRIORITY_KEY + SEPARATOR +
+                            Integer.toString(config.priority) + NL);
+                    out.writeUTF(STATUS_KEY + SEPARATOR +
+                            Integer.toString(config.autoJoinStatus) + NL);
+                    out.writeUTF(SUPPLICANT_STATUS_KEY + SEPARATOR +
+                            Integer.toString(config.status) + NL);
+                    out.writeUTF(SUPPLICANT_DISABLE_REASON_KEY + SEPARATOR +
+                            Integer.toString(config.disableReason) + NL);
+                    out.writeUTF(NETWORK_ID_KEY + SEPARATOR +
+                            Integer.toString(config.networkId) + NL);
+                    out.writeUTF(SELF_ADDED_KEY + SEPARATOR +
+                            Boolean.toString(config.selfAdded) + NL);
+                    out.writeUTF(DID_SELF_ADD_KEY + SEPARATOR +
+                            Boolean.toString(config.didSelfAdd) + NL);
+                    out.writeUTF(NO_INTERNET_ACCESS_REPORTS_KEY + SEPARATOR +
+                            Integer.toString(config.numNoInternetAccessReports) + NL);
+                    out.writeUTF(VALIDATED_INTERNET_ACCESS_KEY + SEPARATOR +
+                            Boolean.toString(config.validatedInternetAccess) + NL);
+                    out.writeUTF(EPHEMERAL_KEY + SEPARATOR +
+                            Boolean.toString(config.ephemeral) + NL);
+                    if (config.creationTime != null) {
+                        out.writeUTF(CREATION_TIME_KEY + SEPARATOR + config.creationTime + NL);
+                    }
+                    if (config.updateTime != null) {
+                        out.writeUTF(UPDATE_TIME_KEY + SEPARATOR + config.updateTime + NL);
+                    }
+                    if (config.peerWifiConfiguration != null) {
+                        out.writeUTF(PEER_CONFIGURATION_KEY + SEPARATOR +
+                                config.peerWifiConfiguration + NL);
+                    }
+                    out.writeUTF(NUM_CONNECTION_FAILURES_KEY + SEPARATOR +
+                            Integer.toString(config.numConnectionFailures) + NL);
+                    out.writeUTF(NUM_AUTH_FAILURES_KEY + SEPARATOR +
+                            Integer.toString(config.numAuthFailures) + NL);
+                    out.writeUTF(NUM_IP_CONFIG_FAILURES_KEY + SEPARATOR +
+                            Integer.toString(config.numIpConfigFailures) + NL);
+                    out.writeUTF(SCORER_OVERRIDE_KEY + SEPARATOR +
+                            Integer.toString(config.numScorerOverride) + NL);
+                    out.writeUTF(SCORER_OVERRIDE_AND_SWITCH_KEY + SEPARATOR +
+                            Integer.toString(config.numScorerOverrideAndSwitchedNetwork) + NL);
+                    out.writeUTF(NUM_ASSOCIATION_KEY + SEPARATOR +
+                            Integer.toString(config.numAssociation) + NL);
+                    out.writeUTF(JOIN_ATTEMPT_BOOST_KEY + SEPARATOR +
+                            Integer.toString(config.autoJoinUseAggressiveJoinAttemptThreshold)+ NL);
+                    //out.writeUTF(BLACKLIST_MILLI_KEY + SEPARATOR +
+                    // Long.toString(config.blackListTimestamp) + NL);
+                    out.writeUTF(CREATOR_UID_KEY + SEPARATOR +
+                            Integer.toString(config.creatorUid) + NL);
+                    out.writeUTF(CONNECT_UID_KEY + SEPARATOR +
+                            Integer.toString(config.lastConnectUid) + NL);
+                    out.writeUTF(UPDATE_UID_KEY + SEPARATOR +
+                            Integer.toString(config.lastUpdateUid) + NL);
+                    out.writeUTF(CREATOR_NAME_KEY + SEPARATOR +
+                            config.creatorName + NL);
+                    out.writeUTF(UPDATE_NAME_KEY + SEPARATOR +
+                            config.lastUpdateName + NL);
+                    out.writeUTF(USER_APPROVED_KEY + SEPARATOR +
+                            Integer.toString(config.userApproved) + NL);
                     String allowedKeyManagementString =
                             makeString(config.allowedKeyManagement,
                                     WifiConfiguration.KeyMgmt.strings);
-                    out.writeUTF(AUTH_KEY + allowedKeyManagementString + SEPARATOR_KEY);
+                    out.writeUTF(AUTH_KEY + SEPARATOR +
+                            allowedKeyManagementString + NL);
 
                     if (config.connectChoices != null) {
                         for (String key : config.connectChoices.keySet()) {
                             Integer choice = config.connectChoices.get(key);
-                            out.writeUTF(CHOICE_KEY + key + "="
-                                    + choice.toString() + SEPARATOR_KEY);
+                            out.writeUTF(CHOICE_KEY + SEPARATOR +
+                                    key + "=" + choice.toString() + NL);
                         }
                     }
                     if (config.linkedConfigurations != null) {
-                        loge("writeKnownNetworkHistory write linked "
+                        log("writeKnownNetworkHistory write linked "
                                 + config.linkedConfigurations.size());
 
                         for (String key : config.linkedConfigurations.keySet()) {
-                            out.writeUTF(LINK_KEY + key + SEPARATOR_KEY);
+                            out.writeUTF(LINK_KEY + SEPARATOR + key + NL);
                         }
                     }
 
                     String macAddress = config.defaultGwMacAddress;
                     if (macAddress != null) {
-                        out.writeUTF(DEFAULT_GW_KEY + macAddress + SEPARATOR_KEY);
+                        out.writeUTF(DEFAULT_GW_KEY + SEPARATOR + macAddress + NL);
                     }
 
-                    if (config.scanResultCache != null) {
-                        for (ScanResult result : config.scanResultCache.values()) {
-                            out.writeUTF(BSSID_KEY + result.BSSID + SEPARATOR_KEY);
+                    if (getScanDetailCache(config) != null) {
+                        for (ScanDetail scanDetail : getScanDetailCache(config).values()) {
+                            ScanResult result = scanDetail.getScanResult();
+                            out.writeUTF(BSSID_KEY + SEPARATOR +
+                                    result.BSSID + NL);
 
-                            out.writeUTF(FREQ_KEY + Integer.toString(result.frequency)
-                                    + SEPARATOR_KEY);
+                            out.writeUTF(FREQ_KEY + SEPARATOR +
+                                    Integer.toString(result.frequency) + NL);
 
-                            out.writeUTF(RSSI_KEY + Integer.toString(result.level)
-                                    + SEPARATOR_KEY);
+                            out.writeUTF(RSSI_KEY + SEPARATOR +
+                                    Integer.toString(result.level) + NL);
 
-                            out.writeUTF(BSSID_STATUS_KEY
-                                    + Integer.toString(result.autoJoinStatus)
-                                    + SEPARATOR_KEY);
+                            out.writeUTF(BSSID_STATUS_KEY + SEPARATOR +
+                                    Integer.toString(result.autoJoinStatus) + NL);
 
                             //if (result.seen != 0) {
-                            //    out.writeUTF(MILLI_KEY + Long.toString(result.seen)
-                            //            + SEPARATOR_KEY);
+                            //    out.writeUTF(MILLI_KEY + SEPARATOR + Long.toString(result.seen)
+                            //            + NL);
                             //}
-                            out.writeUTF(BSSID_KEY_END + SEPARATOR_KEY);
+                            out.writeUTF(BSSID_KEY_END + NL);
                         }
                     }
                     if (config.lastFailure != null) {
-                        out.writeUTF(FAILURE_KEY + config.lastFailure + SEPARATOR_KEY);
+                        out.writeUTF(FAILURE_KEY + SEPARATOR + config.lastFailure + NL);
                     }
-                    out.writeUTF(SEPARATOR_KEY);
+                    out.writeUTF(NL);
                     // Add extra blank lines for clarity
-                    out.writeUTF(SEPARATOR_KEY);
-                    out.writeUTF(SEPARATOR_KEY);
+                    out.writeUTF(NL);
+                    out.writeUTF(NL);
                 }
                 if (mDeletedSSIDs != null && mDeletedSSIDs.size() > 0) {
                     for (Long i : mDeletedSSIDs) {
                         out.writeUTF(DELETED_CRC32_KEY);
                         out.writeUTF(String.valueOf(i));
-                        out.writeUTF(SEPARATOR_KEY);
+                        out.writeUTF(NL);
                     }
                 }
                 if (mDeletedEphemeralSSIDs != null && mDeletedEphemeralSSIDs.size() > 0) {
                     for (String ssid : mDeletedEphemeralSSIDs) {
                         out.writeUTF(DELETED_EPHEMERAL_KEY);
                         out.writeUTF(ssid);
-                        out.writeUTF(SEPARATOR_KEY);
+                        out.writeUTF(NL);
                     }
                 }
             }
@@ -1857,720 +2281,279 @@
         if (showNetworks) {
             localLog("readNetworkHistory() path:" + networkHistoryConfigFile);
         }
-        DataInputStream in = null;
-        try {
-            in = new DataInputStream(new BufferedInputStream(new FileInputStream(
-                    networkHistoryConfigFile)));
+
+        try (DataInputStream in =
+                     new DataInputStream(new BufferedInputStream(
+                             new FileInputStream(networkHistoryConfigFile)))) {
+
+            String bssid = null;
+            String ssid = null;
+
+            int freq = 0;
+            int status = 0;
+            long seen = 0;
+            int rssi = WifiConfiguration.INVALID_RSSI;
+            String caps = null;
+
             WifiConfiguration config = null;
             while (true) {
-                int id = -1;
-                String key = in.readUTF();
-                String bssid = null;
-                String ssid = null;
+                String line = in.readUTF();
+                if (line == null) {
+                    break;
+                }
+                int colon = line.indexOf(':');
+                if (colon < 0) {
+                    continue;
+                }
 
-                int freq = 0;
-                int status = 0;
-                long seen = 0;
-                int rssi = WifiConfiguration.INVALID_RSSI;
-                String caps = null;
-                if (key.startsWith(CONFIG_KEY)) {
+                String key = line.substring(0, colon).trim();
+                String value = line.substring(colon + 1).trim();
 
-                    if (config != null) {
-                        config = null;
-                    }
-                    String configKey = key.replace(CONFIG_KEY, "");
-                    configKey = configKey.replace(SEPARATOR_KEY, "");
-                    // get the networkId for that config Key
-                    Integer n = mNetworkIds.get(configKey.hashCode());
+                if (key.equals(CONFIG_KEY)) {
+
+                    config = mConfiguredNetworks.getByConfigKey(value);
+                    
                     // skip reading that configuration data
                     // since we don't have a corresponding network ID
-                    if (n == null) {
-                        localLog("readNetworkHistory didnt find netid for hash="
-                                + Integer.toString(configKey.hashCode())
-                                + " key: " + configKey);
-                        continue;
-                    }
-                    config = mConfiguredNetworks.get(n);
                     if (config == null) {
-                        localLog("readNetworkHistory didnt find config for netid="
-                                + n.toString()
-                                + " key: " + configKey);
-                    }
-                    status = 0;
-                    ssid = null;
-                    bssid = null;
-                    freq = 0;
-                    seen = 0;
-                    rssi = WifiConfiguration.INVALID_RSSI;
-                    caps = null;
+                        localLog("readNetworkHistory didnt find netid for hash="
+                                + Integer.toString(value.hashCode())
+                                + " key: " + value);
+                        mLostConfigsDbg.add(value);
+                        continue;
+                    } else {
+                        // After an upgrade count old connections as owned by system
+                        if (config.creatorName == null || config.lastUpdateName == null) {
+                            config.creatorName =
+                                mContext.getPackageManager().getNameForUid(Process.SYSTEM_UID);
+                            config.lastUpdateName = config.creatorName;
 
+                            if (DBG) Log.w(TAG, "Upgrading network " + config.networkId
+                                    + " to " + config.creatorName);
+                        }
+                    }
                 } else if (config != null) {
-                    if (key.startsWith(SSID_KEY)) {
-                        ssid = key.replace(SSID_KEY, "");
-                        ssid = ssid.replace(SEPARATOR_KEY, "");
-                        if (config.SSID != null && !config.SSID.equals(ssid)) {
-                            loge("Error parsing network history file, mismatched SSIDs");
-                            config = null; //error
-                            ssid = null;
-                        } else {
-                            config.SSID = ssid;
-                        }
-                    }
-
-                    if (key.startsWith(FQDN_KEY)) {
-                        String fqdn = key.replace(FQDN_KEY, "");
-                        fqdn = fqdn.replace(SEPARATOR_KEY, "");
-                        config.FQDN = fqdn;
-                    }
-
-                    if (key.startsWith(DEFAULT_GW_KEY)) {
-                        String gateway = key.replace(DEFAULT_GW_KEY, "");
-                        gateway = gateway.replace(SEPARATOR_KEY, "");
-                        config.defaultGwMacAddress = gateway;
-                    }
-
-                    if (key.startsWith(STATUS_KEY)) {
-                        String st = key.replace(STATUS_KEY, "");
-                        st = st.replace(SEPARATOR_KEY, "");
-                        config.autoJoinStatus = Integer.parseInt(st);
-                    }
-
-                    if (key.startsWith(SUPPLICANT_DISABLE_REASON_KEY)) {
-                        String reason = key.replace(SUPPLICANT_DISABLE_REASON_KEY, "");
-                        reason = reason.replace(SEPARATOR_KEY, "");
-                        config.disableReason = Integer.parseInt(reason);
-                    }
-
-                    if (key.startsWith(SELF_ADDED_KEY)) {
-                        String selfAdded = key.replace(SELF_ADDED_KEY, "");
-                        selfAdded = selfAdded.replace(SEPARATOR_KEY, "");
-                        config.selfAdded = Boolean.parseBoolean(selfAdded);
-                    }
-
-                    if (key.startsWith(DID_SELF_ADD_KEY)) {
-                        String didSelfAdd = key.replace(DID_SELF_ADD_KEY, "");
-                        didSelfAdd = didSelfAdd.replace(SEPARATOR_KEY, "");
-                        config.didSelfAdd = Boolean.parseBoolean(didSelfAdd);
-                    }
-
-                    if (key.startsWith(NO_INTERNET_ACCESS_REPORTS_KEY)) {
-                        String access = key.replace(NO_INTERNET_ACCESS_REPORTS_KEY, "");
-                        access = access.replace(SEPARATOR_KEY, "");
-                        config.numNoInternetAccessReports = Integer.parseInt(access);
-                    }
-
-                    if (key.startsWith(VALIDATED_INTERNET_ACCESS_KEY)) {
-                        String access = key.replace(VALIDATED_INTERNET_ACCESS_KEY, "");
-                        access = access.replace(SEPARATOR_KEY, "");
-                        config.validatedInternetAccess = Boolean.parseBoolean(access);
-                    }
-
-                    if (key.startsWith(EPHEMERAL_KEY)) {
-                        String access = key.replace(EPHEMERAL_KEY, "");
-                        access = access.replace(SEPARATOR_KEY, "");
-                        config.ephemeral = Boolean.parseBoolean(access);
-                    }
-
-                    if (key.startsWith(CREATOR_UID_KEY)) {
-                        String uid = key.replace(CREATOR_UID_KEY, "");
-                        uid = uid.replace(SEPARATOR_KEY, "");
-                        config.creatorUid = Integer.parseInt(uid);
-                    }
-
-                    if (key.startsWith(BLACKLIST_MILLI_KEY)) {
-                        String milli = key.replace(BLACKLIST_MILLI_KEY, "");
-                        milli = milli.replace(SEPARATOR_KEY, "");
-                        config.blackListTimestamp = Long.parseLong(milli);
-                    }
-
-                    if (key.startsWith(NUM_CONNECTION_FAILURES_KEY)) {
-                        String num = key.replace(NUM_CONNECTION_FAILURES_KEY, "");
-                        num = num.replace(SEPARATOR_KEY, "");
-                        config.numConnectionFailures = Integer.parseInt(num);
-                    }
-
-                    if (key.startsWith(NUM_IP_CONFIG_FAILURES_KEY)) {
-                        String num = key.replace(NUM_IP_CONFIG_FAILURES_KEY, "");
-                        num = num.replace(SEPARATOR_KEY, "");
-                        config.numIpConfigFailures = Integer.parseInt(num);
-                    }
-
-                    if (key.startsWith(NUM_AUTH_FAILURES_KEY)) {
-                        String num = key.replace(NUM_AUTH_FAILURES_KEY, "");
-                        num = num.replace(SEPARATOR_KEY, "");
-                        config.numIpConfigFailures = Integer.parseInt(num);
-                    }
-
-                    if (key.startsWith(SCORER_OVERRIDE_KEY)) {
-                        String num = key.replace(SCORER_OVERRIDE_KEY, "");
-                        num = num.replace(SEPARATOR_KEY, "");
-                        config.numScorerOverride = Integer.parseInt(num);
-                    }
-
-                    if (key.startsWith(SCORER_OVERRIDE_AND_SWITCH_KEY)) {
-                        String num = key.replace(SCORER_OVERRIDE_AND_SWITCH_KEY, "");
-                        num = num.replace(SEPARATOR_KEY, "");
-                        config.numScorerOverrideAndSwitchedNetwork = Integer.parseInt(num);
-                    }
-
-                    if (key.startsWith(NUM_ASSOCIATION_KEY)) {
-                        String num = key.replace(NUM_ASSOCIATION_KEY, "");
-                        num = num.replace(SEPARATOR_KEY, "");
-                        config.numAssociation = Integer.parseInt(num);
-                    }
-
-                    if (key.startsWith(JOIN_ATTEMPT_BOOST_KEY)) {
-                        String num = key.replace(JOIN_ATTEMPT_BOOST_KEY, "");
-                        num = num.replace(SEPARATOR_KEY, "");
-                        config.autoJoinUseAggressiveJoinAttemptThreshold = Integer.parseInt(num);
-                    }
-
-                    if (key.startsWith(CONNECT_UID_KEY)) {
-                        String uid = key.replace(CONNECT_UID_KEY, "");
-                        uid = uid.replace(SEPARATOR_KEY, "");
-                        config.lastConnectUid = Integer.parseInt(uid);
-                    }
-
-                    if (key.startsWith(UPDATE_UID_KEY)) {
-                        String uid = key.replace(UPDATE_UID_KEY, "");
-                        uid = uid.replace(SEPARATOR_KEY, "");
-                        config.lastUpdateUid = Integer.parseInt(uid);
-                    }
-
-                    if (key.startsWith(FAILURE_KEY)) {
-                        config.lastFailure = key.replace(FAILURE_KEY, "");
-                        config.lastFailure = config.lastFailure.replace(SEPARATOR_KEY, "");
-                    }
-
-                    if (key.startsWith(PEER_CONFIGURATION_KEY)) {
-                        config.peerWifiConfiguration = key.replace(PEER_CONFIGURATION_KEY, "");
-                        config.peerWifiConfiguration =
-                                config.peerWifiConfiguration.replace(SEPARATOR_KEY, "");
-                    }
-
-                    if (key.startsWith(CHOICE_KEY)) {
-                        String choiceStr = key.replace(CHOICE_KEY, "");
-                        choiceStr = choiceStr.replace(SEPARATOR_KEY, "");
-                        String configKey = "";
-                        int choice = 0;
-                        Matcher match = mConnectChoice.matcher(choiceStr);
-                        if (!match.find()) {
-                            if (DBG) Log.d(TAG, "WifiConfigStore: connectChoice: " +
-                                    " Couldnt match pattern : " + choiceStr);
-                        } else {
-                            configKey = match.group(1);
-                            try {
-                                choice = Integer.parseInt(match.group(2));
-                            } catch (NumberFormatException e) {
-                                choice = 0;
+                    switch (key) {
+                        case SSID_KEY:
+                            if (config.isPasspoint()) {
+                                break;
                             }
-                            if (choice > 0) {
-                                if (config.connectChoices == null) {
-                                    config.connectChoices = new HashMap<String, Integer>();
+                            ssid = value;
+                            if (config.SSID != null && !config.SSID.equals(ssid)) {
+                                loge("Error parsing network history file, mismatched SSIDs");
+                                config = null; //error
+                                ssid = null;
+                            } else {
+                                config.SSID = ssid;
+                            }
+                            break;
+                        case FQDN_KEY:
+                            // Check for literal 'null' to be backwards compatible.
+                            config.FQDN = value.equals("null") ? null : value;
+                            break;
+                        case DEFAULT_GW_KEY:
+                            config.defaultGwMacAddress = value;
+                            break;
+                        case STATUS_KEY:
+                            config.autoJoinStatus = Integer.parseInt(value);
+                            break;
+                        case SUPPLICANT_DISABLE_REASON_KEY:
+                            config.disableReason = Integer.parseInt(value);
+                            break;
+                        case SELF_ADDED_KEY:
+                            config.selfAdded = Boolean.parseBoolean(value);
+                            break;
+                        case DID_SELF_ADD_KEY:
+                            config.didSelfAdd = Boolean.parseBoolean(value);
+                            break;
+                        case NO_INTERNET_ACCESS_REPORTS_KEY:
+                            config.numNoInternetAccessReports = Integer.parseInt(value);
+                            break;
+                        case VALIDATED_INTERNET_ACCESS_KEY:
+                            config.validatedInternetAccess = Boolean.parseBoolean(value);
+                            break;
+                        case CREATION_TIME_KEY:
+                            config.creationTime = value;
+                            break;
+                        case UPDATE_TIME_KEY:
+                            config.updateTime = value;
+                            break;
+                        case EPHEMERAL_KEY:
+                            config.ephemeral = Boolean.parseBoolean(value);
+                            break;
+                        case CREATOR_UID_KEY:
+                            config.creatorUid = Integer.parseInt(value);
+                            break;
+                        case BLACKLIST_MILLI_KEY:
+                            config.blackListTimestamp = Long.parseLong(value);
+                            break;
+                        case NUM_CONNECTION_FAILURES_KEY:
+                            config.numConnectionFailures = Integer.parseInt(value);
+                            break;
+                        case NUM_IP_CONFIG_FAILURES_KEY:
+                            config.numIpConfigFailures = Integer.parseInt(value);
+                            break;
+                        case NUM_AUTH_FAILURES_KEY:
+                            config.numIpConfigFailures = Integer.parseInt(value);
+                            break;
+                        case SCORER_OVERRIDE_KEY:
+                            config.numScorerOverride = Integer.parseInt(value);
+                            break;
+                        case SCORER_OVERRIDE_AND_SWITCH_KEY:
+                            config.numScorerOverrideAndSwitchedNetwork = Integer.parseInt(value);
+                            break;
+                        case NUM_ASSOCIATION_KEY:
+                            config.numAssociation = Integer.parseInt(value);
+                            break;
+                        case JOIN_ATTEMPT_BOOST_KEY:
+                            config.autoJoinUseAggressiveJoinAttemptThreshold =
+                                    Integer.parseInt(value);
+                            break;
+                        case CONNECT_UID_KEY:
+                            config.lastConnectUid = Integer.parseInt(value);
+                            break;
+                        case UPDATE_UID_KEY:
+                            config.lastUpdateUid = Integer.parseInt(value);
+                            break;
+                        case FAILURE_KEY:
+                            config.lastFailure = value;
+                            break;
+                        case PEER_CONFIGURATION_KEY:
+                            config.peerWifiConfiguration = value;
+                            break;
+                        case CHOICE_KEY:
+                            String configKey = "";
+                            int choice = 0;
+                            Matcher match = mConnectChoice.matcher(value);
+                            if (!match.find()) {
+                                if (DBG) Log.d(TAG, "WifiConfigStore: connectChoice: " +
+                                        " Couldnt match pattern : " + value);
+                            } else {
+                                configKey = match.group(1);
+                                try {
+                                    choice = Integer.parseInt(match.group(2));
+                                } catch (NumberFormatException e) {
+                                    choice = 0;
                                 }
-                                config.connectChoices.put(configKey, choice);
+                                if (choice > 0) {
+                                    if (config.connectChoices == null) {
+                                        config.connectChoices = new HashMap<>();
+                                    }
+                                    config.connectChoices.put(configKey, choice);
+                                }
                             }
-                        }
-                    }
-
-                    if (key.startsWith(LINK_KEY)) {
-                        String configKey = key.replace(LINK_KEY, "");
-                        configKey = configKey.replace(SEPARATOR_KEY, "");
-                        if (config.linkedConfigurations == null) {
-                            config.linkedConfigurations = new HashMap<String, Integer>();
-                        }
-                        if (config.linkedConfigurations != null) {
-                            config.linkedConfigurations.put(configKey, -1);
-                        }
-                    }
-
-                    if (key.startsWith(BSSID_KEY)) {
-                        if (key.startsWith(BSSID_KEY)) {
-                            bssid = key.replace(BSSID_KEY, "");
-                            bssid = bssid.replace(SEPARATOR_KEY, "");
+                            break;
+                        case LINK_KEY:
+                            if (config.linkedConfigurations == null) {
+                                config.linkedConfigurations = new HashMap<>();
+                            }
+                            else {
+                                config.linkedConfigurations.put(value, -1);
+                            }
+                            break;
+                        case BSSID_KEY:
+                            status = 0;
+                            ssid = null;
+                            bssid = null;
                             freq = 0;
                             seen = 0;
                             rssi = WifiConfiguration.INVALID_RSSI;
                             caps = "";
-                            status = 0;
-                        }
-
-                        if (key.startsWith(RSSI_KEY)) {
-                            String lvl = key.replace(RSSI_KEY, "");
-                            lvl = lvl.replace(SEPARATOR_KEY, "");
-                            rssi = Integer.parseInt(lvl);
-                        }
-
-                        if (key.startsWith(BSSID_STATUS_KEY)) {
-                            String st = key.replace(BSSID_STATUS_KEY, "");
-                            st = st.replace(SEPARATOR_KEY, "");
-                            status = Integer.parseInt(st);
-                        }
-
-                        if (key.startsWith(FREQ_KEY)) {
-                            String channel = key.replace(FREQ_KEY, "");
-                            channel = channel.replace(SEPARATOR_KEY, "");
-                            freq = Integer.parseInt(channel);
-                        }
-
-                        if (key.startsWith(DATE_KEY)) {
-                        /*
-                         * when reading the configuration from file we don't update the date
-                         * so as to avoid reading back stale or non-sensical data that would
-                         * depend on network time.
-                         * The date of a WifiConfiguration should only come from actual scan result.
-                         *
-                        String s = key.replace(FREQ_KEY, "");
-                        seen = Integer.getInteger(s);
-                        */
-                        }
-
-                        if (key.startsWith(BSSID_KEY_END)) {
+                            break;
+                        case RSSI_KEY:
+                            rssi = Integer.parseInt(value);
+                            break;
+                        case BSSID_STATUS_KEY:
+                            status = Integer.parseInt(value);
+                            break;
+                        case FREQ_KEY:
+                            freq = Integer.parseInt(value);
+                            break;
+                        case DATE_KEY:
+                            /*
+                             * when reading the configuration from file we don't update the date
+                             * so as to avoid reading back stale or non-sensical data that would
+                             * depend on network time.
+                             * The date of a WifiConfiguration should only come from actual scan result.
+                             *
+                            String s = key.replace(FREQ_KEY, "");
+                            seen = Integer.getInteger(s);
+                            */
+                            break;
+                        case BSSID_KEY_END:
                             if ((bssid != null) && (ssid != null)) {
 
-                                if (config.scanResultCache == null) {
-                                    config.scanResultCache = new HashMap<String, ScanResult>();
+                                if (getScanDetailCache(config) != null) {
+                                    WifiSsid wssid = WifiSsid.createFromAsciiEncoded(ssid);
+                                    ScanDetail scanDetail = new ScanDetail(wssid, bssid,
+                                            caps, rssi, freq, (long) 0, seen);
+                                    getScanDetailCache(config).put(scanDetail);
+                                    scanDetail.getScanResult().autoJoinStatus = status;
                                 }
-                                WifiSsid wssid = WifiSsid.createFromAsciiEncoded(ssid);
-                                ScanResult result = new ScanResult(wssid, bssid,
-                                        caps, rssi, freq, (long) 0);
-                                result.seen = seen;
-                                config.scanResultCache.put(bssid, result);
-                                result.autoJoinStatus = status;
                             }
-                        }
-
-                        if (key.startsWith(DELETED_CRC32_KEY)) {
-                            String crc = key.replace(DELETED_CRC32_KEY, "");
-                            Long c = Long.parseLong(crc);
-                            mDeletedSSIDs.add(c);
-                        }
-                        if (key.startsWith(DELETED_EPHEMERAL_KEY)) {
-                            String s = key.replace(DELETED_EPHEMERAL_KEY, "");
-                            if (!TextUtils.isEmpty(s)) {
-                                s = s.replace(SEPARATOR_KEY, "");
-                                mDeletedEphemeralSSIDs.add(s);
+                            break;
+                        case DELETED_CRC32_KEY:
+                            mDeletedSSIDs.add(Long.parseLong(value));
+                            break;
+                        case DELETED_EPHEMERAL_KEY:
+                            if (!TextUtils.isEmpty(value)) {
+                                mDeletedEphemeralSSIDs.add(value);
                             }
-                        }
+                            break;
+                        case CREATOR_NAME_KEY:
+                            config.creatorName = value;
+                            break;
+                        case UPDATE_NAME_KEY:
+                            config.lastUpdateName = value;
+                            break;
+                        case USER_APPROVED_KEY:
+                            config.userApproved = Integer.parseInt(value);
+                            break;
                     }
                 }
             }
-        } catch (EOFException ignore) {
-            if (in != null) {
-                try {
-                    in.close();
-                } catch (Exception e) {
-                    loge("readNetworkHistory: Error reading file" + e);
-                }
-            }
+        } catch (NumberFormatException e) {
+            Log.e(TAG, "readNetworkHistory: failed to read, revert to default, " + e, e);
+        } catch (EOFException e) {
+            // do nothing
         } catch (IOException e) {
-            loge("readNetworkHistory: No config file, revert to default" + e);
-        }
-
-        if(in!=null) {
-            try {
-                in.close();
-            } catch (Exception e) {
-                loge("readNetworkHistory: Error closing file" + e);
-            }
+            Log.e(TAG, "readNetworkHistory: No config file, revert to default, " + e, e);
         }
     }
 
     private void readAutoJoinConfig() {
-        BufferedReader reader = null;
-        try {
-
-            reader = new BufferedReader(new FileReader(autoJoinConfigFile));
-
+        try (BufferedReader reader = new BufferedReader(new FileReader(autoJoinConfigFile))) {
             for (String key = reader.readLine(); key != null; key = reader.readLine()) {
-                if (key != null) {
-                    Log.d(TAG, "readAutoJoinConfig line: " + key);
-                }
-                if (key.startsWith(ENABLE_AUTO_JOIN_WHILE_ASSOCIATED_KEY)) {
-                    String st = key.replace(ENABLE_AUTO_JOIN_WHILE_ASSOCIATED_KEY, "");
-                    st = st.replace(SEPARATOR_KEY, "");
-                    try {
-                        enableAutoJoinWhenAssociated = Integer.parseInt(st) != 0;
-                        Log.d(TAG,"readAutoJoinConfig: enabled = " + enableAutoJoinWhenAssociated);
-                    } catch (NumberFormatException e) {
-                        Log.d(TAG,"readAutoJoinConfig: incorrect format :" + key);
-                    }
+                Log.d(TAG, "readAutoJoinConfig line: " + key);
+
+                int split = key.indexOf(':');
+                if (split < 0) {
+                    continue;
                 }
 
-                if (key.startsWith(ENABLE_FULL_BAND_SCAN_WHEN_ASSOCIATED_KEY)) {
-                    String st = key.replace(ENABLE_FULL_BAND_SCAN_WHEN_ASSOCIATED_KEY, "");
-                    st = st.replace(SEPARATOR_KEY, "");
-                    try {
-                        enableFullBandScanWhenAssociated = Integer.parseInt(st) != 0;
-                        Log.d(TAG,"readAutoJoinConfig: enableFullBandScanWhenAssociated = "
-                                + enableFullBandScanWhenAssociated);
-                    } catch (NumberFormatException e) {
-                        Log.d(TAG,"readAutoJoinConfig: incorrect format :" + key);
-                    }
+                String name = key.substring(0, split);
+                Object reference = sKeyMap.get(name);
+                if (reference == null) {
+                    continue;
                 }
 
-                if (key.startsWith(ENABLE_AUTO_JOIN_SCAN_WHILE_ASSOCIATED_KEY)) {
-                    String st = key.replace(ENABLE_AUTO_JOIN_SCAN_WHILE_ASSOCIATED_KEY, "");
-                    st = st.replace(SEPARATOR_KEY, "");
-                    try {
-                        enableAutoJoinScanWhenAssociated = Integer.parseInt(st) != 0;
-                        Log.d(TAG,"readAutoJoinConfig: enabled = "
-                                + enableAutoJoinScanWhenAssociated);
-                    } catch (NumberFormatException e) {
-                        Log.d(TAG,"readAutoJoinConfig: incorrect format :" + key);
-                    }
-                }
-
-                if (key.startsWith(ENABLE_CHIP_WAKE_UP_WHILE_ASSOCIATED_KEY)) {
-                    String st = key.replace(ENABLE_CHIP_WAKE_UP_WHILE_ASSOCIATED_KEY, "");
-                    st = st.replace(SEPARATOR_KEY, "");
-                    try {
-                        enableChipWakeUpWhenAssociated = Integer.parseInt(st) != 0;
-                        Log.d(TAG,"readAutoJoinConfig: enabled = "
-                                + enableChipWakeUpWhenAssociated);
-                    } catch (NumberFormatException e) {
-                        Log.d(TAG,"readAutoJoinConfig: incorrect format :" + key);
-                    }
-                }
-
-                if (key.startsWith(ENABLE_RSSI_POLL_WHILE_ASSOCIATED_KEY)) {
-                    String st = key.replace(ENABLE_RSSI_POLL_WHILE_ASSOCIATED_KEY, "");
-                    st = st.replace(SEPARATOR_KEY, "");
-                    try {
-                        enableRssiPollWhenAssociated = Integer.parseInt(st) != 0;
-                        Log.d(TAG,"readAutoJoinConfig: enabled = "
-                                + enableRssiPollWhenAssociated);
-                    } catch (NumberFormatException e) {
-                        Log.d(TAG,"readAutoJoinConfig: incorrect format :" + key);
-                    }
-                }
-
-                if (key.startsWith(THRESHOLD_INITIAL_AUTO_JOIN_ATTEMPT_RSSI_MIN_5G_KEY)) {
-                    String st =
-                            key.replace(THRESHOLD_INITIAL_AUTO_JOIN_ATTEMPT_RSSI_MIN_5G_KEY, "");
-                    st = st.replace(SEPARATOR_KEY, "");
-                    try {
-                        thresholdInitialAutoJoinAttemptMin5RSSI = Integer.parseInt(st);
-                        Log.d(TAG,"readAutoJoinConfig: thresholdInitialAutoJoinAttemptMin5RSSI = "
-                                + Integer.toString(thresholdInitialAutoJoinAttemptMin5RSSI));
-                    } catch (NumberFormatException e) {
-                        Log.d(TAG,"readAutoJoinConfig: incorrect format :" + key);
-                    }
-                }
-
-                if (key.startsWith(THRESHOLD_INITIAL_AUTO_JOIN_ATTEMPT_RSSI_MIN_24G_KEY)) {
-                    String st =
-                            key.replace(THRESHOLD_INITIAL_AUTO_JOIN_ATTEMPT_RSSI_MIN_24G_KEY, "");
-                    st = st.replace(SEPARATOR_KEY, "");
-                    try {
-                        thresholdInitialAutoJoinAttemptMin24RSSI = Integer.parseInt(st);
-                        Log.d(TAG,"readAutoJoinConfig: thresholdInitialAutoJoinAttemptMin24RSSI = "
-                                + Integer.toString(thresholdInitialAutoJoinAttemptMin24RSSI));
-                    } catch (NumberFormatException e) {
-                        Log.d(TAG,"readAutoJoinConfig: incorrect format :" + key);
-                    }
-                }
-
-                if (key.startsWith(THRESHOLD_UNBLACKLIST_HARD_5G_KEY)) {
-                    String st = key.replace(THRESHOLD_UNBLACKLIST_HARD_5G_KEY, "");
-                    st = st.replace(SEPARATOR_KEY, "");
-                    try {
-                        thresholdUnblacklistThreshold5Hard = Integer.parseInt(st);
-                        Log.d(TAG,"readAutoJoinConfig: thresholdUnblacklistThreshold5Hard = "
-                            + Integer.toString(thresholdUnblacklistThreshold5Hard));
-                    } catch (NumberFormatException e) {
-                        Log.d(TAG,"readAutoJoinConfig: incorrect format :" + key);
-                    }
-                }
-                if (key.startsWith(THRESHOLD_UNBLACKLIST_SOFT_5G_KEY)) {
-                    String st = key.replace(THRESHOLD_UNBLACKLIST_SOFT_5G_KEY, "");
-                    st = st.replace(SEPARATOR_KEY, "");
-                    try {
-                        thresholdUnblacklistThreshold5Soft = Integer.parseInt(st);
-                        Log.d(TAG,"readAutoJoinConfig: thresholdUnblacklistThreshold5Soft = "
-                            + Integer.toString(thresholdUnblacklistThreshold5Soft));
-                    } catch (NumberFormatException e) {
-                        Log.d(TAG,"readAutoJoinConfig: incorrect format :" + key);
-                    }
-                }
-                if (key.startsWith(THRESHOLD_UNBLACKLIST_HARD_24G_KEY)) {
-                    String st = key.replace(THRESHOLD_UNBLACKLIST_HARD_24G_KEY, "");
-                    st = st.replace(SEPARATOR_KEY, "");
-                    try {
-                        thresholdUnblacklistThreshold24Hard = Integer.parseInt(st);
-                        Log.d(TAG,"readAutoJoinConfig: thresholdUnblacklistThreshold24Hard = "
-                            + Integer.toString(thresholdUnblacklistThreshold24Hard));
-                    } catch (NumberFormatException e) {
-                        Log.d(TAG,"readAutoJoinConfig: incorrect format :" + key);
-                    }
-                }
-                if (key.startsWith(THRESHOLD_UNBLACKLIST_SOFT_24G_KEY)) {
-                    String st = key.replace(THRESHOLD_UNBLACKLIST_SOFT_24G_KEY, "");
-                    st = st.replace(SEPARATOR_KEY, "");
-                    try {
-                        thresholdUnblacklistThreshold24Soft = Integer.parseInt(st);
-                        Log.d(TAG,"readAutoJoinConfig: thresholdUnblacklistThreshold24Soft = "
-                            + Integer.toString(thresholdUnblacklistThreshold24Soft));
-                    } catch (NumberFormatException e) {
-                        Log.d(TAG,"readAutoJoinConfig: incorrect format :" + key);
-                    }
-                }
-
-                if (key.startsWith(THRESHOLD_GOOD_RSSI_5_KEY)) {
-                    String st = key.replace(THRESHOLD_GOOD_RSSI_5_KEY, "");
-                    st = st.replace(SEPARATOR_KEY, "");
-                    try {
-                        thresholdGoodRssi5 = Integer.parseInt(st);
-                        Log.d(TAG,"readAutoJoinConfig: thresholdGoodRssi5 = "
-                            + Integer.toString(thresholdGoodRssi5));
-                    } catch (NumberFormatException e) {
-                        Log.d(TAG,"readAutoJoinConfig: incorrect format :" + key);
-                    }
-                }
-                if (key.startsWith(THRESHOLD_LOW_RSSI_5_KEY)) {
-                    String st = key.replace(THRESHOLD_LOW_RSSI_5_KEY, "");
-                    st = st.replace(SEPARATOR_KEY, "");
-                    try {
-                        thresholdLowRssi5 = Integer.parseInt(st);
-                        Log.d(TAG,"readAutoJoinConfig: thresholdLowRssi5 = "
-                            + Integer.toString(thresholdLowRssi5));
-                    } catch (NumberFormatException e) {
-                        Log.d(TAG,"readAutoJoinConfig: incorrect format :" + key);
-                    }
-                }
-                if (key.startsWith(THRESHOLD_BAD_RSSI_5_KEY)) {
-                    String st = key.replace(THRESHOLD_BAD_RSSI_5_KEY, "");
-                    st = st.replace(SEPARATOR_KEY, "");
-                    try {
-                        thresholdBadRssi5 = Integer.parseInt(st);
-                        Log.d(TAG,"readAutoJoinConfig: thresholdBadRssi5 = "
-                            + Integer.toString(thresholdBadRssi5));
-                    } catch (NumberFormatException e) {
-                        Log.d(TAG,"readAutoJoinConfig: incorrect format :" + key);
-                    }
-                }
-
-                if (key.startsWith(THRESHOLD_GOOD_RSSI_24_KEY)) {
-                    String st = key.replace(THRESHOLD_GOOD_RSSI_24_KEY, "");
-                    st = st.replace(SEPARATOR_KEY, "");
-                    try {
-                        thresholdGoodRssi24 = Integer.parseInt(st);
-                        Log.d(TAG,"readAutoJoinConfig: thresholdGoodRssi24 = "
-                            + Integer.toString(thresholdGoodRssi24));
-                    } catch (NumberFormatException e) {
-                        Log.d(TAG,"readAutoJoinConfig: incorrect format :" + key);
-                    }
-                }
-                if (key.startsWith(THRESHOLD_LOW_RSSI_24_KEY)) {
-                    String st = key.replace(THRESHOLD_LOW_RSSI_24_KEY, "");
-                    st = st.replace(SEPARATOR_KEY, "");
-                    try {
-                        thresholdLowRssi24 = Integer.parseInt(st);
-                        Log.d(TAG,"readAutoJoinConfig: thresholdLowRssi24 = "
-                            + Integer.toString(thresholdLowRssi24));
-                    } catch (NumberFormatException e) {
-                        Log.d(TAG,"readAutoJoinConfig: incorrect format :" + key);
-                    }
-                }
-                if (key.startsWith(THRESHOLD_BAD_RSSI_24_KEY)) {
-                    String st = key.replace(THRESHOLD_BAD_RSSI_24_KEY, "");
-                    st = st.replace(SEPARATOR_KEY, "");
-                    try {
-                        thresholdBadRssi24 = Integer.parseInt(st);
-                        Log.d(TAG,"readAutoJoinConfig: thresholdBadRssi24 = "
-                            + Integer.toString(thresholdBadRssi24));
-                    } catch (NumberFormatException e) {
-                        Log.d(TAG,"readAutoJoinConfig: incorrect format :" + key);
-                    }
-                }
-
-                if (key.startsWith(THRESHOLD_MAX_TX_PACKETS_FOR_NETWORK_SWITCHING_KEY)) {
-                    String st = key.replace(THRESHOLD_MAX_TX_PACKETS_FOR_NETWORK_SWITCHING_KEY, "");
-                    st = st.replace(SEPARATOR_KEY, "");
-                    try {
-                        maxTxPacketForNetworkSwitching = Integer.parseInt(st);
-                        Log.d(TAG,"readAutoJoinConfig: maxTxPacketForNetworkSwitching = "
-                            + Integer.toString(maxTxPacketForNetworkSwitching));
-                    } catch (NumberFormatException e) {
-                        Log.d(TAG,"readAutoJoinConfig: incorrect format :" + key);
-                    }
-                }
-                if (key.startsWith(THRESHOLD_MAX_RX_PACKETS_FOR_NETWORK_SWITCHING_KEY)) {
-                    String st = key.replace(THRESHOLD_MAX_RX_PACKETS_FOR_NETWORK_SWITCHING_KEY, "");
-                    st = st.replace(SEPARATOR_KEY, "");
-                    try {
-                        maxRxPacketForNetworkSwitching = Integer.parseInt(st);
-                        Log.d(TAG,"readAutoJoinConfig: maxRxPacketForNetworkSwitching = "
-                            + Integer.toString(maxRxPacketForNetworkSwitching));
-                    } catch (NumberFormatException e) {
-                        Log.d(TAG,"readAutoJoinConfig: incorrect format :" + key);
-                    }
-                }
-
-                if (key.startsWith(THRESHOLD_MAX_TX_PACKETS_FOR_FULL_SCANS_KEY)) {
-                    String st = key.replace(THRESHOLD_MAX_TX_PACKETS_FOR_FULL_SCANS_KEY, "");
-                    st = st.replace(SEPARATOR_KEY, "");
-                    try {
-                        maxTxPacketForNetworkSwitching = Integer.parseInt(st);
-                        Log.d(TAG,"readAutoJoinConfig: maxTxPacketForFullScans = "
-                                + Integer.toString(maxTxPacketForFullScans));
-                    } catch (NumberFormatException e) {
-                        Log.d(TAG,"readAutoJoinConfig: incorrect format :" + key);
-                    }
-                }
-                if (key.startsWith(THRESHOLD_MAX_RX_PACKETS_FOR_FULL_SCANS_KEY)) {
-                    String st = key.replace(THRESHOLD_MAX_RX_PACKETS_FOR_FULL_SCANS_KEY, "");
-                    st = st.replace(SEPARATOR_KEY, "");
-                    try {
-                        maxRxPacketForNetworkSwitching = Integer.parseInt(st);
-                        Log.d(TAG,"readAutoJoinConfig: maxRxPacketForFullScans = "
-                                + Integer.toString(maxRxPacketForFullScans));
-                    } catch (NumberFormatException e) {
-                        Log.d(TAG,"readAutoJoinConfig: incorrect format :" + key);
-                    }
-                }
-
-                if (key.startsWith(THRESHOLD_MAX_TX_PACKETS_FOR_PARTIAL_SCANS_KEY)) {
-                    String st = key.replace(THRESHOLD_MAX_TX_PACKETS_FOR_PARTIAL_SCANS_KEY, "");
-                    st = st.replace(SEPARATOR_KEY, "");
-                    try {
-                        maxTxPacketForNetworkSwitching = Integer.parseInt(st);
-                        Log.d(TAG,"readAutoJoinConfig: maxTxPacketForPartialScans = "
-                                + Integer.toString(maxTxPacketForPartialScans));
-                    } catch (NumberFormatException e) {
-                        Log.d(TAG,"readAutoJoinConfig: incorrect format :" + key);
-                    }
-                }
-                if (key.startsWith(THRESHOLD_MAX_RX_PACKETS_FOR_PARTIAL_SCANS_KEY)) {
-                    String st = key.replace(THRESHOLD_MAX_RX_PACKETS_FOR_PARTIAL_SCANS_KEY, "");
-                    st = st.replace(SEPARATOR_KEY, "");
-                    try {
-                        maxRxPacketForNetworkSwitching = Integer.parseInt(st);
-                        Log.d(TAG,"readAutoJoinConfig: maxRxPacketForPartialScans = "
-                                + Integer.toString(maxRxPacketForPartialScans));
-                    } catch (NumberFormatException e) {
-                        Log.d(TAG,"readAutoJoinConfig: incorrect format :" + key);
-                    }
-                }
-
-                if (key.startsWith(WIFI_VERBOSE_LOGS_KEY)) {
-                    String st = key.replace(WIFI_VERBOSE_LOGS_KEY, "");
-                    st = st.replace(SEPARATOR_KEY, "");
-                    try {
-                        enableVerboseLogging = Integer.parseInt(st);
-                        Log.d(TAG,"readAutoJoinConfig: enable verbose logs = "
-                                + Integer.toString(enableVerboseLogging));
-                    } catch (NumberFormatException e) {
-                        Log.d(TAG,"readAutoJoinConfig: incorrect format :" + key);
-                    }
-                }
-                if (key.startsWith(A_BAND_PREFERENCE_RSSI_THRESHOLD_KEY)) {
-                    String st = key.replace(A_BAND_PREFERENCE_RSSI_THRESHOLD_KEY, "");
-                    st = st.replace(SEPARATOR_KEY, "");
-                    try {
-                        bandPreferenceBoostThreshold5 = Integer.parseInt(st);
-                        Log.d(TAG,"readAutoJoinConfig: bandPreferenceBoostThreshold5 = "
-                            + Integer.toString(bandPreferenceBoostThreshold5));
-                    } catch (NumberFormatException e) {
-                        Log.d(TAG,"readAutoJoinConfig: incorrect format :" + key);
-                    }
-                }
-                if (key.startsWith(ASSOCIATED_PARTIAL_SCAN_PERIOD_KEY)) {
-                    String st = key.replace(ASSOCIATED_PARTIAL_SCAN_PERIOD_KEY, "");
-                    st = st.replace(SEPARATOR_KEY, "");
-                    try {
-                        associatedPartialScanPeriodMilli = Integer.parseInt(st);
-                        Log.d(TAG,"readAutoJoinConfig: associatedScanPeriod = "
-                                + Integer.toString(associatedPartialScanPeriodMilli));
-                    } catch (NumberFormatException e) {
-                        Log.d(TAG,"readAutoJoinConfig: incorrect format :" + key);
-                    }
-                }
-                if (key.startsWith(ASSOCIATED_FULL_SCAN_BACKOFF_KEY)) {
-                    String st = key.replace(ASSOCIATED_FULL_SCAN_BACKOFF_KEY, "");
-                    st = st.replace(SEPARATOR_KEY, "");
-                    try {
-                        associatedFullScanBackoff = Integer.parseInt(st);
-                        Log.d(TAG,"readAutoJoinConfig: associatedFullScanBackoff = "
-                                + Integer.toString(associatedFullScanBackoff));
-                    } catch (NumberFormatException e) {
-                        Log.d(TAG,"readAutoJoinConfig: incorrect format :" + key);
-                    }
-                }
-                if (key.startsWith(G_BAND_PREFERENCE_RSSI_THRESHOLD_KEY)) {
-                    String st = key.replace(G_BAND_PREFERENCE_RSSI_THRESHOLD_KEY, "");
-                    st = st.replace(SEPARATOR_KEY, "");
-                    try {
-                        bandPreferencePenaltyThreshold5 = Integer.parseInt(st);
-                        Log.d(TAG,"readAutoJoinConfig: bandPreferencePenaltyThreshold5 = "
-                            + Integer.toString(bandPreferencePenaltyThreshold5));
-                    } catch (NumberFormatException e) {
-                        Log.d(TAG,"readAutoJoinConfig: incorrect format :" + key);
-                    }
-                }
-                if (key.startsWith(ALWAYS_ENABLE_SCAN_WHILE_ASSOCIATED_KEY)) {
-                    String st = key.replace(ALWAYS_ENABLE_SCAN_WHILE_ASSOCIATED_KEY, "");
-                    st = st.replace(SEPARATOR_KEY, "");
-                    try {
-                        alwaysEnableScansWhileAssociated = Integer.parseInt(st);
-                        Log.d(TAG,"readAutoJoinConfig: alwaysEnableScansWhileAssociated = "
-                                + Integer.toString(alwaysEnableScansWhileAssociated));
-                    } catch (NumberFormatException e) {
-                        Log.d(TAG,"readAutoJoinConfig: incorrect format :" + key);
-                    }
-                }
-                if (key.startsWith(MAX_NUM_PASSIVE_CHANNELS_FOR_PARTIAL_SCANS_KEY)) {
-                    String st = key.replace(MAX_NUM_PASSIVE_CHANNELS_FOR_PARTIAL_SCANS_KEY, "");
-                    st = st.replace(SEPARATOR_KEY, "");
-                    try {
-                        maxNumPassiveChannelsForPartialScans = Integer.parseInt(st);
-                        Log.d(TAG,"readAutoJoinConfig: maxNumPassiveChannelsForPartialScans = "
-                                + Integer.toString(maxNumPassiveChannelsForPartialScans));
-                    } catch (NumberFormatException e) {
-                        Log.d(TAG,"readAutoJoinConfig: incorrect format :" + key);
-                    }
-                }
-                if (key.startsWith(MAX_NUM_ACTIVE_CHANNELS_FOR_PARTIAL_SCANS_KEY)) {
-                    String st = key.replace(MAX_NUM_ACTIVE_CHANNELS_FOR_PARTIAL_SCANS_KEY, "");
-                    st = st.replace(SEPARATOR_KEY, "");
-                    try {
-                        maxNumActiveChannelsForPartialScans = Integer.parseInt(st);
-                        Log.d(TAG,"readAutoJoinConfig: maxNumActiveChannelsForPartialScans = "
-                                + Integer.toString(maxNumActiveChannelsForPartialScans));
-                    } catch (NumberFormatException e) {
-                        Log.d(TAG,"readAutoJoinConfig: incorrect format :" + key);
-                    }
-                }
-            }
-        } catch (EOFException ignore) {
-            if (reader != null) {
                 try {
-                    reader.close();
-                    reader = null;
-                } catch (Exception e) {
-                    loge("readAutoJoinStatus: Error closing file" + e);
+                    int value = Integer.parseInt(key.substring(split+1).trim());
+                    if (reference.getClass() == AtomicBoolean.class) {
+                        ((AtomicBoolean)reference).set(value != 0);
+                    }
+                    else {
+                        ((AtomicInteger)reference).set(value);
+                    }
+                    Log.d(TAG,"readAutoJoinConfig: " + name + " = " + value);
                 }
-            }
-        } catch (FileNotFoundException ignore) {
-            if (reader != null) {
-                try {
-                    reader.close();
-                    reader = null;
-                } catch (Exception e) {
-                    loge("readAutoJoinStatus: Error closing file" + e);
+                catch (NumberFormatException nfe) {
+                    Log.d(TAG,"readAutoJoinConfig: incorrect format :" + key);
                 }
             }
         } catch (IOException e) {
             loge("readAutoJoinStatus: Error parsing configuration" + e);
         }
-
-        if (reader!=null) {
-           try {
-               reader.close();
-           } catch (Exception e) {
-               loge("readAutoJoinStatus: Error closing file" + e);
-           }
-        }
     }
 
 
@@ -2595,8 +2578,8 @@
 
         for (int i = 0; i < networks.size(); i++) {
             int id = networks.keyAt(i);
-            WifiConfiguration config = mConfiguredNetworks.get(mNetworkIds.get(id));
-
+            WifiConfiguration config = mConfiguredNetworks.getByConfigKeyID(id);
+            // This is the only place the map is looked up through a (dangerous) hash-value!
 
             if (config == null || config.autoJoinStatus == WifiConfiguration.AUTO_JOIN_DELETED ||
                     config.ephemeral) {
@@ -2615,9 +2598,8 @@
      * and that can confuses the supplicant because it uses space charaters as delimiters
      */
 
-    private String encodeSSID(String str){
-        String tmp = removeDoubleQuotes(str);
-        return String.format("%x", new BigInteger(1, tmp.getBytes(Charset.forName("UTF-8"))));
+    public static String encodeSSID(String str){
+        return Utils.toHex(removeDoubleQuotes(str).getBytes(StandardCharsets.UTF_8));
     }
 
     private NetworkUpdateResult addOrUpdateNetworkNative(WifiConfiguration config, int uid) {
@@ -2628,27 +2610,23 @@
          */
 
         if (VDBG) localLog("addOrUpdateNetworkNative " + config.getPrintableSsid());
+        if (config.isPasspoint() && !mMOManager.isEnabled()) {
+            Log.e(TAG, "Passpoint is not enabled");
+            return new NetworkUpdateResult(INVALID_NETWORK_ID);
+        }
 
         int netId = config.networkId;
         boolean newNetwork = false;
         // networkId of INVALID_NETWORK_ID means we want to create a new network
         if (netId == INVALID_NETWORK_ID) {
-            Integer savedNetId = mNetworkIds.get(configKey(config));
-            // Check if either we have a network Id or a WifiConfiguration
-            // matching the one we are trying to add.
-            if (savedNetId == null) {
-                for (WifiConfiguration test : mConfiguredNetworks.values()) {
-                    if (test.configKey().equals(config.configKey())) {
-                        savedNetId = test.networkId;
-                        loge("addOrUpdateNetworkNative " + config.configKey()
-                                + " was found, but no network Id");
-                        break;
-                    }
-                }
-            }
-            if (savedNetId != null) {
-                netId = savedNetId;
+            WifiConfiguration savedConfig = mConfiguredNetworks.getByConfigKey(config.configKey());
+            if (savedConfig != null) {
+                netId = savedConfig.networkId;
             } else {
+                if (mMOManager.getHomeSP(config.FQDN) != null) {
+                    loge("addOrUpdateNetworkNative passpoint " + config.FQDN
+                            + " was found, but no network Id");
+                }
                 newNetwork = true;
                 netId = mWifiNative.addNetwork();
                 if (netId < 0) {
@@ -2673,8 +2651,18 @@
                 break setVariables;
             }
 
+            if (config.isPasspoint()) {
+                if (!mWifiNative.setNetworkVariable(
+                            netId,
+                            idStringVarName,
+                            '"' + config.FQDN + '"')) {
+                    loge("failed to set id_str: " + config.FQDN);
+                    break setVariables;
+                }
+            }
+
             if (config.BSSID != null) {
-                loge("Setting BSSID for " + config.configKey() + " to " + config.BSSID);
+                log("Setting BSSID for " + config.configKey() + " to " + config.BSSID);
                 if (!mWifiNative.setNetworkVariable(
                         netId,
                         WifiConfiguration.bssidVarName,
@@ -2862,6 +2850,11 @@
                             // No need to try to set an obfuscated password, which will fail
                             continue;
                         }
+                        if (key.equals(WifiEnterpriseConfig.REALM_KEY)
+                                || key.equals(WifiEnterpriseConfig.PLMN_KEY)) {
+                            // No need to save realm or PLMN in supplicant
+                            continue;
+                        }
                         if (!mWifiNative.setNetworkVariable(
                                     netId,
                                     key,
@@ -2907,15 +2900,54 @@
                 currentConfig.lastConnectUid = config.lastConnectUid;
                 currentConfig.lastUpdateUid = config.lastUpdateUid;
                 currentConfig.creatorUid = config.creatorUid;
+                currentConfig.creatorName = config.creatorName;
+                currentConfig.lastUpdateName = config.lastUpdateName;
                 currentConfig.peerWifiConfiguration = config.peerWifiConfiguration;
+                currentConfig.FQDN = config.FQDN;
+                currentConfig.providerFriendlyName = config.providerFriendlyName;
+                currentConfig.roamingConsortiumIds = config.roamingConsortiumIds;
+                currentConfig.validatedInternetAccess = config.validatedInternetAccess;
+                currentConfig.numNoInternetAccessReports = config.numNoInternetAccessReports;
+                currentConfig.updateTime = config.updateTime;
+                currentConfig.creationTime = config.creationTime;
             }
             if (DBG) {
-                loge("created new config netId=" + Integer.toString(netId)
-                        + " uid=" + Integer.toString(currentConfig.creatorUid));
+                log("created new config netId=" + Integer.toString(netId)
+                        + " uid=" + Integer.toString(currentConfig.creatorUid)
+                        + " name=" + currentConfig.creatorName);
             }
         }
 
-        if (uid >= 0) {
+        /* save HomeSP object for passpoint networks */
+        HomeSP homeSP = null;
+
+        if (config.isPasspoint()) {
+            try {
+                Credential credential =
+                        new Credential(config.enterpriseConfig, mKeyStore, !newNetwork);
+                HashSet<Long> roamingConsortiumIds = new HashSet<Long>();
+                for (Long roamingConsortiumId : config.roamingConsortiumIds) {
+                    roamingConsortiumIds.add(roamingConsortiumId);
+                }
+
+                homeSP = new HomeSP(Collections.<String, Long>emptyMap(), config.FQDN,
+                        roamingConsortiumIds, Collections.<String>emptySet(),
+                        Collections.<Long>emptySet(), Collections.<Long>emptyList(),
+                        config.providerFriendlyName, null, credential);
+
+                log("created a homeSP object for " + config.networkId + ":" + config.SSID);
+
+                /* fix enterprise config properties for passpoint */
+                currentConfig.enterpriseConfig.setRealm(config.enterpriseConfig.getRealm());
+                currentConfig.enterpriseConfig.setPlmn(config.enterpriseConfig.getPlmn());
+            }
+            catch (IOException ioe) {
+                Log.e(TAG, "Failed to create Passpoint config: " + ioe);
+                return new NetworkUpdateResult(INVALID_NETWORK_ID);
+            }
+        }
+
+        if (uid != WifiConfiguration.UNKNOWN_UID) {
             if (newNetwork) {
                 currentConfig.creatorUid = uid;
             } else {
@@ -2923,8 +2955,18 @@
             }
         }
 
+        // For debug, record the time the configuration was modified
+        StringBuilder sb = new StringBuilder();
+        sb.append("time=");
+        Calendar c = Calendar.getInstance();
+        c.setTimeInMillis(System.currentTimeMillis());
+        sb.append(String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c));
+
         if (newNetwork) {
             currentConfig.dirty = true;
+            currentConfig.creationTime = sb.toString();
+        } else {
+            currentConfig.updateTime = sb.toString();
         }
 
         if (currentConfig.autoJoinStatus == WifiConfiguration.AUTO_JOIN_DELETED) {
@@ -2934,7 +2976,7 @@
             currentConfig.selfAdded = false;
             currentConfig.didSelfAdd = false;
             if (DBG) {
-                loge("remove deleted status netId=" + Integer.toString(netId)
+                log("remove deleted status netId=" + Integer.toString(netId)
                         + " " + currentConfig.configKey());
             }
         }
@@ -2948,26 +2990,59 @@
                 currentConfig.ephemeral) {
             // Make the config non-ephemeral since the user just explicitly clicked it.
             currentConfig.ephemeral = false;
-            if (DBG) loge("remove ephemeral status netId=" + Integer.toString(netId)
+            if (DBG) log("remove ephemeral status netId=" + Integer.toString(netId)
                     + " " + currentConfig.configKey());
         }
 
-        if (DBG) loge("will read network variables netId=" + Integer.toString(netId));
+        if (VDBG) log("will read network variables netId=" + Integer.toString(netId));
 
         readNetworkVariables(currentConfig);
 
+        // Persist configuration paramaters that are not saved by supplicant.
+        if (config.lastUpdateName != null) {
+            currentConfig.lastUpdateName = config.lastUpdateName;
+        }
+        if (config.lastUpdateUid != WifiConfiguration.UNKNOWN_UID) {
+            currentConfig.lastUpdateUid = config.lastUpdateUid;
+        }
+
         mConfiguredNetworks.put(netId, currentConfig);
-        mNetworkIds.put(configKey(currentConfig), netId);
 
         NetworkUpdateResult result = writeIpAndProxyConfigurationsOnChange(currentConfig, config);
         result.setIsNewNetwork(newNetwork);
         result.setNetworkId(netId);
 
+        if (homeSP != null) {
+            writePasspointConfigs(null, homeSP);
+        }
         writeKnownNetworkHistory(false);
 
         return result;
     }
 
+    public WifiConfiguration getWifiConfigForHomeSP(HomeSP homeSP) {
+        WifiConfiguration config = mConfiguredNetworks.getByFQDN(homeSP.getFQDN());
+        if (config == null) {
+            Log.e(TAG, "Could not find network for homeSP " + homeSP.getFQDN());
+        }
+        return config;
+    }
+
+    private HomeSP getHomeSPForConfig(WifiConfiguration config) {
+        WifiConfiguration storedConfig = mConfiguredNetworks.get(config.networkId);
+        return storedConfig != null && storedConfig.isPasspoint() ?
+                mMOManager.getHomeSP(storedConfig.FQDN) : null;
+    }
+
+    public ScanDetailCache getScanDetailCache(WifiConfiguration config) {
+        if (config == null) return null;
+        ScanDetailCache cache = mScanDetailCaches.get(config.networkId);
+        if (cache == null && config.networkId != WifiConfiguration.INVALID_NETWORK_ID) {
+            cache = new ScanDetailCache(config);
+            mScanDetailCaches.put(config.networkId, cache);
+        }
+        return cache;
+    }
 
     /**
      * This function run thru the Saved WifiConfigurations and check if some should be linked.
@@ -2975,7 +3050,7 @@
      */
     public void linkConfiguration(WifiConfiguration config) {
 
-        if (config.scanResultCache != null && config.scanResultCache.size() > 6) {
+        if (getScanDetailCache(config) != null && getScanDetailCache(config).size() > 6) {
             // Ignore configurations with large number of BSSIDs
             return;
         }
@@ -3000,7 +3075,8 @@
                 continue;
             }
 
-            if (link.scanResultCache != null && link.scanResultCache.size() > 6) {
+            ScanDetailCache linkedScanDetailCache = getScanDetailCache(link);
+            if (linkedScanDetailCache != null && linkedScanDetailCache.size() > 6) {
                 // Ignore configurations with large number of BSSIDs
                 continue;
             }
@@ -3019,10 +3095,11 @@
                 // hoping that WifiConfigurations are indeed behind the same gateway.
                 // once both WifiConfiguration have been tried and thus once both efault gateways
                 // are known we will revisit the choice of linking them
-                if ((config.scanResultCache != null) && (config.scanResultCache.size() <= 6)
-                        && (link.scanResultCache != null) && (link.scanResultCache.size() <= 6)) {
-                    for (String abssid : config.scanResultCache.keySet()) {
-                        for (String bbssid : link.scanResultCache.keySet()) {
+                if ((getScanDetailCache(config) != null)
+                        && (getScanDetailCache(config).size() <= 6)) {
+
+                    for (String abssid : getScanDetailCache(config).keySet()) {
+                        for (String bbssid : linkedScanDetailCache.keySet()) {
                             if (VVDBG) {
                                 loge("linkConfiguration try to link due to DBDC BSSID match "
                                         + link.SSID +
@@ -3052,8 +3129,8 @@
 
             if (doLink) {
                 if (VDBG) {
-                   loge("linkConfiguration: will link " + link.configKey()
-                           + " and " + config.configKey());
+                    loge("linkConfiguration: will link " + link.configKey()
+                            + " and " + config.configKey());
                 }
                 if (link.linkedConfigurations == null) {
                     link.linkedConfigurations = new HashMap<String, Integer>();
@@ -3092,139 +3169,6 @@
         }
     }
 
-    /*
-     * We try to link a scan result with a WifiConfiguration for which SSID and
-     * key management dont match,
-     * for instance, we try identify the 5GHz SSID of a DBDC AP,
-     * even though we know only of the 2.4GHz
-     *
-     * Obviously, this function is not optimal since it is used to compare every scan
-     * result with every Saved WifiConfiguration, with a string.equals operation.
-     * As a speed up, might be better to implement the mConfiguredNetworks store as a
-     * <String, WifiConfiguration> object instead of a <Integer, WifiConfiguration> object
-     * so as to speed this up. Also to prevent the tiny probability of hash collision.
-     *
-     */
-    public WifiConfiguration associateWithConfiguration(ScanResult result) {
-        boolean doNotAdd = false;
-        String configKey = WifiConfiguration.configKey(result);
-        if (configKey == null) {
-            if (DBG) loge("associateWithConfiguration(): no config key " );
-            return null;
-        }
-
-        // Need to compare with quoted string
-        String SSID = "\"" + result.SSID + "\"";
-
-        if (VVDBG) {
-            loge("associateWithConfiguration(): try " + configKey);
-        }
-
-        Checksum csum = new CRC32();
-        csum.update(SSID.getBytes(), 0, SSID.getBytes().length);
-        if (mDeletedSSIDs.contains(csum.getValue())) {
-            doNotAdd = true;
-        }
-
-        WifiConfiguration config = null;
-        for (WifiConfiguration link : mConfiguredNetworks.values()) {
-            boolean doLink = false;
-
-            if (link.autoJoinStatus == WifiConfiguration.AUTO_JOIN_DELETED || link.selfAdded ||
-                    link.ephemeral) {
-                if (VVDBG) loge("associateWithConfiguration(): skip selfadd " + link.configKey() );
-                // Make sure we dont associate the scan result to a deleted config
-                continue;
-            }
-
-            if (!link.allowedKeyManagement.get(KeyMgmt.WPA_PSK)) {
-                if (VVDBG) loge("associateWithConfiguration(): skip non-PSK " + link.configKey() );
-                // Make sure we dont associate the scan result to a non-PSK config
-                continue;
-            }
-
-            if (configKey.equals(link.configKey())) {
-                if (VVDBG) loge("associateWithConfiguration(): found it!!! " + configKey );
-                return link; // Found it exactly
-            }
-
-            if (!doNotAdd && (link.scanResultCache != null) && (link.scanResultCache.size() <= 6)) {
-                for (String bssid : link.scanResultCache.keySet()) {
-                    if (result.BSSID.regionMatches(true, 0, bssid, 0, 16)
-                            && SSID.regionMatches(false, 0, link.SSID, 0, 4)) {
-                        // If first 16 ascii characters of BSSID matches, and first 3
-                        // characters of SSID match, we assume this is a home setup
-                        // and thus we will try to transfer the password from the known
-                        // BSSID/SSID to the recently found BSSID/SSID
-
-                        // If (VDBG)
-                        //    loge("associateWithConfiguration OK " );
-                        doLink = true;
-                        break;
-                    }
-                }
-            }
-
-            if (doLink) {
-                // Try to make a non verified WifiConfiguration, but only if the original
-                // configuration was not self already added
-                if (VDBG) {
-                    loge("associateWithConfiguration: try to create " +
-                            result.SSID + " and associate it with: " + link.SSID
-                            + " key " + link.configKey());
-                }
-                config = wifiConfigurationFromScanResult(result);
-                if (config != null) {
-                    config.selfAdded = true;
-                    config.didSelfAdd = true;
-                    config.dirty = true;
-                    config.peerWifiConfiguration = link.configKey();
-                    if (config.allowedKeyManagement.equals(link.allowedKeyManagement) &&
-                            config.allowedKeyManagement.get(KeyMgmt.WPA_PSK)) {
-                        if (VDBG && config != null) {
-                            loge("associateWithConfiguration: got a config from beacon"
-                                    + config.SSID + " key " + config.configKey());
-                        }
-                        // Transfer the credentials from the configuration we are linking from
-                        String psk = readNetworkVariableFromSupplicantFile(link.SSID, "psk");
-                        if (psk != null) {
-                            config.preSharedKey = psk;
-                            if (VDBG) {
-                                if (config.preSharedKey != null)
-                                    loge(" transfer PSK : " + config.preSharedKey);
-                            }
-
-                            // Link configurations
-                            if (link.linkedConfigurations == null) {
-                                link.linkedConfigurations = new HashMap<String, Integer>();
-                            }
-                            if (config.linkedConfigurations == null) {
-                                config.linkedConfigurations = new HashMap<String, Integer>();
-                            }
-                            link.linkedConfigurations.put(config.configKey(), Integer.valueOf(1));
-                            config.linkedConfigurations.put(link.configKey(), Integer.valueOf(1));
-
-                            // Carry over the Ip configuration
-                            if (link.getIpConfiguration() != null) {
-                                config.setIpConfiguration(link.getIpConfiguration());
-                            }
-                        } else {
-                            config = null;
-                        }
-                    } else {
-                        config = null;
-                    }
-                    if (config != null) break;
-                }
-                if (VDBG && config != null) {
-                    loge("associateWithConfiguration: success, created: " + config.SSID
-                            + " key " + config.configKey());
-                }
-            }
-        }
-        return config;
-    }
-
     public HashSet<Integer> makeChannelList(WifiConfiguration config, int age, boolean restrict) {
         if (config == null)
             return null;
@@ -3233,7 +3177,7 @@
         HashSet<Integer> channels = new HashSet<Integer>();
 
         //get channels for this configuration, if there are at least 2 BSSIDs
-        if (config.scanResultCache == null && config.linkedConfigurations == null) {
+        if (getScanDetailCache(config) == null && config.linkedConfigurations == null) {
             return null;
         }
 
@@ -3242,8 +3186,8 @@
             dbg.append("makeChannelList age=" + Integer.toString(age)
                     + " for " + config.configKey()
                     + " max=" + maxNumActiveChannelsForPartialScans);
-            if (config.scanResultCache != null) {
-                dbg.append(" bssids=" + config.scanResultCache.size());
+            if (getScanDetailCache(config) != null) {
+                dbg.append(" bssids=" + getScanDetailCache(config).size());
             }
             if (config.linkedConfigurations != null) {
                 dbg.append(" linked=" + config.linkedConfigurations.size());
@@ -3252,10 +3196,11 @@
         }
 
         int numChannels = 0;
-        if (config.scanResultCache != null && config.scanResultCache.size() > 0) {
-            for (ScanResult result : config.scanResultCache.values()) {
+        if (getScanDetailCache(config) != null && getScanDetailCache(config).size() > 0) {
+            for (ScanDetail scanDetail : getScanDetailCache(config).values()) {
+                ScanResult result = scanDetail.getScanResult();
                 //TODO : cout active and passive channels separately
-                if (numChannels > maxNumActiveChannelsForPartialScans) {
+                if (numChannels > maxNumActiveChannelsForPartialScans.get()) {
                     break;
                 }
                 if (VDBG) {
@@ -3276,16 +3221,17 @@
                 WifiConfiguration linked = getWifiConfiguration(key);
                 if (linked == null)
                     continue;
-                if (linked.scanResultCache == null) {
+                if (getScanDetailCache(linked) == null) {
                     continue;
                 }
-                for (ScanResult result : linked.scanResultCache.values()) {
+                for (ScanDetail scanDetail : getScanDetailCache(linked).values()) {
+                    ScanResult result = scanDetail.getScanResult();
                     if (VDBG) {
                         loge("has link: " + result.BSSID
                                 + " freq=" + Integer.toString(result.frequency)
                                 + " age=" + Long.toString(now_ms - result.seen));
                     }
-                    if (numChannels > maxNumActiveChannelsForPartialScans) {
+                    if (numChannels > maxNumActiveChannelsForPartialScans.get()) {
                         break;
                     }
                     if (((now_ms - result.seen) < age)/*||(!restrict || result.is24GHz())*/) {
@@ -3298,15 +3244,214 @@
         return channels;
     }
 
+    private Map<HomeSP, PasspointMatch> matchPasspointNetworks(ScanDetail scanDetail) {
+        if (!mMOManager.isConfigured()) {
+            return null;
+        }
+        NetworkDetail networkDetail = scanDetail.getNetworkDetail();
+        if (!networkDetail.hasInterworking()) {
+            return null;
+        }
+        updateAnqpCache(scanDetail, networkDetail.getANQPElements());
+
+        Map<HomeSP, PasspointMatch> matches = matchNetwork(scanDetail, true);
+        Log.d(Utils.hs2LogTag(getClass()), scanDetail.getSSID() +
+                " pass 1 matches: " + toMatchString(matches));
+        return matches;
+    }
+
+    private Map<HomeSP, PasspointMatch> matchNetwork(ScanDetail scanDetail, boolean query) {
+        NetworkDetail networkDetail = scanDetail.getNetworkDetail();
+
+        ANQPData anqpData = mAnqpCache.getEntry(networkDetail);
+
+        Map<Constants.ANQPElementType, ANQPElement> anqpElements =
+                anqpData != null ? anqpData.getANQPElements() : null;
+
+        boolean queried = !query;
+        Collection<HomeSP> homeSPs = mMOManager.getLoadedSPs().values();
+        Map<HomeSP, PasspointMatch> matches = new HashMap<>(homeSPs.size());
+        Log.d(Utils.hs2LogTag(getClass()), "match nwk " + scanDetail.toKeyString() +
+                ", anqp " + ( anqpData != null ? "present" : "missing" ) +
+                ", query " + query + ", home sps: " + homeSPs.size());
+
+        for (HomeSP homeSP : homeSPs) {
+            PasspointMatch match = homeSP.match(networkDetail, anqpElements, mSIMAccessor);
+
+            Log.d(Utils.hs2LogTag(getClass()), " -- " +
+                    homeSP.getFQDN() + ": match " + match + ", queried " + queried);
+
+            if (match == PasspointMatch.Incomplete && !queried) {
+                if (mAnqpCache.initiate(networkDetail)) {
+                    mSupplicantBridge.startANQP(scanDetail);
+                }
+                queried = true;
+            }
+            matches.put(homeSP, match);
+        }
+        return matches;
+    }
+
+    public void notifyANQPDone(Long bssid, boolean success) {
+        mSupplicantBridge.notifyANQPDone(bssid, success);
+    }
+
+    public void notifyANQPResponse(ScanDetail scanDetail,
+                                   Map<Constants.ANQPElementType, ANQPElement> anqpElements) {
+
+        updateAnqpCache(scanDetail, anqpElements);
+        if (anqpElements == null || anqpElements.isEmpty()) {
+            return;
+        }
+        scanDetail.propagateANQPInfo(anqpElements);
+
+        Map<HomeSP, PasspointMatch> matches = matchNetwork(scanDetail, false);
+        Log.d(Utils.hs2LogTag(getClass()), scanDetail.getSSID() +
+                " pass 2 matches: " + toMatchString(matches));
+
+        cacheScanResultForPasspointConfigs(scanDetail, matches);
+    }
+
+
+    private void updateAnqpCache(ScanDetail scanDetail,
+                                 Map<Constants.ANQPElementType,ANQPElement> anqpElements)
+    {
+        NetworkDetail networkDetail = scanDetail.getNetworkDetail();
+
+        if (anqpElements == null) {
+            // Try to pull cached data if query failed.
+            ANQPData data = mAnqpCache.getEntry(networkDetail);
+            if (data != null) {
+                scanDetail.propagateANQPInfo(data.getANQPElements());
+            }
+            return;
+        }
+
+        mAnqpCache.update(networkDetail, anqpElements);
+    }
+
+    private static String toMatchString(Map<HomeSP, PasspointMatch> matches) {
+        StringBuilder sb = new StringBuilder();
+        for (Map.Entry<HomeSP, PasspointMatch> entry : matches.entrySet()) {
+            sb.append(' ').append(entry.getKey().getFQDN()).append("->").append(entry.getValue());
+        }
+        return sb.toString();
+    }
+
+    private void cacheScanResultForPasspointConfigs(ScanDetail scanDetail,
+                                           Map<HomeSP,PasspointMatch> matches) {
+
+        for (Map.Entry<HomeSP, PasspointMatch> entry : matches.entrySet()) {
+            PasspointMatch match = entry.getValue();
+            if (match == PasspointMatch.HomeProvider || match == PasspointMatch.RoamingProvider) {
+                WifiConfiguration config = getWifiConfigForHomeSP(entry.getKey());
+                if (config != null) {
+                    cacheScanResultForConfig(config, scanDetail, entry.getValue());
+                } else {
+		            Log.w(Utils.hs2LogTag(getClass()), "Failed to find config for '" +
+                            entry.getKey().getFQDN() + "'");
+                    /* perhaps the configuration was deleted?? */
+                }
+            }
+        }
+    }
+
+    private void cacheScanResultForConfig(
+            WifiConfiguration config, ScanDetail scanDetail, PasspointMatch passpointMatch) {
+
+        ScanResult scanResult = scanDetail.getScanResult();
+
+        if (config.autoJoinStatus >= WifiConfiguration.AUTO_JOIN_DELETED) {
+            if (VVDBG) {
+                loge("updateSavedNetworkHistory(): found a deleted, skip it...  "
+                        + config.configKey());
+            }
+            // The scan result belongs to a deleted config:
+            //   - increment numConfigFound to remember that we found a config
+            //            matching for this scan result
+            //   - dont do anything since the config was deleted, just skip...
+            return;
+        }
+
+        ScanDetailCache scanDetailCache = getScanDetailCache(config);
+        if (scanDetailCache == null) {
+            Log.w(TAG, "Could not allocate scan cache for " + config.SSID);
+            return;
+        }
+
+        // Adding a new BSSID
+        ScanResult result = scanDetailCache.get(scanResult.BSSID);
+        if (result != null) {
+            // transfer the black list status
+            scanResult.autoJoinStatus = result.autoJoinStatus;
+            scanResult.blackListTimestamp = result.blackListTimestamp;
+            scanResult.numIpConfigFailures = result.numIpConfigFailures;
+            scanResult.numConnection = result.numConnection;
+            scanResult.isAutoJoinCandidate = result.isAutoJoinCandidate;
+        }
+
+        if (config.ephemeral) {
+            // For an ephemeral Wi-Fi config, the ScanResult should be considered
+            // untrusted.
+            scanResult.untrusted = true;
+        }
+
+        if (scanDetailCache.size() > (maxNumScanCacheEntries + 64)) {
+            long now_dbg = 0;
+            if (VVDBG) {
+                loge(" Will trim config " + config.configKey()
+                        + " size " + scanDetailCache.size());
+
+                for (ScanDetail sd : scanDetailCache.values()) {
+                    loge("     " + sd.getBSSIDString() + " " + sd.getSeen());
+                }
+                now_dbg = SystemClock.elapsedRealtimeNanos();
+            }
+            // Trim the scan result cache to maxNumScanCacheEntries entries max
+            // Since this operation is expensive, make sure it is not performed
+            // until the cache has grown significantly above the trim treshold
+            scanDetailCache.trim(maxNumScanCacheEntries);
+            if (VVDBG) {
+                long diff = SystemClock.elapsedRealtimeNanos() - now_dbg;
+                loge(" Finished trimming config, time(ns) " + diff);
+                for (ScanDetail sd : scanDetailCache.values()) {
+                    loge("     " + sd.getBSSIDString() + " " + sd.getSeen());
+                }
+            }
+        }
+
+        // Add the scan result to this WifiConfiguration
+        if (passpointMatch != null)
+            scanDetailCache.put(scanDetail, passpointMatch, getHomeSPForConfig(config));
+        else
+            scanDetailCache.put(scanDetail);
+
+        // Since we added a scan result to this configuration, re-attempt linking
+        linkConfiguration(config);
+    }
+
+
     // Update the WifiConfiguration database with the new scan result
     // A scan result can be associated to multiple WifiConfigurations
-    public boolean updateSavedNetworkHistory(ScanResult scanResult) {
+    public boolean updateSavedNetworkHistory(ScanDetail scanDetail) {
+
+        ScanResult scanResult = scanDetail.getScanResult();
+        NetworkDetail networkDetail = scanDetail.getNetworkDetail();
+
         int numConfigFound = 0;
         if (scanResult == null)
             return false;
 
         String SSID = "\"" + scanResult.SSID + "\"";
 
+        if (networkDetail.hasInterworking()) {
+            Map<HomeSP, PasspointMatch> matches = matchPasspointNetworks(scanDetail);
+            if (matches != null) {
+                cacheScanResultForPasspointConfigs(scanDetail, matches);
+                return matches.size() != 0;
+            }
+        }
+
         for (WifiConfiguration config : mConfiguredNetworks.values()) {
             boolean found = false;
 
@@ -3344,70 +3489,7 @@
 
             if (found) {
                 numConfigFound ++;
-
-                if (config.autoJoinStatus >= WifiConfiguration.AUTO_JOIN_DELETED) {
-                    if (VVDBG) {
-                        loge("updateSavedNetworkHistory(): found a deleted, skip it...  "
-                                + config.configKey());
-                    }
-                    // The scan result belongs to a deleted config:
-                    //   - increment numConfigFound to remember that we found a config
-                    //            matching for this scan result
-                    //   - dont do anything since the config was deleted, just skip...
-                    continue;
-                }
-
-                if (config.scanResultCache == null) {
-                    config.scanResultCache = new HashMap<String, ScanResult>();
-                }
-
-                // Adding a new BSSID
-                ScanResult result = config.scanResultCache.get(scanResult.BSSID);
-                if (result == null) {
-                    config.dirty = true;
-                } else {
-                    // transfer the black list status
-                    scanResult.autoJoinStatus = result.autoJoinStatus;
-                    scanResult.blackListTimestamp = result.blackListTimestamp;
-                    scanResult.numIpConfigFailures = result.numIpConfigFailures;
-                    scanResult.numConnection = result.numConnection;
-                    scanResult.isAutoJoinCandidate = result.isAutoJoinCandidate;
-                }
-
-                if (config.ephemeral) {
-                    // For an ephemeral Wi-Fi config, the ScanResult should be considered
-                    // untrusted.
-                    scanResult.untrusted = true;
-                }
-
-                if (config.scanResultCache.size() > (maxNumScanCacheEntries + 64)) {
-                    long now_dbg = 0;
-                    if (VVDBG) {
-                        loge(" Will trim config " + config.configKey()
-                                + " size " + config.scanResultCache.size());
-
-                        for (ScanResult r : config.scanResultCache.values()) {
-                            loge("     " + result.BSSID + " " + result.seen);
-                        }
-                        now_dbg = SystemClock.elapsedRealtimeNanos();
-                    }
-                    // Trim the scan result cache to maxNumScanCacheEntries entries max
-                    // Since this operation is expensive, make sure it is not performed
-                    // until the cache has grown significantly above the trim treshold
-                    config.trimScanResultsCache(maxNumScanCacheEntries);
-                    if (VVDBG) {
-                        long diff = SystemClock.elapsedRealtimeNanos() - now_dbg;
-                        loge(" Finished trimming config, time(ns) " + diff);
-                        for (ScanResult r : config.scanResultCache.values()) {
-                            loge("     " + r.BSSID + " " + r.seen);
-                        }
-                    }
-                }
-
-                // Add the scan result to this WifiConfiguration
-                config.scanResultCache.put(scanResult.BSSID, scanResult);
-                // Since we added a scan result to this configuration, re-attempt linking
-                linkConfiguration(config);
+                cacheScanResultForConfig(config, scanDetail, null);
             }
 
             if (VDBG && found) {
@@ -3418,7 +3500,7 @@
                 loge("        got known scan result " +
                         scanResult.BSSID + " key : "
                         + config.configKey() + " num: " +
-                        Integer.toString(config.scanResultCache.size())
+                        Integer.toString(getScanDetailCache(config).size())
                         + " rssi=" + Integer.toString(scanResult.level)
                         + " freq=" + Integer.toString(scanResult.frequency)
                         + status);
@@ -3609,70 +3691,20 @@
             config.preSharedKey = null;
         }
 
-        value = mWifiNative.getNetworkVariable(config.networkId,
-                WifiConfiguration.Protocol.varName);
-        if (!TextUtils.isEmpty(value)) {
-            String vals[] = value.split(" ");
-            for (String val : vals) {
-                int index =
-                    lookupString(val, WifiConfiguration.Protocol.strings);
-                if (0 <= index) {
-                    config.allowedProtocols.set(index);
-                }
-            }
-        }
+        readNetworkBitsetVariable(config.networkId, config.allowedProtocols,
+                WifiConfiguration.Protocol.varName, WifiConfiguration.Protocol.strings);
 
-        value = mWifiNative.getNetworkVariable(config.networkId,
-                WifiConfiguration.KeyMgmt.varName);
-        if (!TextUtils.isEmpty(value)) {
-            String vals[] = value.split(" ");
-            for (String val : vals) {
-                int index =
-                    lookupString(val, WifiConfiguration.KeyMgmt.strings);
-                if (0 <= index) {
-                    config.allowedKeyManagement.set(index);
-                }
-            }
-        }
+        readNetworkBitsetVariable(config.networkId, config.allowedKeyManagement,
+                WifiConfiguration.KeyMgmt.varName, WifiConfiguration.KeyMgmt.strings);
 
-        value = mWifiNative.getNetworkVariable(config.networkId,
-                WifiConfiguration.AuthAlgorithm.varName);
-        if (!TextUtils.isEmpty(value)) {
-            String vals[] = value.split(" ");
-            for (String val : vals) {
-                int index =
-                    lookupString(val, WifiConfiguration.AuthAlgorithm.strings);
-                if (0 <= index) {
-                    config.allowedAuthAlgorithms.set(index);
-                }
-            }
-        }
+        readNetworkBitsetVariable(config.networkId, config.allowedAuthAlgorithms,
+                WifiConfiguration.AuthAlgorithm.varName, WifiConfiguration.AuthAlgorithm.strings);
 
-        value = mWifiNative.getNetworkVariable(config.networkId,
-                WifiConfiguration.PairwiseCipher.varName);
-        if (!TextUtils.isEmpty(value)) {
-            String vals[] = value.split(" ");
-            for (String val : vals) {
-                int index =
-                    lookupString(val, WifiConfiguration.PairwiseCipher.strings);
-                if (0 <= index) {
-                    config.allowedPairwiseCiphers.set(index);
-                }
-            }
-        }
+        readNetworkBitsetVariable(config.networkId, config.allowedPairwiseCiphers,
+                WifiConfiguration.PairwiseCipher.varName, WifiConfiguration.PairwiseCipher.strings);
 
-        value = mWifiNative.getNetworkVariable(config.networkId,
-                WifiConfiguration.GroupCipher.varName);
-        if (!TextUtils.isEmpty(value)) {
-            String vals[] = value.split(" ");
-            for (String val : vals) {
-                int index =
-                    lookupString(val, WifiConfiguration.GroupCipher.strings);
-                if (0 <= index) {
-                    config.allowedGroupCiphers.set(index);
-                }
-            }
-        }
+        readNetworkBitsetVariable(config.networkId, config.allowedGroupCiphers,
+                WifiConfiguration.GroupCipher.varName, WifiConfiguration.GroupCipher.strings);
 
         if (config.enterpriseConfig == null) {
             config.enterpriseConfig = new WifiEnterpriseConfig();
@@ -3746,7 +3778,9 @@
 
     /* return the allowed key management based on a scan result */
 
-    public WifiConfiguration wifiConfigurationFromScanResult(ScanResult result) {
+    public WifiConfiguration wifiConfigurationFromScanResult(ScanDetail scanDetail) {
+
+        ScanResult result = scanDetail.getScanResult();
         WifiConfiguration config = new WifiConfiguration();
 
         config.SSID = "\"" + result.SSID + "\"";
@@ -3771,10 +3805,7 @@
             config.allowedKeyManagement.set(KeyMgmt.IEEE8021X);
         }
 
-        config.scanResultCache = new HashMap<String, ScanResult>();
-        if (config.scanResultCache == null)
-            return null;
-        config.scanResultCache.put(result.BSSID, result);
+        /* getScanDetailCache(config).put(scanDetail); */
 
         return config;
     }
@@ -3790,16 +3821,26 @@
         pw.println("Dump of WifiConfigStore");
         pw.println("mLastPriority " + mLastPriority);
         pw.println("Configured networks");
-        for (WifiConfiguration conf : getConfiguredNetworks()) {
+        for (WifiConfiguration conf : getAllConfiguredNetworks()) {
             pw.println(conf);
         }
         pw.println();
-
+        if (mLostConfigsDbg != null && mLostConfigsDbg.size() > 0) {
+            pw.println("LostConfigs: ");
+            for (String s : mLostConfigsDbg) {
+                pw.println(s);
+            }
+        }
         if (mLocalLog != null) {
             pw.println("WifiConfigStore - Log Begin ----");
             mLocalLog.dump(fd, pw, args);
             pw.println("WifiConfigStore - Log End ----");
         }
+        if (mMOManager.isConfigured()) {
+            pw.println("Begin dump of ANQP Cache");
+            mAnqpCache.dump(pw);
+            pw.println("End dump of ANQP Cache");
+        }
     }
 
     public String getConfigFile() {
@@ -3821,6 +3862,14 @@
         }
     }
 
+    private void logKernelTime() {
+        long kernelTimeMs = System.nanoTime()/(1000*1000);
+        StringBuilder builder = new StringBuilder();
+        builder.append("kernel time = ").append(kernelTimeMs/1000).append(".").append
+                (kernelTimeMs%1000).append("\n");
+        localLog(builder.toString());
+    }
+
     protected void log(String s) {
         Log.d(TAG, s);
     }
@@ -3842,7 +3891,7 @@
         }
 
         WifiConfiguration config;
-        synchronized(mConfiguredNetworks) {
+        synchronized(mConfiguredNetworks) {             // !!! Useless synchronization
             config = mConfiguredNetworks.get(netId);
         }
 
@@ -3928,6 +3977,112 @@
         return false;
     }
 
+    boolean isNetworkConfigured(WifiConfiguration config) {
+        // Check if either we have a network Id or a WifiConfiguration
+        // matching the one we are trying to add.
+
+        if(config.networkId != INVALID_NETWORK_ID) {
+            return (mConfiguredNetworks.get(config.networkId) != null);
+        }
+
+        return (mConfiguredNetworks.getByConfigKey(config.configKey()) != null);
+    }
+
+    /**
+     * Checks if uid has access to modify the configuration corresponding to networkId.
+     *
+     * Factors involved in modifiability of a config are as follows.
+     *    If uid is a Device Owner app then it has full control over the device, including WiFi
+     * configs.
+     *    If the modification is only for administrative annotation (e.g. when connecting) or the
+     * config is not lockdown eligible (currently that means any config not last updated by the DO)
+     * then the creator of config or an app holding OVERRIDE_CONFIG_WIFI can modify the config.
+     *    If the config is lockdown eligible and the modification is substantial (not annotation)
+     * then the requirement to be able to modify the config by the uid is as follows:
+     *    a) the uid has to hold OVERRIDE_CONFIG_WIFI and
+     *    b) the lockdown feature should be disabled.
+     */
+    boolean canModifyNetwork(int uid, int networkId, boolean onlyAnnotate) {
+        WifiConfiguration config = mConfiguredNetworks.get(networkId);
+
+        if (config == null) {
+            loge("canModifyNetwork: cannot find config networkId " + networkId);
+            return false;
+        }
+
+        final DevicePolicyManagerInternal dpmi = LocalServices.getService(
+                DevicePolicyManagerInternal.class);
+
+        final boolean isUidDeviceOwner = dpmi != null && dpmi.isActiveAdminWithPolicy(uid,
+                DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+
+        if (isUidDeviceOwner) {
+            // Device Owner has full control over the device, including WiFi Configs
+            return true;
+        }
+
+        final boolean isCreator = (config.creatorUid == uid);
+
+        if (onlyAnnotate) {
+            return isCreator || checkConfigOverridePermission(uid);
+        }
+
+        // Check if device has DPM capability. If it has and dpmi is still null, then we
+        // treat this case with suspicion and bail out.
+        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN)
+                && dpmi == null) {
+            return false;
+        }
+
+        // WiFi config lockdown related logic. At this point we know uid NOT to be a Device Owner.
+
+        final boolean isConfigEligibleForLockdown = dpmi != null && dpmi.isActiveAdminWithPolicy(
+                config.creatorUid, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+        if (!isConfigEligibleForLockdown) {
+            return isCreator || checkConfigOverridePermission(uid);
+        }
+
+        final ContentResolver resolver = mContext.getContentResolver();
+        final boolean isLockdownFeatureEnabled = Settings.Global.getInt(resolver,
+                Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) != 0;
+        return !isLockdownFeatureEnabled && checkConfigOverridePermission(uid);
+    }
+
+    /**
+     * Checks if uid has access to modify config.
+     */
+    boolean canModifyNetwork(int uid, WifiConfiguration config, boolean onlyAnnotate) {
+        if (config == null) {
+            loge("canModifyNetowrk recieved null configuration");
+            return false;
+        }
+
+        // Resolve the correct network id.
+        int netid;
+        if (config.networkId != INVALID_NETWORK_ID){
+            netid = config.networkId;
+        } else {
+            WifiConfiguration test = mConfiguredNetworks.getByConfigKey(config.configKey());
+            if (test == null) {
+                return false;
+            } else {
+                netid = test.networkId;
+            }
+        }
+
+        return canModifyNetwork(uid, netid, onlyAnnotate);
+    }
+
+    boolean checkConfigOverridePermission(int uid) {
+        try {
+            return (AppGlobals.getPackageManager().checkUidPermission(
+                    android.Manifest.permission.OVERRIDE_WIFI_CONFIG, uid)
+                    == PackageManager.PERMISSION_GRANTED);
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
     /** called when CS ask WiFistateMachine to disconnect the current network
      * because the score is bad.
      */
@@ -3962,16 +4117,17 @@
 
         // Look for the BSSID in our config store
         for (WifiConfiguration config : mConfiguredNetworks.values()) {
-            if (config.scanResultCache != null) {
-                for (ScanResult result: config.scanResultCache.values()) {
-                    if (result.BSSID.equals(BSSID)) {
+            if (getScanDetailCache(config) != null) {
+                for (ScanDetail scanDetail : getScanDetailCache(config).values()) {
+                    if (scanDetail.getBSSIDString().equals(BSSID)) {
                         if (enable) {
-                            result.setAutoJoinStatus(ScanResult.ENABLED);
+                            scanDetail.getScanResult().setAutoJoinStatus(ScanResult.ENABLED);
                         } else {
                             // Black list the BSSID we were trying to join
                             // so as the Roam state machine
                             // doesn't pick it up over and over
-                            result.setAutoJoinStatus(ScanResult.AUTO_ROAM_DISABLED);
+                            scanDetail.getScanResult().setAutoJoinStatus(
+                                    ScanResult.AUTO_ROAM_DISABLED);
                             found = true;
                         }
                     }
@@ -3987,20 +4143,48 @@
                 DEFAULT_MAX_DHCP_RETRIES);
     }
 
+    void clearBssidBlacklist() {
+        if (!mWifiStateMachine.useHalBasedAutoJoinOffload()) {
+            if(DBG) {
+                Log.d(TAG, "No blacklist allowed without epno enabled");
+            }
+            return;
+        }
+        mBssidBlacklist = new HashSet<String>();
+        mWifiNative.clearBlacklist();
+        mWifiNative.setBssidBlacklist(null);
+    }
+
+    void blackListBssid(String BSSID) {
+        if (!mWifiStateMachine.useHalBasedAutoJoinOffload()) {
+            if(DBG) {
+                Log.d(TAG, "No blacklist allowed without epno enabled");
+            }
+            return;
+        }
+        if (BSSID == null)
+            return;
+        mBssidBlacklist.add(BSSID);
+        // Blacklist at wpa_supplicant
+        mWifiNative.addToBlacklist(BSSID);
+        // Blacklist at firmware
+        String list[] = new String[mBssidBlacklist.size()];
+        int count = 0;
+        for (String bssid : mBssidBlacklist) {
+            list[count++] = bssid;
+        }
+        mWifiNative.setBssidBlacklist(list);
+    }
+
     void handleSSIDStateChange(int netId, boolean enabled, String message, String BSSID) {
         WifiConfiguration config = mConfiguredNetworks.get(netId);
         if (config != null) {
             if (enabled) {
-                loge("SSID re-enabled for  " + config.configKey() +
+                loge("Ignoring SSID re-enabled from supplicant:  " + config.configKey() +
                         " had autoJoinStatus=" + Integer.toString(config.autoJoinStatus)
                         + " self added " + config.selfAdded + " ephemeral " + config.ephemeral);
-                //TODO: http://b/16381983 Fix Wifi Network Blacklisting
-                //TODO: really I don't know if re-enabling is right but we
-                //TODO: should err on the side of trying to connect
-                //TODO: even if the attempt will fail
-                if (config.autoJoinStatus == WifiConfiguration.AUTO_JOIN_DISABLED_ON_AUTH_FAILURE) {
-                    config.setAutoJoinStatus(WifiConfiguration.AUTO_JOIN_ENABLED);
-                }
+                //We should not re-enable the BSSID based on Supplicant reanable.
+                // Framework will re-enable it after its own blacklist timer expires
             } else {
                 loge("SSID temp disabled for  " + config.configKey() +
                         " had autoJoinStatus=" + Integer.toString(config.autoJoinStatus)
@@ -4059,8 +4243,8 @@
                             // Also blacklist the BSSId if we find it
                             ScanResult result = null;
                             String bssidDbg = "";
-                            if (config.scanResultCache != null && BSSID != null) {
-                                result = config.scanResultCache.get(BSSID);
+                            if (getScanDetailCache(config) != null && BSSID != null) {
+                                result = getScanDetailCache(config).get(BSSID);
                             }
                             if (result != null) {
                                 result.numIpConfigFailures ++;
@@ -4130,7 +4314,7 @@
             ret = putCertInKeyStore(userCertName, config.getClientCertificate());
             if (ret == false) {
                 // Remove private key installed
-                mKeyStore.delKey(privKeyName, Process.WIFI_UID);
+                mKeyStore.delete(privKeyName, Process.WIFI_UID);
                 return ret;
             }
         }
@@ -4140,7 +4324,7 @@
             if (ret == false) {
                 if (config.getClientCertificate() != null) {
                     // Remove client key+cert
-                    mKeyStore.delKey(privKeyName, Process.WIFI_UID);
+                    mKeyStore.delete(privKeyName, Process.WIFI_UID);
                     mKeyStore.delete(userCertName, Process.WIFI_UID);
                 }
                 return ret;
@@ -4179,7 +4363,7 @@
         // a valid client certificate is configured
         if (!TextUtils.isEmpty(client)) {
             if (DBG) Log.d(TAG, "removing client private key and user cert");
-            mKeyStore.delKey(Credentials.USER_PRIVATE_KEY + client, Process.WIFI_UID);
+            mKeyStore.delete(Credentials.USER_PRIVATE_KEY + client, Process.WIFI_UID);
             mKeyStore.delete(Credentials.USER_CERTIFICATE + client, Process.WIFI_UID);
         }
 
@@ -4269,4 +4453,18 @@
         }
     }
 
+    private void readNetworkBitsetVariable(int netId, BitSet variable, String varName,
+            String[] strings) {
+        String value = mWifiNative.getNetworkVariable(netId, varName);
+        if (!TextUtils.isEmpty(value)) {
+            variable.clear();
+            String vals[] = value.split(" ");
+            for (String val : vals) {
+                int index = lookupString(val, strings);
+                if (0 <= index) {
+                    variable.set(index);
+                }
+            }
+        }
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiController.java b/service/java/com/android/server/wifi/WifiController.java
index 8f36cb7..3b41e3b 100644
--- a/service/java/com/android/server/wifi/WifiController.java
+++ b/service/java/com/android/server/wifi/WifiController.java
@@ -114,6 +114,7 @@
     static final int CMD_SET_AP                     = BASE + 10;
     static final int CMD_DEFERRED_TOGGLE            = BASE + 11;
     static final int CMD_USER_PRESENT               = BASE + 12;
+    static final int CMD_AP_START_FAILURE           = BASE + 13;
 
     private DefaultState mDefaultState = new DefaultState();
     private StaEnabledState mStaEnabledState = new StaEnabledState();
@@ -172,6 +173,7 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(ACTION_DEVICE_IDLE);
         filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+        filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
         mContext.registerReceiver(
                 new BroadcastReceiver() {
                     @Override
@@ -182,6 +184,14 @@
                         } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
                             mNetworkInfo = (NetworkInfo) intent.getParcelableExtra(
                                     WifiManager.EXTRA_NETWORK_INFO);
+                        } else if (action.equals(WifiManager.WIFI_AP_STATE_CHANGED_ACTION)) {
+                            int state = intent.getIntExtra(
+                                    WifiManager.EXTRA_WIFI_AP_STATE,
+                                    WifiManager.WIFI_AP_STATE_FAILED);
+                            if(state == WifiManager.WIFI_AP_STATE_FAILED) {
+                                loge(TAG + "SoftAP start failed");
+                                sendMessage(CMD_AP_START_FAILURE);
+                            }
                         }
                     }
                 },
@@ -371,6 +381,7 @@
                 case CMD_WIFI_TOGGLED:
                 case CMD_AIRPLANE_TOGGLED:
                 case CMD_EMERGENCY_MODE_CHANGED:
+                case CMD_AP_START_FAILURE:
                     break;
                 case CMD_USER_PRESENT:
                     mFirstUserSignOnSeen = true;
@@ -398,6 +409,7 @@
             mDisabledTimestamp = SystemClock.elapsedRealtime();
             mDeferredEnableSerialNumber++;
             mHaveDeferredEnable = false;
+            mWifiStateMachine.clearANQPCache();
         }
         @Override
         public boolean processMessage(Message msg) {
@@ -519,6 +531,7 @@
             mDisabledTimestamp = SystemClock.elapsedRealtime();
             mDeferredEnableSerialNumber++;
             mHaveDeferredEnable = false;
+            mWifiStateMachine.clearANQPCache();
         }
 
         @Override
@@ -607,6 +620,12 @@
                         transitionTo(mApStaDisabledState);
                     }
                     break;
+                case CMD_AP_START_FAILURE:
+                    if(!mSettingsStore.isScanAlwaysAvailable()) {
+                        transitionTo(mApStaDisabledState);
+                    } else {
+                        transitionTo(mStaDisabledWithScanState);
+                    }
                 default:
                     return NOT_HANDLED;
             }
diff --git a/service/java/com/android/server/wifi/WifiLogger.java b/service/java/com/android/server/wifi/WifiLogger.java
new file mode 100644
index 0000000..8add779
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiLogger.java
@@ -0,0 +1,450 @@
+/*
+ * Copyright (C) 2010 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 com.android.server.wifi;
+
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.util.Protocol;
+
+import android.support.v4.util.CircularArray;
+import android.util.Base64;
+import android.util.LocalLog;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.zip.Deflater;
+
+/**
+ * Tracks various logs for framework
+ */
+class WifiLogger  {
+
+    private static final String TAG = "WifiLogger";
+    private static final boolean DBG = false;
+
+    /** log level flags; keep these consistent with wifi_logger.h */
+
+    /** No logs whatsoever */
+    public static final int VERBOSE_NO_LOG = 0;
+    /** No logs whatsoever */
+    public static final int VERBOSE_NORMAL_LOG = 1;
+    /** Be careful since this one can affect performance and power */
+    public static final int VERBOSE_LOG_WITH_WAKEUP  = 2;
+    /** Be careful since this one can affect performance and power and memory */
+    public static final int VERBOSE_DETAILED_LOG_WITH_WAKEUP  = 3;
+
+    /** ring buffer flags; keep these consistent with wifi_logger.h */
+    public static final int RING_BUFFER_FLAG_HAS_BINARY_ENTRIES     = 0x00000001;
+    public static final int RING_BUFFER_FLAG_HAS_ASCII_ENTRIES      = 0x00000002;
+    public static final int RING_BUFFER_FLAG_HAS_PER_PACKET_ENTRIES = 0x00000004;
+
+    /** various reason codes */
+    public static final int REPORT_REASON_NONE                      = 0;
+    public static final int REPORT_REASON_ASSOC_FAILURE             = 1;
+    public static final int REPORT_REASON_AUTH_FAILURE              = 2;
+    public static final int REPORT_REASON_AUTOROAM_FAILURE          = 3;
+    public static final int REPORT_REASON_DHCP_FAILURE              = 4;
+    public static final int REPORT_REASON_UNEXPECTED_DISCONNECT     = 5;
+    public static final int REPORT_REASON_SCAN_FAILURE              = 6;
+    public static final int REPORT_REASON_USER_ACTION               = 7;
+
+    /** number of ring buffer entries to cache */
+    public static final int MAX_RING_BUFFERS                        = 10;
+
+    /** number of bug reports to hold */
+    public static final int MAX_BUG_REPORTS                         = 4;
+
+    /** number of alerts to hold */
+    public static final int MAX_ALERT_REPORTS                       = 1;
+
+    /** minimum wakeup interval for each of the log levels */
+    private static final int MinWakeupIntervals[] = new int[] { 0, 3600, 60, 10 };
+    /** minimum buffer size for each of the log levels */
+    private static final int MinBufferSizes[] = new int[] { 0, 16384, 16384, 65536 };
+
+    private int mLogLevel = VERBOSE_NO_LOG;
+    private String mFirmwareVersion;
+    private String mDriverVersion;
+    private int mSupportedFeatureSet;
+    private WifiNative.RingBufferStatus[] mRingBuffers;
+    private WifiNative.RingBufferStatus mPerPacketRingBuffer;
+    private WifiStateMachine mWifiStateMachine;
+
+    public WifiLogger(WifiStateMachine wifiStateMachine) {
+        mWifiStateMachine = wifiStateMachine;
+    }
+
+    public synchronized void startLogging(boolean verboseEnabled) {
+        mFirmwareVersion = WifiNative.getFirmwareVersion();
+        mDriverVersion = WifiNative.getDriverVersion();
+        mSupportedFeatureSet = WifiNative.getSupportedLoggerFeatureSet();
+
+        if (mLogLevel == VERBOSE_NO_LOG)
+            WifiNative.setLoggingEventHandler(mHandler);
+
+        if (verboseEnabled) {
+            mLogLevel = VERBOSE_LOG_WITH_WAKEUP;
+        } else {
+            mLogLevel = VERBOSE_NORMAL_LOG;
+        }
+        if (mRingBuffers == null) {
+            if (fetchRingBuffers()) {
+                startLoggingAllExceptPerPacketBuffers();
+            }
+        }
+    }
+
+    public synchronized void startPacketLog() {
+        if (mPerPacketRingBuffer != null) {
+            startLoggingRingBuffer(mPerPacketRingBuffer);
+        } else {
+            if (DBG) Log.d(TAG, "There is no per packet ring buffer");
+        }
+    }
+
+    public synchronized void stopPacketLog() {
+        if (mPerPacketRingBuffer != null) {
+            stopLoggingRingBuffer(mPerPacketRingBuffer);
+        } else {
+            if (DBG) Log.d(TAG, "There is no per packet ring buffer");
+        }
+    }
+
+    public synchronized void stopLogging() {
+        if (mLogLevel != VERBOSE_NO_LOG) {
+            //resetLogHandler only can be used when you terminate all logging since all handler will
+            //be removed. This also stop alert logging
+            if(!WifiNative.resetLogHandler()) {
+                Log.e(TAG, "Fail to reset log handler");
+            } else {
+                if (DBG) Log.d(TAG,"Reset log handler");
+            }
+            stopLoggingAllBuffers();
+            mRingBuffers = null;
+            mLogLevel = VERBOSE_NO_LOG;
+        }
+    }
+
+    public synchronized void captureBugReportData(int reason) {
+        BugReport report = captureBugreport(reason, true);
+        mLastBugReports.addLast(report);
+    }
+
+    public synchronized void captureAlertData(int errorCode, byte[] alertData) {
+        BugReport report = captureBugreport(errorCode, /* captureFWDump = */ true);
+        report.alertData = alertData;
+        mLastAlerts.addLast(report);
+    }
+
+    public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("Chipset information :-----------------------------------------------");
+        pw.println("FW Version is: " + mFirmwareVersion);
+        pw.println("Driver Version is: " + mDriverVersion);
+        pw.println("Supported Feature set: " + mSupportedFeatureSet);
+
+        for (int i = 0; i < mLastAlerts.size(); i++) {
+            pw.println("--------------------------------------------------------------------");
+            pw.println("Alert dump " + i);
+            pw.print(mLastAlerts.get(i));
+            pw.println("--------------------------------------------------------------------");
+        }
+
+        for (int i = 0; i < mLastBugReports.size(); i++) {
+            pw.println("--------------------------------------------------------------------");
+            pw.println("Bug dump " + i);
+            pw.print(mLastBugReports.get(i));
+            pw.println("--------------------------------------------------------------------");
+        }
+
+        pw.println("--------------------------------------------------------------------");
+    }
+
+    /* private methods and data */
+    private static class BugReport {
+        long systemTimeMs;
+        long kernelTimeNanos;
+        int errorCode;
+        HashMap<String, byte[][]> ringBuffers = new HashMap();
+        byte[] fwMemoryDump;
+        byte[] alertData;
+
+        public String toString() {
+            StringBuilder builder = new StringBuilder();
+
+            Calendar c = Calendar.getInstance();
+            c.setTimeInMillis(systemTimeMs);
+            builder.append("system time = ").append(
+                    String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c)).append("\n");
+
+            long kernelTimeMs = kernelTimeNanos/(1000*1000);
+            builder.append("kernel time = ").append(kernelTimeMs/1000).append(".").append
+                    (kernelTimeMs%1000).append("\n");
+
+            if (alertData == null)
+                builder.append("reason = ").append(errorCode).append("\n");
+            else {
+                builder.append("errorCode = ").append(errorCode);
+                builder.append("data \n");
+                builder.append(compressToBase64(alertData)).append("\n");
+            }
+
+            for (HashMap.Entry<String, byte[][]> e : ringBuffers.entrySet()) {
+                String ringName = e.getKey();
+                byte[][] buffers = e.getValue();
+                builder.append("ring-buffer = ").append(ringName).append("\n");
+
+                int size = 0;
+                for (int i = 0; i < buffers.length; i++) {
+                    size += buffers[i].length;
+                }
+
+                byte[] buffer = new byte[size];
+                int index = 0;
+                for (int i = 0; i < buffers.length; i++) {
+                    System.arraycopy(buffers[i], 0, buffer, index, buffers[i].length);
+                    index += buffers[i].length;
+                }
+
+                builder.append(compressToBase64(buffer));
+                builder.append("\n");
+            }
+
+            if (fwMemoryDump != null) {
+                builder.append("FW Memory dump \n");
+                builder.append(compressToBase64(fwMemoryDump));
+            }
+
+            return builder.toString();
+        }
+    }
+
+    static class LimitedCircularArray<E> {
+        private CircularArray<E> mArray;
+        private int mMax;
+        LimitedCircularArray(int max) {
+            mArray = new CircularArray<E>();
+            mMax = max;
+        }
+
+        public final void addLast(E e) {
+            if (mArray.size() >= mMax)
+                mArray.popFirst();
+            mArray.addLast(e);
+        }
+
+        public final int size() {
+            return mArray.size();
+        }
+
+        public final E get(int i) {
+            return mArray.get(i);
+        }
+    }
+
+    private final LimitedCircularArray<BugReport> mLastAlerts =
+            new LimitedCircularArray<BugReport>(MAX_ALERT_REPORTS);
+    private final LimitedCircularArray<BugReport> mLastBugReports =
+            new LimitedCircularArray<BugReport>(MAX_BUG_REPORTS);
+    private final HashMap<String, LimitedCircularArray<byte[]>> mRingBufferData = new HashMap();
+
+    private final WifiNative.WifiLoggerEventHandler mHandler =
+            new WifiNative.WifiLoggerEventHandler() {
+        @Override
+        public void onRingBufferData(WifiNative.RingBufferStatus status, byte[] buffer) {
+            WifiLogger.this.onRingBufferData(status, buffer);
+        }
+
+        @Override
+        public void onWifiAlert(int errorCode, byte[] buffer) {
+            WifiLogger.this.onWifiAlert(errorCode, buffer);
+        }
+    };
+
+    synchronized void onRingBufferData(WifiNative.RingBufferStatus status, byte[] buffer) {
+        LimitedCircularArray<byte[]> ring = mRingBufferData.get(status.name);
+        if (ring != null) {
+            ring.addLast(buffer);
+        }
+    }
+
+    synchronized void onWifiAlert(int errorCode, byte[] buffer) {
+        if (mWifiStateMachine != null) {
+            mWifiStateMachine.sendMessage(
+                    WifiStateMachine.CMD_FIRMWARE_ALERT, errorCode, 0, buffer);
+        }
+    }
+
+    private boolean fetchRingBuffers() {
+        if (mRingBuffers != null) return true;
+
+        mRingBuffers = WifiNative.getRingBufferStatus();
+        if (mRingBuffers != null) {
+            for (WifiNative.RingBufferStatus buffer : mRingBuffers) {
+                if (DBG) Log.d(TAG, "RingBufferStatus is: \n" + buffer.name);
+                if (mRingBufferData.containsKey(buffer.name) == false) {
+                    mRingBufferData.put(buffer.name,
+                            new LimitedCircularArray<byte[]>(MAX_RING_BUFFERS));
+                }
+                if ((buffer.flag & RING_BUFFER_FLAG_HAS_PER_PACKET_ENTRIES) != 0) {
+                    mPerPacketRingBuffer = buffer;
+                }
+            }
+        } else {
+            Log.e(TAG, "no ring buffers found");
+        }
+
+        return mRingBuffers != null;
+    }
+
+    private boolean startLoggingAllExceptPerPacketBuffers() {
+
+        if (mRingBuffers == null) {
+            if (DBG) Log.d(TAG, "No ring buffers to log anything!");
+            return false;
+        }
+
+        for (WifiNative.RingBufferStatus buffer : mRingBuffers){
+
+            if ((buffer.flag & RING_BUFFER_FLAG_HAS_PER_PACKET_ENTRIES) != 0) {
+                /* skip per-packet-buffer */
+                if (DBG) Log.d(TAG, "skipped per packet logging ring " + buffer.name);
+                continue;
+            }
+
+            startLoggingRingBuffer(buffer);
+        }
+
+        return true;
+    }
+
+    private boolean startLoggingRingBuffer(WifiNative.RingBufferStatus buffer) {
+
+        int minInterval = MinWakeupIntervals[mLogLevel];
+        int minDataSize = MinBufferSizes[mLogLevel];
+
+        if (WifiNative.startLoggingRingBuffer(
+                mLogLevel, 0, minInterval, minDataSize, buffer.name) == false) {
+            if (DBG) Log.e(TAG, "Could not start logging ring " + buffer.name);
+            return false;
+        }
+
+        return true;
+    }
+
+    private boolean stopLoggingRingBuffer(WifiNative.RingBufferStatus buffer) {
+        if (WifiNative.startLoggingRingBuffer(0, 0, 0, 0, buffer.name) == false) {
+            if (DBG) Log.e(TAG, "Could not stop logging ring " + buffer.name);
+        }
+        return true;
+    }
+
+    private boolean stopLoggingAllBuffers() {
+        if (mRingBuffers != null) {
+            for (WifiNative.RingBufferStatus buffer : mRingBuffers) {
+                stopLoggingRingBuffer(buffer);
+            }
+        }
+        return true;
+    }
+
+    private boolean getAllRingBufferData() {
+        if (mRingBuffers == null) {
+            Log.e(TAG, "Not ring buffers available to collect data!");
+            return false;
+        }
+
+        for (WifiNative.RingBufferStatus element : mRingBuffers){
+            boolean result = WifiNative.getRingBufferData(element.name);
+            if (!result) {
+                Log.e(TAG, "Fail to get ring buffer data of: " + element.name);
+                return false;
+            }
+        }
+
+        Log.d(TAG, "getAllRingBufferData Successfully!");
+        return true;
+    }
+
+    private BugReport captureBugreport(int errorCode, boolean captureFWDump) {
+        BugReport report = new BugReport();
+        report.errorCode = errorCode;
+        report.systemTimeMs = System.currentTimeMillis();
+        report.kernelTimeNanos = System.nanoTime();
+
+        if (mRingBuffers != null) {
+            for (WifiNative.RingBufferStatus buffer : mRingBuffers) {
+                /* this will push data in mRingBuffers */
+                WifiNative.getRingBufferData(buffer.name);
+                LimitedCircularArray<byte[]> data = mRingBufferData.get(buffer.name);
+                byte[][] buffers = new byte[data.size()][];
+                for (int i = 0; i < data.size(); i++) {
+                    buffers[i] = data.get(i).clone();
+                }
+                report.ringBuffers.put(buffer.name, buffers);
+            }
+        }
+
+        if (captureFWDump) {
+            report.fwMemoryDump = WifiNative.getFwMemoryDump();
+        }
+        return report;
+    }
+
+    private static String compressToBase64(byte[] input) {
+        String result;
+        //compress
+        Deflater compressor = new Deflater();
+        compressor.setLevel(Deflater.BEST_COMPRESSION);
+        compressor.setInput(input);
+        compressor.finish();
+        ByteArrayOutputStream bos = new ByteArrayOutputStream(input.length);
+        final byte[] buf = new byte[1024];
+
+        while (!compressor.finished()) {
+            int count = compressor.deflate(buf);
+            bos.write(buf, 0, count);
+        }
+
+        try {
+            compressor.end();
+            bos.close();
+        } catch (IOException e) {
+            Log.e(TAG, "ByteArrayOutputStream close error");
+            result =  android.util.Base64.encodeToString(input, Base64.DEFAULT);
+            return result;
+        }
+
+        byte[] compressed = bos.toByteArray();
+        if (DBG) {
+            Log.d(TAG," length is:" + (compressed == null? "0" : compressed.length));
+        }
+
+        //encode
+        result = android.util.Base64.encodeToString(
+                compressed.length < input.length ? compressed : input , Base64.DEFAULT);
+
+        if (DBG) {
+            Log.d(TAG, "FwMemoryDump length is :" + result.length());
+        }
+
+        return result;
+    }
+}
diff --git a/service/java/com/android/server/wifi/WifiMonitor.java b/service/java/com/android/server/wifi/WifiMonitor.java
index b807a92..9758d57 100644
--- a/service/java/com/android/server/wifi/WifiMonitor.java
+++ b/service/java/com/android/server/wifi/WifiMonitor.java
@@ -27,10 +27,11 @@
 import android.net.wifi.p2p.WifiP2pProvDiscEvent;
 import android.net.wifi.p2p.nsd.WifiP2pServiceResponse;
 import android.os.Message;
-import android.os.SystemClock;
 import android.text.TextUtils;
+import android.util.LocalLog;
 import android.util.Log;
 
+import com.android.server.wifi.hotspot2.Utils;
 import com.android.server.wifi.p2p.WifiP2pServiceImpl.P2pStatus;
 
 import com.android.internal.util.Protocol;
@@ -66,9 +67,10 @@
     private static final int ASSOC_REJECT = 9;
     private static final int SSID_TEMP_DISABLE = 10;
     private static final int SSID_REENABLE = 11;
-    private static final int BSS_ADDED = 12;
-    private static final int BSS_REMOVED = 13;
+    private static final int BSS_ADDED    = 12;
+    private static final int BSS_REMOVED  = 13;
     private static final int UNKNOWN      = 14;
+    private static final int SCAN_FAILED  = 15;
 
     /** All events coming from the supplicant start with this prefix */
     private static final String EVENT_PREFIX_STR = "CTRL-EVENT-";
@@ -158,6 +160,13 @@
 
     /**
      * <pre>
+     * CTRL-EVENT-SCAN-FAILED ret=code[ retry=1]
+     * </pre>
+     */
+    private static final String SCAN_FAILED_STR =  "SCAN-FAILED";
+
+    /**
+     * <pre>
      * CTRL-EVENT-LINK-SPEED x Mb/s
      * </pre>
      * {@code x} is the link speed in Mb/sec.
@@ -189,6 +198,10 @@
      */
     private static final String EAP_AUTH_FAILURE_STR = "EAP authentication failed";
 
+    /* EAP authentication timeout events */
+    private static final String AUTH_EVENT_PREFIX_STR = "Authentication with";
+    private static final String AUTH_TIMEOUT_STR = "timed out.";
+
     /**
      * This indicates an assoc reject event
      */
@@ -263,7 +276,7 @@
             Pattern.compile("Associated with ((?:[0-9a-f]{2}:){5}[0-9a-f]{2}).*");
 
     /**
-     * Regex pattern for extracting SSIDs from request identity string.
+     * Regex pattern for extracting an external GSM sim authentication request from a string.
      * Matches a strings like the following:<pre>
      * CTRL-REQ-SIM-<network id>:GSM-AUTH:<RAND1>:<RAND2>[:<RAND3>] needed for SSID <SSID>
      * This pattern should find
@@ -277,6 +290,19 @@
             Pattern.compile("SIM-([0-9]*):GSM-AUTH((:[0-9a-f]+)+) needed for SSID (.+)");
 
     /**
+     * Regex pattern for extracting an external 3G sim authentication request from a string.
+     * Matches a strings like the following:<pre>
+     * CTRL-REQ-SIM-<network id>:UMTS-AUTH:<RAND>:<AUTN> needed for SSID <SSID>
+     * This pattern should find
+     *    1 - id
+     *    2 - Rand
+     *    3 - Autn
+     *    4 - SSID
+     */
+    private static Pattern mRequestUmtsAuthPattern =
+            Pattern.compile("SIM-([0-9]*):UMTS-AUTH:([0-9a-f]+):([0-9a-f]+) needed for SSID (.+)");
+
+    /**
      * Regex pattern for extracting SSIDs from request identity string.
      * Matches a strings like the following:<pre>
      * CTRL-REQ-IDENTITY-xx:Identity needed for SSID XXXX</pre>
@@ -396,6 +422,7 @@
     private static final String AP_STA_CONNECTED_STR = "AP-STA-CONNECTED";
     /* AP-STA-DISCONNECTED 42:fc:89:a8:96:09 */
     private static final String AP_STA_DISCONNECTED_STR = "AP-STA-DISCONNECTED";
+    private static final String ANQP_DONE_STR = "ANQP-QUERY-DONE";
 
     /* Supplicant events reported to a state machine */
     private static final int BASE = Protocol.BASE_WIFI_MONITOR;
@@ -436,6 +463,8 @@
     /* Request SIM Auth */
     public static final int SUP_REQUEST_SIM_AUTH                 = BASE + 16;
 
+    public static final int SCAN_FAILED_EVENT                    = BASE + 17;
+
     /* P2P events */
     public static final int P2P_DEVICE_FOUND_EVENT               = BASE + 21;
     public static final int P2P_DEVICE_LOST_EVENT                = BASE + 22;
@@ -462,6 +491,7 @@
 
     /* Indicates assoc reject event */
     public static final int ASSOCIATION_REJECTION_EVENT          = BASE + 43;
+    public static final int ANQP_DONE_EVENT                      = BASE + 44;
 
     /* hotspot 2.0 ANQP events */
     public static final int GAS_QUERY_START_EVENT                = BASE + 51;
@@ -569,8 +599,8 @@
                     if (mWifiNative.connectToSupplicant()) {
                         m.mMonitoring = true;
                         m.mStateMachine.sendMessage(SUP_CONNECTION_EVENT);
-                        new MonitorThread(mWifiNative, this).start();
                         mConnected = true;
+                        new MonitorThread(mWifiNative, this).start();
                         break;
                     }
                     if (connectTries++ < 5) {
@@ -579,7 +609,6 @@
                         } catch (InterruptedException ignore) {
                         }
                     } else {
-                        mIfaceMap.remove(iface);
                         m.mStateMachine.sendMessage(SUP_DISCONNECTION_EVENT);
                         Log.e(TAG, "startMonitoring(" + iface + ") failed!");
                         break;
@@ -633,6 +662,7 @@
 
         private synchronized boolean dispatchEvent(String eventStr) {
             String iface;
+            // IFNAME=wlan0 ANQP-QUERY-DONE addr=18:cf:5e:26:a4:88 result=SUCCESS
             if (eventStr.startsWith("IFNAME=")) {
                 int space = eventStr.indexOf(' ');
                 if (space != -1) {
@@ -705,6 +735,7 @@
     private static class MonitorThread extends Thread {
         private final WifiNative mWifiNative;
         private final WifiMonitorSingleton mWifiMonitorSingleton;
+        private final LocalLog mLocalLog = WifiNative.getLocalLog();
 
         public MonitorThread(WifiNative wifiNative, WifiMonitorSingleton wifiMonitorSingleton) {
             super("WifiMonitor");
@@ -713,13 +744,23 @@
         }
 
         public void run() {
+            if (DBG) {
+                Log.d(TAG, "MonitorThread start with mConnected=" +
+                     mWifiMonitorSingleton.mConnected);
+            }
             //noinspection InfiniteLoopStatement
             for (;;) {
+                if (!mWifiMonitorSingleton.mConnected) {
+                    if (DBG) Log.d(TAG, "MonitorThread exit because mConnected is false");
+                    break;
+                }
                 String eventStr = mWifiNative.waitForEvent();
 
-                // Skip logging the common but mostly uninteresting scan-results event
-                if (DBG && eventStr.indexOf(SCAN_RESULTS_STR) == -1) {
-                    Log.d(TAG, "Event [" + eventStr + "]");
+                // Skip logging the common but mostly uninteresting events
+                if (eventStr.indexOf(BSS_ADDED_STR) == -1
+                        && eventStr.indexOf(BSS_REMOVED_STR) == -1) {
+                    if (DBG) Log.d(TAG, "Event [" + eventStr + "]");
+                    mLocalLog.log("Event [" + eventStr + "]");
                 }
 
                 if (mWifiMonitorSingleton.dispatchEvent(eventStr)) {
@@ -764,13 +805,20 @@
                 handleP2pEvents(eventStr);
             } else if (eventStr.startsWith(HOST_AP_EVENT_PREFIX_STR)) {
                 handleHostApEvents(eventStr);
-            } else if (eventStr.startsWith(GAS_QUERY_PREFIX_STR)) {
+            } else if (eventStr.startsWith(ANQP_DONE_STR)) {
+                try {
+                    handleAnqpResult(eventStr);
+                }
+                catch (IllegalArgumentException iae) {
+                    Log.e(TAG, "Bad ANQP event string: '" + eventStr + "': " + iae);
+                }
+            } else if (eventStr.startsWith(GAS_QUERY_PREFIX_STR)) {        // !!! clean >>End
                 handleGasQueryEvents(eventStr);
             } else if (eventStr.startsWith(RX_HS20_ANQP_ICON_STR)) {
                 if (mStateMachine2 != null)
                     mStateMachine2.sendMessage(RX_HS20_ANQP_ICON_EVENT,
                             eventStr.substring(RX_HS20_ANQP_ICON_STR_LEN + 1));
-            } else if (eventStr.startsWith(HS20_PREFIX_STR)) {
+            } else if (eventStr.startsWith(HS20_PREFIX_STR)) {                  // !!! <<End
                 handleHs20Events(eventStr);
             } else if (eventStr.startsWith(REQUEST_PREFIX_STR)) {
                 handleRequests(eventStr);
@@ -778,6 +826,9 @@
                 handleTargetBSSIDEvent(eventStr);
             } else if (eventStr.startsWith(ASSOCIATED_WITH_STR)) {
                 handleAssociatedBSSIDEvent(eventStr);
+            } else if (eventStr.startsWith(AUTH_EVENT_PREFIX_STR) &&
+                    eventStr.endsWith(AUTH_TIMEOUT_STR)) {
+                mStateMachine.sendMessage(AUTHENTICATION_FAILURE_EVENT);
             } else {
                 if (DBG) Log.w(TAG, "couldn't identify event type - " + eventStr);
             }
@@ -806,6 +857,8 @@
             event = STATE_CHANGE;
         else if (eventName.equals(SCAN_RESULTS_STR))
             event = SCAN_RESULTS;
+        else if (eventName.equals(SCAN_FAILED_STR))
+            event = SCAN_FAILED;
         else if (eventName.equals(LINK_SPEED_STR))
             event = LINK_SPEED;
         else if (eventName.equals(TERMINATING_STR))
@@ -824,8 +877,7 @@
             event = BSS_ADDED;
         } else if (eventName.equals(BSS_REMOVED_STR)) {
             event = BSS_REMOVED;
-        }
-        else
+        } else
             event = UNKNOWN;
 
         String eventData = eventStr;
@@ -958,6 +1010,10 @@
                 mStateMachine.sendMessage(SCAN_RESULTS_EVENT);
                 break;
 
+            case SCAN_FAILED:
+                mStateMachine.sendMessage(SCAN_FAILED_EVENT);
+                break;
+
             case UNKNOWN:
                 if (DBG) {
                     logDbg("handleEvent unknown: " + Integer.toString(event) + "  " + remainder);
@@ -1053,14 +1109,34 @@
         return err;
     }
 
+    WifiP2pDevice getWifiP2pDevice(String dataString) {
+        try {
+            WifiP2pDevice device = new WifiP2pDevice(dataString);
+            return device;
+        } catch (IllegalArgumentException e) {
+            return null;
+        }
+    }
+
+    WifiP2pGroup getWifiP2pGroup(String dataString) {
+        try {
+            WifiP2pGroup group = new WifiP2pGroup(dataString);
+            return group;
+        } catch (IllegalArgumentException e) {
+            return null;
+        }
+    }
+
     /**
      * Handle p2p events
      */
     private void handleP2pEvents(String dataString) {
         if (dataString.startsWith(P2P_DEVICE_FOUND_STR)) {
-            mStateMachine.sendMessage(P2P_DEVICE_FOUND_EVENT, new WifiP2pDevice(dataString));
+            WifiP2pDevice device = getWifiP2pDevice(dataString);
+            if (device != null) mStateMachine.sendMessage(P2P_DEVICE_FOUND_EVENT, device);
         } else if (dataString.startsWith(P2P_DEVICE_LOST_STR)) {
-            mStateMachine.sendMessage(P2P_DEVICE_LOST_EVENT, new WifiP2pDevice(dataString));
+            WifiP2pDevice device = getWifiP2pDevice(dataString);
+            if (device != null) mStateMachine.sendMessage(P2P_DEVICE_LOST_EVENT, device);
         } else if (dataString.startsWith(P2P_FIND_STOPPED_STR)) {
             mStateMachine.sendMessage(P2P_FIND_STOPPED_EVENT);
         } else if (dataString.startsWith(P2P_GO_NEG_REQUEST_STR)) {
@@ -1075,9 +1151,11 @@
         } else if (dataString.startsWith(P2P_GROUP_FORMATION_FAILURE_STR)) {
             mStateMachine.sendMessage(P2P_GROUP_FORMATION_FAILURE_EVENT, p2pError(dataString));
         } else if (dataString.startsWith(P2P_GROUP_STARTED_STR)) {
-            mStateMachine.sendMessage(P2P_GROUP_STARTED_EVENT, new WifiP2pGroup(dataString));
+            WifiP2pGroup group = getWifiP2pGroup(dataString);
+            if (group != null) mStateMachine.sendMessage(P2P_GROUP_STARTED_EVENT, group);
         } else if (dataString.startsWith(P2P_GROUP_REMOVED_STR)) {
-            mStateMachine.sendMessage(P2P_GROUP_REMOVED_EVENT, new WifiP2pGroup(dataString));
+            WifiP2pGroup group = getWifiP2pGroup(dataString);
+            if (group != null) mStateMachine.sendMessage(P2P_GROUP_REMOVED_EVENT, group);
         } else if (dataString.startsWith(P2P_INVITATION_RECEIVED_STR)) {
             mStateMachine.sendMessage(P2P_INVITATION_RECEIVED_EVENT,
                     new WifiP2pGroup(dataString));
@@ -1121,6 +1199,38 @@
         }
     }
 
+    private static final String ADDR_STRING = "addr=";
+    private static final String RESULT_STRING = "result=";
+
+    // ANQP-QUERY-DONE addr=18:cf:5e:26:a4:88 result=SUCCESS
+
+    private void handleAnqpResult(String eventStr) {
+        int addrPos = eventStr.indexOf(ADDR_STRING);
+        int resPos = eventStr.indexOf(RESULT_STRING);
+        if (addrPos < 0 || resPos < 0) {
+            throw new IllegalArgumentException("Unexpected ANQP result notification");
+        }
+        int eoaddr = eventStr.indexOf(' ', addrPos + ADDR_STRING.length());
+        if (eoaddr < 0) {
+            eoaddr = eventStr.length();
+        }
+        int eoresult = eventStr.indexOf(' ', resPos + RESULT_STRING.length());
+        if (eoresult < 0) {
+            eoresult = eventStr.length();
+        }
+
+        try {
+            long bssid = Utils.parseMac(eventStr.substring(addrPos + ADDR_STRING.length(), eoaddr));
+            int result = eventStr.substring(
+                    resPos + RESULT_STRING.length(), eoresult).equalsIgnoreCase("success") ? 1 : 0;
+
+            mStateMachine.sendMessage(ANQP_DONE_EVENT, result, 0, bssid);
+        }
+        catch (IllegalArgumentException iae) {
+            Log.e(TAG, "Bad MAC address in ANQP response: " + iae.getMessage());
+        }
+    }
+
     /**
      * Handle ANQP events
      */
@@ -1208,15 +1318,24 @@
                 Log.e(TAG, "didn't find SSID " + requestName);
             }
             mStateMachine.sendMessage(SUP_REQUEST_IDENTITY, eventLogCounter, reason, SSID);
-        } if (requestName.startsWith(SIM_STR)) {
-            Matcher match = mRequestGsmAuthPattern.matcher(requestName);
-            if (match.find()) {
-                WifiStateMachine.SimAuthRequestData data =
-                        new WifiStateMachine.SimAuthRequestData();
-                data.networkId = Integer.parseInt(match.group(1));
+        } else if (requestName.startsWith(SIM_STR)) {
+            Matcher matchGsm = mRequestGsmAuthPattern.matcher(requestName);
+            Matcher matchUmts = mRequestUmtsAuthPattern.matcher(requestName);
+            WifiStateMachine.SimAuthRequestData data =
+                    new WifiStateMachine.SimAuthRequestData();
+            if (matchGsm.find()) {
+                data.networkId = Integer.parseInt(matchGsm.group(1));
                 data.protocol = WifiEnterpriseConfig.Eap.SIM;
-                data.ssid = match.group(4);
-                data.challenges = match.group(2).split(":");
+                data.ssid = matchGsm.group(4);
+                data.data = matchGsm.group(2).split(":");
+                mStateMachine.sendMessage(SUP_REQUEST_SIM_AUTH, data);
+            } else if (matchUmts.find()) {
+                data.networkId = Integer.parseInt(matchUmts.group(1));
+                data.protocol = WifiEnterpriseConfig.Eap.AKA;
+                data.ssid = matchUmts.group(4);
+                data.data = new String[2];
+                data.data[0] = matchUmts.group(2);
+                data.data[1] = matchUmts.group(3);
                 mStateMachine.sendMessage(SUP_REQUEST_SIM_AUTH, data);
             } else {
                 Log.e(TAG, "couldn't parse SIM auth request - " + requestName);
diff --git a/service/java/com/android/server/wifi/WifiNative.java b/service/java/com/android/server/wifi/WifiNative.java
index b95db28..899529d 100644
--- a/service/java/com/android/server/wifi/WifiNative.java
+++ b/service/java/com/android/server/wifi/WifiNative.java
@@ -19,21 +19,35 @@
 import android.net.wifi.BatchedScanSettings;
 import android.net.wifi.RttManager;
 import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiLinkLayerStats;
+import android.net.wifi.WifiManager;
 import android.net.wifi.WifiScanner;
+import android.net.wifi.RttManager;
+import android.net.wifi.WifiSsid;
 import android.net.wifi.WpsInfo;
 import android.net.wifi.p2p.WifiP2pConfig;
 import android.net.wifi.p2p.WifiP2pGroup;
+import android.net.wifi.p2p.nsd.WifiP2pServiceInfo;
+import android.net.wifi.WifiEnterpriseConfig;
 import android.os.SystemClock;
 import android.text.TextUtils;
-import android.net.wifi.p2p.nsd.WifiP2pServiceInfo;
+import android.util.Base64;
 import android.util.LocalLog;
 import android.util.Log;
 
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
-
+import java.util.zip.Deflater;
+import libcore.util.HexEncoding;
 /**
  * Native calls for bring up/shut down of the supplicant daemon and for
  * sending requests to the supplicant daemon
@@ -65,6 +79,10 @@
 
     private boolean mSuspendOptEnabled = false;
 
+    private static final int EID_HT_OPERATION = 61;
+    private static final int EID_VHT_OPERATION = 192;
+    private static final int EID_EXTENDED_CAPS = 127;
+    private static final int RTT_RESP_ENABLE_BIT = 70;
     /* Register native functions */
 
     static {
@@ -122,12 +140,12 @@
         }
     }
 
-    private static final LocalLog mLocalLog = new LocalLog(1024);
+    private static final LocalLog mLocalLog = new LocalLog(16384);
 
     // hold mLock before accessing mCmdIdLock
     private static int sCmdId;
 
-    public LocalLog getLocalLog() {
+    public static LocalLog getLocalLog() {
         return mLocalLog;
     }
 
@@ -171,6 +189,16 @@
         }
     }
 
+    private boolean doBooleanCommandWithoutLogging(String command) {
+        if (DBG) Log.d(mTAG, "doBooleanCommandWithoutLogging: " + command);
+        synchronized (mLock) {
+            int cmdId = getNewCmdIdLocked();
+            boolean result = doBooleanCommandNative(mInterfacePrefix + command);
+            if (DBG) Log.d(mTAG, command + ": returned " + result);
+            return result;
+        }
+    }
+
     private int doIntCommand(String command) {
         if (DBG) Log.d(mTAG, "doInt: " + command);
         synchronized (mLock) {
@@ -267,7 +295,12 @@
 
     public boolean setNetworkVariable(int netId, String name, String value) {
         if (TextUtils.isEmpty(name) || TextUtils.isEmpty(value)) return false;
-        return doBooleanCommand("SET_NETWORK " + netId + " " + name + " " + value);
+        if (name.equals(WifiConfiguration.pskVarName)
+                || name.equals(WifiEnterpriseConfig.PASSWORD_KEY)) {
+            return doBooleanCommandWithoutLogging("SET_NETWORK " + netId + " " + name + " " + value);
+        } else {
+            return doBooleanCommand("SET_NETWORK " + netId + " " + name + " " + value);
+        }
     }
 
     public String getNetworkVariable(int netId, String name) {
@@ -308,6 +341,11 @@
         return doBooleanCommand("DISABLE_NETWORK " + netId);
     }
 
+    public boolean selectNetwork(int netId) {
+        if (DBG) logDbg("selectNetwork nid=" + Integer.toString(netId));
+        return doBooleanCommand("SELECT_NETWORK " + netId);
+    }
+
     public boolean reconnect() {
         if (DBG) logDbg("RECONNECT ");
         return doBooleanCommand("RECONNECT");
@@ -345,6 +383,8 @@
         return null;
     }
 
+
+
     /**
      * Format of results:
      * =================
@@ -361,9 +401,29 @@
      * RANGE=ALL gets all scan results
      * RANGE=ID- gets results from ID
      * MASK=<N> see wpa_supplicant/src/common/wpa_ctrl.h for details
+     * 0                         0                        1                       0     2
+     *                           WPA_BSS_MASK_MESH_SCAN | WPA_BSS_MASK_DELIM    | WPA_BSS_MASK_WIFI_DISPLAY
+     * 0                         0                        0                       1     1   -> 9
+     * WPA_BSS_MASK_INTERNETW  | WPA_BSS_MASK_P2P_SCAN  | WPA_BSS_MASK_WPS_SCAN | WPA_BSS_MASK_SSID
+     * 1                         0                        0                       1     9   -> d
+     * WPA_BSS_MASK_FLAGS      | WPA_BSS_MASK_IE        | WPA_BSS_MASK_AGE      | WPA_BSS_MASK_TSF
+     * 1                         0                        0                       0     8
+     * WPA_BSS_MASK_LEVEL      | WPA_BSS_MASK_NOISE     | WPA_BSS_MASK_QUAL     | WPA_BSS_MASK_CAPABILITIES
+     * 0                         1                        1                       1     7
+     * WPA_BSS_MASK_BEACON_INT | WPA_BSS_MASK_FREQ      | WPA_BSS_MASK_BSSID    | WPA_BSS_MASK_ID
+     *
+     * WPA_BSS_MASK_INTERNETW adds ANQP info (ctrl_iface:4151-4176)
+     *
+     * ctrl_iface.c:wpa_supplicant_ctrl_iface_process:7884
+     *  wpa_supplicant_ctrl_iface_bss:4315
+     *  print_bss_info
      */
     public String scanResults(int sid) {
-        return doStringCommandWithoutLogging("BSS RANGE=" + sid + "- MASK=0x21987");
+        return doStringCommandWithoutLogging("BSS RANGE=" + sid + "- MASK=0x29d87");
+    }
+
+    public String doCustomCommand(String command) {
+        return doStringCommand(command);
     }
 
     /**
@@ -510,22 +570,24 @@
             && doBooleanCommand("DRIVER RXFILTER-START");
     }
 
-    public int getBand() {
-       String ret = doStringCommand("DRIVER GETBAND");
-        if (!TextUtils.isEmpty(ret)) {
-            //reply is "BAND X" where X is the band
-            String[] tokens = ret.split(" ");
-            try {
-                if (tokens.length == 2) return Integer.parseInt(tokens[1]);
-            } catch (NumberFormatException e) {
-                return -1;
-            }
-        }
-        return -1;
-    }
-
+    /**
+     * Set the operational frequency band
+     * @param band One of
+     *     {@link WifiManager#WIFI_FREQUENCY_BAND_AUTO},
+     *     {@link WifiManager#WIFI_FREQUENCY_BAND_5GHZ},
+     *     {@link WifiManager#WIFI_FREQUENCY_BAND_2GHZ},
+     * @return {@code true} if the operation succeeded, {@code false} otherwise
+     */
     public boolean setBand(int band) {
-        return doBooleanCommand("DRIVER SETBAND " + band);
+        String bandstr;
+
+        if (band == WifiManager.WIFI_FREQUENCY_BAND_5GHZ)
+            bandstr = "5G";
+        else if (band == WifiManager.WIFI_FREQUENCY_BAND_2GHZ)
+            bandstr = "2G";
+        else
+            bandstr = "AUTO";
+        return doBooleanCommand("SET SETBAND " + bandstr);
     }
 
     /**
@@ -586,15 +648,20 @@
     }
 
     public boolean setCountryCode(String countryCode) {
-        return doBooleanCommand("DRIVER COUNTRY " + countryCode.toUpperCase(Locale.ROOT));
+        if (countryCode != null)
+            return doBooleanCommand("DRIVER COUNTRY " + countryCode.toUpperCase(Locale.ROOT));
+        else
+            return doBooleanCommand("DRIVER COUNTRY");
     }
 
-    public void enableBackgroundScan(boolean enable) {
+    public boolean enableBackgroundScan(boolean enable) {
+        boolean ret;
         if (enable) {
-            doBooleanCommand("SET pno 1");
+            ret = doBooleanCommand("SET pno 1");
         } else {
-            doBooleanCommand("SET pno 0");
+            ret = doBooleanCommand("SET pno 0");
         }
+        return ret;
     }
 
     public void enableAutoConnect(boolean enable) {
@@ -697,9 +764,16 @@
         }
     }
 
-    public boolean simAuthResponse(int id, String response) {
+    public boolean simAuthResponse(int id, String type, String response) {
+        // with type = GSM-AUTH, UMTS-AUTH or UMTS-AUTS
         synchronized (mLock) {
-            return doBooleanCommand("CTRL-RSP-SIM-" + id + ":GSM-AUTH" + response);
+            return doBooleanCommand("CTRL-RSP-SIM-" + id + ":" + type + response);
+        }
+    }
+
+    public boolean simIdentityResponse(int id, String response) {
+        synchronized (mLock) {
+            return doBooleanCommand("CTRL-RSP-IDENTITY-" + id + ":" + response);
         }
     }
 
@@ -1132,379 +1206,6 @@
         return doBooleanCommand("ANQP_GET " + bssid + " " + subtypes);
     }
 
-    /* WIFI HAL support */
-
-    private static final String TAG = "WifiNative-HAL";
-    private static long sWifiHalHandle = 0;  /* used by JNI to save wifi_handle */
-    private static long[] sWifiIfaceHandles = null;  /* used by JNI to save interface handles */
-    private static int sWlan0Index = -1;
-    private static int sP2p0Index = -1;
-
-    private static boolean sHalIsStarted = false;
-    private static boolean sHalFailed = false;
-
-    private static native boolean startHalNative();
-    private static native void stopHalNative();
-    private static native void waitForHalEventNative();
-
-    private static class MonitorThread extends Thread {
-        public void run() {
-            Log.i(TAG, "Waiting for HAL events mWifiHalHandle=" + Long.toString(sWifiHalHandle));
-            waitForHalEventNative();
-        }
-    }
-
-    synchronized public static boolean startHal() {
-        Log.i(TAG, "startHal");
-        synchronized (mLock) {
-            if (sHalIsStarted)
-                return true;
-            if (sHalFailed)
-                return false;
-            if (startHalNative() && (getInterfaces() != 0) && (sWlan0Index != -1)) {
-                new MonitorThread().start();
-                sHalIsStarted = true;
-                return true;
-            } else {
-                Log.i(TAG, "Could not start hal");
-                sHalIsStarted = false;
-                sHalFailed = true;
-                return false;
-            }
-        }
-    }
-
-    synchronized public static void stopHal() {
-        stopHalNative();
-    }
-
-    private static native int getInterfacesNative();
-
-    synchronized public static int getInterfaces() {
-        synchronized (mLock) {
-            if (sWifiIfaceHandles == null) {
-                int num = getInterfacesNative();
-                int wifi_num = 0;
-                for (int i = 0; i < num; i++) {
-                    String name = getInterfaceNameNative(i);
-                    Log.i(TAG, "interface[" + i + "] = " + name);
-                    if (name.equals("wlan0")) {
-                        sWlan0Index = i;
-                        wifi_num++;
-                    } else if (name.equals("p2p0")) {
-                        sP2p0Index = i;
-                        wifi_num++;
-                    }
-                }
-                return wifi_num;
-            } else {
-                return sWifiIfaceHandles.length;
-            }
-        }
-    }
-
-    private static native String getInterfaceNameNative(int index);
-    synchronized public static String getInterfaceName(int index) {
-        return getInterfaceNameNative(index);
-    }
-
-    public static class ScanCapabilities {
-        public int  max_scan_cache_size;                 // in number of scan results??
-        public int  max_scan_buckets;
-        public int  max_ap_cache_per_scan;
-        public int  max_rssi_sample_size;
-        public int  max_scan_reporting_threshold;        // in number of scan results??
-        public int  max_hotlist_aps;
-        public int  max_significant_wifi_change_aps;
-    }
-
-    public static boolean getScanCapabilities(ScanCapabilities capabilities) {
-        return getScanCapabilitiesNative(sWlan0Index, capabilities);
-    }
-
-    private static native boolean getScanCapabilitiesNative(
-            int iface, ScanCapabilities capabilities);
-
-    private static native boolean startScanNative(int iface, int id, ScanSettings settings);
-    private static native boolean stopScanNative(int iface, int id);
-    private static native ScanResult[] getScanResultsNative(int iface, boolean flush);
-    private static native WifiLinkLayerStats getWifiLinkLayerStatsNative(int iface);
-
-    public static class ChannelSettings {
-        int frequency;
-        int dwell_time_ms;
-        boolean passive;
-    }
-
-    public static class BucketSettings {
-        int bucket;
-        int band;
-        int period_ms;
-        int report_events;
-        int num_channels;
-        ChannelSettings channels[];
-    }
-
-    public static class ScanSettings {
-        int base_period_ms;
-        int max_ap_per_scan;
-        int report_threshold;
-        int num_buckets;
-        BucketSettings buckets[];
-    }
-
-    public static interface ScanEventHandler {
-        void onScanResultsAvailable();
-        void onFullScanResult(ScanResult fullScanResult);
-        void onSingleScanComplete();
-        void onScanPaused();
-        void onScanRestarted();
-    }
-
-    synchronized static void onScanResultsAvailable(int id) {
-        if (sScanEventHandler  != null) {
-            sScanEventHandler.onScanResultsAvailable();
-        }
-    }
-
-    /* scan status, keep these values in sync with gscan.h */
-    private static int WIFI_SCAN_BUFFER_FULL = 0;
-    private static int WIFI_SCAN_COMPLETE = 1;
-
-    synchronized static void onScanStatus(int status) {
-        Log.i(TAG, "Got a scan status changed event, status = " + status);
-
-        if (status == WIFI_SCAN_BUFFER_FULL) {
-            /* we have a separate event to take care of this */
-        } else if (status == WIFI_SCAN_COMPLETE) {
-            if (sScanEventHandler  != null) {
-                sScanEventHandler.onSingleScanComplete();
-            }
-        }
-    }
-
-    synchronized static void onFullScanResult(int id, ScanResult result, byte bytes[]) {
-        if (DBG) Log.i(TAG, "Got a full scan results event, ssid = " + result.SSID + ", " +
-                "num = " + bytes.length);
-
-        if (sScanEventHandler == null) {
-            return;
-        }
-
-        int num = 0;
-        for (int i = 0; i < bytes.length; ) {
-            int type  = bytes[i] & 0xFF;
-            int len = bytes[i + 1] & 0xFF;
-
-            if (i + len + 2 > bytes.length) {
-                Log.w(TAG, "bad length " + len + " of IE " + type + " from " + result.BSSID);
-                Log.w(TAG, "ignoring the rest of the IEs");
-                break;
-            }
-            num++;
-            i += len + 2;
-            if (DBG) Log.i(TAG, "bytes[" + i + "] = [" + type + ", " + len + "]" + ", " +
-                    "next = " + i);
-        }
-
-        ScanResult.InformationElement elements[] = new ScanResult.InformationElement[num];
-        for (int i = 0, index = 0; i < num; i++) {
-            int type  = bytes[index] & 0xFF;
-            int len = bytes[index + 1] & 0xFF;
-            if (DBG) Log.i(TAG, "index = " + index + ", type = " + type + ", len = " + len);
-            ScanResult.InformationElement elem = new ScanResult.InformationElement();
-            elem.id = type;
-            elem.bytes = new byte[len];
-            for (int j = 0; j < len; j++) {
-                elem.bytes[j] = bytes[index + j + 2];
-            }
-            elements[i] = elem;
-            index += (len + 2);
-        }
-
-        result.informationElements = elements;
-        sScanEventHandler.onFullScanResult(result);
-    }
-
-    private static int sScanCmdId = 0;
-    private static ScanEventHandler sScanEventHandler;
-    private static ScanSettings sScanSettings;
-
-    synchronized public static boolean startScan(
-            ScanSettings settings, ScanEventHandler eventHandler) {
-        synchronized (mLock) {
-
-            if (sScanCmdId != 0) {
-                stopScan();
-            } else if (sScanSettings != null || sScanEventHandler != null) {
-                /* current scan is paused; no need to stop it */
-            }
-
-            sScanCmdId = getNewCmdIdLocked();
-
-            sScanSettings = settings;
-            sScanEventHandler = eventHandler;
-
-            if (startScanNative(sWlan0Index, sScanCmdId, settings) == false) {
-                sScanEventHandler = null;
-                sScanSettings = null;
-                return false;
-            }
-
-            return true;
-        }
-    }
-
-    synchronized public static void stopScan() {
-        synchronized (mLock) {
-            stopScanNative(sWlan0Index, sScanCmdId);
-            sScanSettings = null;
-            sScanEventHandler = null;
-            sScanCmdId = 0;
-        }
-    }
-
-    synchronized public static void pauseScan() {
-        synchronized (mLock) {
-            if (sScanCmdId != 0 && sScanSettings != null && sScanEventHandler != null) {
-                Log.d(TAG, "Pausing scan");
-                stopScanNative(sWlan0Index, sScanCmdId);
-                sScanCmdId = 0;
-                sScanEventHandler.onScanPaused();
-            }
-        }
-    }
-
-    synchronized public static void restartScan() {
-        synchronized (mLock) {
-            if (sScanCmdId == 0 && sScanSettings != null && sScanEventHandler != null) {
-                Log.d(TAG, "Restarting scan");
-                startScan(sScanSettings, sScanEventHandler);
-                sScanEventHandler.onScanRestarted();
-            }
-        }
-    }
-
-    synchronized public static ScanResult[] getScanResults() {
-        synchronized (mLock) {
-            return getScanResultsNative(sWlan0Index, /* flush = */ false);
-        }
-    }
-
-    public static interface HotlistEventHandler {
-        void onHotlistApFound (ScanResult[]result);
-    }
-
-    private static int sHotlistCmdId = 0;
-    private static HotlistEventHandler sHotlistEventHandler;
-
-    private native static boolean setHotlistNative(int iface, int id,
-            WifiScanner.HotlistSettings settings);
-    private native static boolean resetHotlistNative(int iface, int id);
-
-    synchronized public static boolean setHotlist(WifiScanner.HotlistSettings settings,
-                                    HotlistEventHandler eventHandler) {
-        synchronized (mLock) {
-            if (sHotlistCmdId != 0) {
-                return false;
-            } else {
-                sHotlistCmdId = getNewCmdIdLocked();
-            }
-
-            sHotlistEventHandler = eventHandler;
-            if (setHotlistNative(sWlan0Index, sScanCmdId, settings) == false) {
-                sHotlistEventHandler = null;
-                return false;
-            }
-
-            return true;
-        }
-    }
-
-    synchronized public static void resetHotlist() {
-        synchronized (mLock) {
-            if (sHotlistCmdId != 0) {
-                resetHotlistNative(sWlan0Index, sHotlistCmdId);
-                sHotlistCmdId = 0;
-                sHotlistEventHandler = null;
-            }
-        }
-    }
-
-    synchronized public static void onHotlistApFound(int id, ScanResult[] results) {
-        synchronized (mLock) {
-            if (sHotlistCmdId != 0) {
-                sHotlistEventHandler.onHotlistApFound(results);
-            } else {
-                /* this can happen because of race conditions */
-                Log.d(TAG, "Ignoring hotlist AP found change");
-            }
-        }
-    }
-
-    public static interface SignificantWifiChangeEventHandler {
-        void onChangesFound(ScanResult[] result);
-    }
-
-    private static SignificantWifiChangeEventHandler sSignificantWifiChangeHandler;
-    private static int sSignificantWifiChangeCmdId;
-
-    private static native boolean trackSignificantWifiChangeNative(
-            int iface, int id, WifiScanner.WifiChangeSettings settings);
-    private static native boolean untrackSignificantWifiChangeNative(int iface, int id);
-
-    synchronized public static boolean trackSignificantWifiChange(
-            WifiScanner.WifiChangeSettings settings, SignificantWifiChangeEventHandler handler) {
-        synchronized (mLock) {
-            if (sSignificantWifiChangeCmdId != 0) {
-                return false;
-            } else {
-                sSignificantWifiChangeCmdId = getNewCmdIdLocked();
-            }
-
-            sSignificantWifiChangeHandler = handler;
-            if (trackSignificantWifiChangeNative(sWlan0Index, sScanCmdId, settings) == false) {
-                sSignificantWifiChangeHandler = null;
-                return false;
-            }
-
-            return true;
-        }
-    }
-
-    synchronized static void untrackSignificantWifiChange() {
-        synchronized (mLock) {
-            if (sSignificantWifiChangeCmdId != 0) {
-                untrackSignificantWifiChangeNative(sWlan0Index, sSignificantWifiChangeCmdId);
-                sSignificantWifiChangeCmdId = 0;
-                sSignificantWifiChangeHandler = null;
-            }
-        }
-    }
-
-    synchronized static void onSignificantWifiChange(int id, ScanResult[] results) {
-        synchronized (mLock) {
-            if (sSignificantWifiChangeCmdId != 0) {
-                sSignificantWifiChangeHandler.onChangesFound(results);
-            } else {
-                /* this can happen because of race conditions */
-                Log.d(TAG, "Ignoring significant wifi change");
-            }
-        }
-    }
-
-    synchronized public static WifiLinkLayerStats getWifiLinkLayerStats(String iface) {
-        // TODO: use correct iface name to Index translation
-        if (iface == null) return null;
-        synchronized (mLock) {
-            if (!sHalIsStarted)
-                startHal();
-            if (sHalIsStarted)
-                return getWifiLinkLayerStatsNative(sWlan0Index);
-        }
-        return null;
-    }
-
     /*
      * NFC-related calls
      */
@@ -1528,9 +1229,594 @@
         return doBooleanCommand("NFC_REPORT_HANDOVER RESP P2P " + requestMessage + " 00");
     }
 
+    /* WIFI HAL support */
+
+    private static final String TAG = "WifiNative-HAL";
+    private static long sWifiHalHandle = 0;             /* used by JNI to save wifi_handle */
+    private static long[] sWifiIfaceHandles = null;     /* used by JNI to save interface handles */
+    private static int sWlan0Index = -1;
+    private static int sP2p0Index = -1;
+    private static MonitorThread sThread;
+    private static final int STOP_HAL_TIMEOUT_MS = 1000;
+
+    private static native boolean startHalNative();
+    private static native void stopHalNative();
+    private static native void waitForHalEventNative();
+
+    private static class MonitorThread extends Thread {
+        public void run() {
+            Log.i(TAG, "Waiting for HAL events mWifiHalHandle=" + Long.toString(sWifiHalHandle));
+            waitForHalEventNative();
+        }
+    }
+
+    synchronized public static boolean startHal() {
+
+        String debugLog = "startHal stack: ";
+        java.lang.StackTraceElement[] elements = Thread.currentThread().getStackTrace();
+        for (int i = 2; i < elements.length && i <= 7; i++ ) {
+            debugLog = debugLog + " - " + elements[i].getMethodName();
+        }
+
+        mLocalLog.log(debugLog);
+
+        synchronized (mLock) {
+            if (startHalNative() && (getInterfaces() != 0) && (sWlan0Index != -1)) {
+                sThread = new MonitorThread();
+                sThread.start();
+                return true;
+            } else {
+                if (DBG) mLocalLog.log("Could not start hal");
+                Log.e(TAG, "Could not start hal");
+                return false;
+            }
+        }
+    }
+
+    synchronized public static void stopHal() {
+        synchronized (mLock) {
+            if (isHalStarted()) {
+                stopHalNative();
+                try {
+                    sThread.join(STOP_HAL_TIMEOUT_MS);
+                    Log.d(TAG, "HAL event thread stopped successfully");
+                } catch (InterruptedException e) {
+                    Log.e(TAG, "Could not stop HAL cleanly");
+                }
+                sThread = null;
+                sWifiHalHandle = 0;
+                sWifiIfaceHandles = null;
+                sWlan0Index = -1;
+                sP2p0Index = -1;
+            }
+        }
+    }
+
+    public static boolean isHalStarted() {
+        return (sWifiHalHandle != 0);
+    }
+    private static native int getInterfacesNative();
+
+    synchronized public static int getInterfaces() {
+        synchronized (mLock) {
+            if (isHalStarted()) {
+                if (sWifiIfaceHandles == null) {
+                    int num = getInterfacesNative();
+                    int wifi_num = 0;
+                    for (int i = 0; i < num; i++) {
+                        String name = getInterfaceNameNative(i);
+                        Log.i(TAG, "interface[" + i + "] = " + name);
+                        if (name.equals("wlan0")) {
+                            sWlan0Index = i;
+                            wifi_num++;
+                        } else if (name.equals("p2p0")) {
+                            sP2p0Index = i;
+                            wifi_num++;
+                        }
+                    }
+                    return wifi_num;
+                } else {
+                    return sWifiIfaceHandles.length;
+                }
+            } else {
+                return 0;
+            }
+        }
+    }
+
+    private static native String getInterfaceNameNative(int index);
+    synchronized public static String getInterfaceName(int index) {
+        return getInterfaceNameNative(index);
+    }
+
+    public static class ScanCapabilities {
+        public int  max_scan_cache_size;                 // in number of scan results??
+        public int  max_scan_buckets;
+        public int  max_ap_cache_per_scan;
+        public int  max_rssi_sample_size;
+        public int  max_scan_reporting_threshold;        // in number of scan results??
+        public int  max_hotlist_bssids;
+        public int  max_significant_wifi_change_aps;
+    }
+
+    synchronized public static boolean getScanCapabilities(ScanCapabilities capabilities) {
+        synchronized (mLock) {
+            return isHalStarted() && getScanCapabilitiesNative(sWlan0Index, capabilities);
+        }
+    }
+
+    private static native boolean getScanCapabilitiesNative(
+            int iface, ScanCapabilities capabilities);
+
+    private static native boolean startScanNative(int iface, int id, ScanSettings settings);
+    private static native boolean stopScanNative(int iface, int id);
+    private static native WifiScanner.ScanData[] getScanResultsNative(int iface, boolean flush);
+    private static native WifiLinkLayerStats getWifiLinkLayerStatsNative(int iface);
+    private static native void setWifiLinkLayerStatsNative(int iface, int enable);
+
+    public static class ChannelSettings {
+        int frequency;
+        int dwell_time_ms;
+        boolean passive;
+    }
+
+    public static class BucketSettings {
+        int bucket;
+        int band;
+        int period_ms;
+        int report_events;
+        int num_channels;
+        ChannelSettings channels[];
+    }
+
+    public static class ScanSettings {
+        int base_period_ms;
+        int max_ap_per_scan;
+        int report_threshold_percent;
+        int report_threshold_num_scans;
+        int num_buckets;
+        BucketSettings buckets[];
+    }
+
+    public static interface ScanEventHandler {
+        void onScanResultsAvailable();
+        void onFullScanResult(ScanResult fullScanResult);
+        void onScanStatus();
+        void onScanPaused(WifiScanner.ScanData[] data);
+        void onScanRestarted();
+    }
+
+    synchronized static void onScanResultsAvailable(int id) {
+        if (sScanEventHandler  != null) {
+            sScanEventHandler.onScanResultsAvailable();
+        }
+    }
+
+    /* scan status, keep these values in sync with gscan.h */
+    private static int WIFI_SCAN_BUFFER_FULL = 0;
+    private static int WIFI_SCAN_COMPLETE = 1;
+
+    synchronized static void onScanStatus(int status) {
+        if (status == WIFI_SCAN_BUFFER_FULL) {
+            /* we have a separate event to take care of this */
+        } else if (status == WIFI_SCAN_COMPLETE) {
+            if (sScanEventHandler  != null) {
+                sScanEventHandler.onScanStatus();
+            }
+        }
+    }
+
+    public static  WifiSsid createWifiSsid (byte[] rawSsid) {
+        String ssidHexString = String.valueOf(HexEncoding.encode(rawSsid));
+
+        if (ssidHexString == null) {
+            return null;
+        }
+
+        WifiSsid wifiSsid = WifiSsid.createFromHex(ssidHexString);
+
+        return wifiSsid;
+    }
+
+    public static String ssidConvert(byte[] rawSsid) {
+        String ssid;
+
+        CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
+        try {
+            CharBuffer decoded = decoder.decode(ByteBuffer.wrap(rawSsid));
+            ssid = decoded.toString();
+        } catch (CharacterCodingException cce) {
+            ssid = null;
+        }
+
+        if (ssid == null) {
+            ssid = new String(rawSsid, StandardCharsets.ISO_8859_1);
+        }
+
+        return ssid;
+    }
+
+    public static boolean setSsid(byte[] rawSsid, ScanResult result) {
+        if (rawSsid == null || rawSsid.length == 0 || result == null) {
+            return false;
+        }
+
+        result.SSID = ssidConvert(rawSsid);
+        result.wifiSsid = createWifiSsid(rawSsid);
+        return true;
+    }
+
+    static void populateScanResult(ScanResult result, byte bytes[], String dbg) {
+        int num = 0;
+        if (bytes == null) return;
+        if (dbg == null) dbg = "";
+        for (int i = 0; i < bytes.length - 1; ) {
+            int type  = bytes[i] & 0xFF;
+            int len = bytes[i + 1] & 0xFF;
+            if (i + len + 2 > bytes.length) {
+                Log.w(TAG, dbg + "bad length " + len + " of IE " + type + " from " + result.BSSID);
+                Log.w(TAG, dbg + "ignoring the rest of the IEs");
+                break;
+            }
+            num++;
+            if (DBG) Log.i(TAG, dbg + "bytes[" + i + "] = [" + type + ", " + len + "]" + ", " +
+                    "next = " + (i + len + 2));
+            i += len + 2;
+        }
+
+        int secondChanelOffset = 0;
+        byte channelMode = 0;
+        byte centerFreqIndex1 = 0;
+        byte centerFreqIndex2 = 0;
+
+        boolean is80211McRTTResponder = false;
+
+        ScanResult.InformationElement elements[] = new ScanResult.InformationElement[num];
+        for (int i = 0, index = 0; i < num; i++) {
+            int type  = bytes[index] & 0xFF;
+            int len = bytes[index + 1] & 0xFF;
+            if (DBG) Log.i(TAG, dbg + "index = " + index + ", type = " + type + ", len = " + len);
+            ScanResult.InformationElement elem = new ScanResult.InformationElement();
+            elem.id = type;
+            elem.bytes = new byte[len];
+            for (int j = 0; j < len; j++) {
+                elem.bytes[j] = bytes[index + j + 2];
+            }
+            elements[i] = elem;
+            int inforStart = index + 2;
+            index += (len + 2);
+
+            if(type == EID_HT_OPERATION) {
+                secondChanelOffset = bytes[inforStart + 1] & 0x3;
+            } else if(type == EID_VHT_OPERATION) {
+                channelMode = bytes[inforStart];
+                centerFreqIndex1 = bytes[inforStart + 1];
+                centerFreqIndex2 = bytes[inforStart + 2];
+            } else if (type == EID_EXTENDED_CAPS) {
+                int tempIndex = RTT_RESP_ENABLE_BIT / 8;
+                byte offset = RTT_RESP_ENABLE_BIT % 8;
+
+                if(len < tempIndex + 1) {
+                    is80211McRTTResponder = false;
+                } else {
+                    if ((bytes[inforStart + tempIndex] & ((byte)0x1 << offset)) != 0) {
+                        is80211McRTTResponder = true;
+                    } else {
+                        is80211McRTTResponder = false;
+                    }
+                }
+            }
+        }
+
+        if (is80211McRTTResponder) {
+            result.setFlag(ScanResult.FLAG_80211mc_RESPONDER);
+        } else {
+            result.clearFlag(ScanResult.FLAG_80211mc_RESPONDER);
+        }
+
+        //handle RTT related information
+        if (channelMode != 0) {
+            // 80 or 160 MHz
+            result.channelWidth = channelMode + 1;
+
+            //convert channel index to frequency in MHz, channel 36 is 5180MHz
+            result.centerFreq0 = (centerFreqIndex1 - 36) * 5 + 5180;
+
+            if(channelMode > 1) { //160MHz
+                result.centerFreq1 = (centerFreqIndex2 - 36) * 5 + 5180;
+            } else {
+                result.centerFreq1 = 0;
+            }
+        } else {
+            //20 or 40 MHz
+            if (secondChanelOffset != 0) {//40MHz
+                result.channelWidth = 1;
+                if (secondChanelOffset == 1) {
+                    result.centerFreq0 = result.frequency + 20;
+                } else if (secondChanelOffset == 3) {
+                    result.centerFreq0 = result.frequency - 20;
+                } else {
+                    result.centerFreq0 = 0;
+                    Log.e(TAG, dbg + ": Error on secondChanelOffset");
+                }
+            } else {
+                result.centerFreq0  = 0;
+                result.centerFreq1  = 0;
+            }
+            result.centerFreq1  = 0;
+        }
+        if(DBG) {
+            Log.d(TAG, dbg + "SSID: " + result.SSID + " ChannelWidth is: " + result.channelWidth +
+                    " PrimaryFreq: " + result.frequency +" mCenterfreq0: " + result.centerFreq0 +
+                    " mCenterfreq1: " + result.centerFreq1 + (is80211McRTTResponder ?
+                    "Support RTT reponder: " : "Do not support RTT responder"));
+        }
+
+        result.informationElements = elements;
+    }
+
+    synchronized static void onFullScanResult(int id, ScanResult result, byte bytes[]) {
+        if (DBG) Log.i(TAG, "Got a full scan results event, ssid = " + result.SSID + ", " +
+                "num = " + bytes.length);
+
+        if (sScanEventHandler == null) {
+            return;
+        }
+        populateScanResult(result, bytes, " onFullScanResult ");
+
+        sScanEventHandler.onFullScanResult(result);
+    }
+
+    private static int sScanCmdId = 0;
+    private static ScanEventHandler sScanEventHandler;
+    private static ScanSettings sScanSettings;
+
+    synchronized public static boolean startScan(
+            ScanSettings settings, ScanEventHandler eventHandler) {
+        synchronized (mLock) {
+            if (isHalStarted()) {
+
+                if (sScanCmdId != 0) {
+                    stopScan();
+                } else if (sScanSettings != null || sScanEventHandler != null) {
+                /* current scan is paused; no need to stop it */
+                }
+
+                sScanCmdId = getNewCmdIdLocked();
+
+                sScanSettings = settings;
+                sScanEventHandler = eventHandler;
+
+                if (startScanNative(sWlan0Index, sScanCmdId, settings) == false) {
+                    sScanEventHandler = null;
+                    sScanSettings = null;
+                    sScanCmdId = 0;
+                    return false;
+                }
+
+                return true;
+            } else {
+                return false;
+            }
+        }
+    }
+
+    synchronized public static void stopScan() {
+        synchronized (mLock) {
+            if (isHalStarted()) {
+                stopScanNative(sWlan0Index, sScanCmdId);
+                sScanSettings = null;
+                sScanEventHandler = null;
+                sScanCmdId = 0;
+            }
+        }
+    }
+
+    synchronized public static void pauseScan() {
+        synchronized (mLock) {
+            if (isHalStarted()) {
+                if (sScanCmdId != 0 && sScanSettings != null && sScanEventHandler != null) {
+                    Log.d(TAG, "Pausing scan");
+                    WifiScanner.ScanData scanData[] = getScanResultsNative(sWlan0Index, true);
+                    stopScanNative(sWlan0Index, sScanCmdId);
+                    sScanCmdId = 0;
+                    sScanEventHandler.onScanPaused(scanData);
+                }
+            }
+        }
+    }
+
+    synchronized public static void restartScan() {
+        synchronized (mLock) {
+            if (isHalStarted()) {
+                if (sScanCmdId == 0 && sScanSettings != null && sScanEventHandler != null) {
+                    Log.d(TAG, "Restarting scan");
+                    ScanEventHandler handler = sScanEventHandler;
+                    ScanSettings settings = sScanSettings;
+                    if (startScan(sScanSettings, sScanEventHandler)) {
+                        sScanEventHandler.onScanRestarted();
+                    } else {
+                    /* we are still paused; don't change state */
+                        sScanEventHandler = handler;
+                        sScanSettings = settings;
+                    }
+                }
+            }
+        }
+    }
+
+    synchronized public static WifiScanner.ScanData[] getScanResults(boolean flush) {
+        synchronized (mLock) {
+            if (isHalStarted()) {
+                return getScanResultsNative(sWlan0Index, flush);
+            } else {
+                return null;
+            }
+        }
+    }
+
+    public static interface HotlistEventHandler {
+        void onHotlistApFound (ScanResult[] result);
+        void onHotlistApLost  (ScanResult[] result);
+    }
+
+    private static int sHotlistCmdId = 0;
+    private static HotlistEventHandler sHotlistEventHandler;
+
+    private native static boolean setHotlistNative(int iface, int id,
+            WifiScanner.HotlistSettings settings);
+    private native static boolean resetHotlistNative(int iface, int id);
+
+    synchronized public static boolean setHotlist(WifiScanner.HotlistSettings settings,
+                                    HotlistEventHandler eventHandler) {
+        synchronized (mLock) {
+            if (isHalStarted()) {
+                if (sHotlistCmdId != 0) {
+                    return false;
+                } else {
+                    sHotlistCmdId = getNewCmdIdLocked();
+                }
+
+                sHotlistEventHandler = eventHandler;
+                if (setHotlistNative(sWlan0Index, sHotlistCmdId, settings) == false) {
+                    sHotlistEventHandler = null;
+                    return false;
+                }
+
+                return true;
+            } else {
+                return false;
+            }
+        }
+    }
+
+    synchronized public static void resetHotlist() {
+        synchronized (mLock) {
+            if (isHalStarted()) {
+                if (sHotlistCmdId != 0) {
+                    resetHotlistNative(sWlan0Index, sHotlistCmdId);
+                    sHotlistCmdId = 0;
+                    sHotlistEventHandler = null;
+                }
+            }
+        }
+    }
+
+    synchronized public static void onHotlistApFound(int id, ScanResult[] results) {
+        synchronized (mLock) {
+            if (isHalStarted()) {
+                if (sHotlistCmdId != 0) {
+                    sHotlistEventHandler.onHotlistApFound(results);
+                } else {
+                /* this can happen because of race conditions */
+                    Log.d(TAG, "Ignoring hotlist AP found event");
+                }
+            }
+        }
+    }
+
+    synchronized public static void onHotlistApLost(int id, ScanResult[] results) {
+        synchronized (mLock) {
+            if (isHalStarted()) {
+                if (sHotlistCmdId != 0) {
+                    sHotlistEventHandler.onHotlistApLost(results);
+                } else {
+                /* this can happen because of race conditions */
+                    Log.d(TAG, "Ignoring hotlist AP lost event");
+                }
+            }
+        }
+    }
+
+    public static interface SignificantWifiChangeEventHandler {
+        void onChangesFound(ScanResult[] result);
+    }
+
+    private static SignificantWifiChangeEventHandler sSignificantWifiChangeHandler;
+    private static int sSignificantWifiChangeCmdId;
+
+    private static native boolean trackSignificantWifiChangeNative(
+            int iface, int id, WifiScanner.WifiChangeSettings settings);
+    private static native boolean untrackSignificantWifiChangeNative(int iface, int id);
+
+    synchronized public static boolean trackSignificantWifiChange(
+            WifiScanner.WifiChangeSettings settings, SignificantWifiChangeEventHandler handler) {
+        synchronized (mLock) {
+            if (isHalStarted()) {
+                if (sSignificantWifiChangeCmdId != 0) {
+                    return false;
+                } else {
+                    sSignificantWifiChangeCmdId = getNewCmdIdLocked();
+                }
+
+                sSignificantWifiChangeHandler = handler;
+                if (trackSignificantWifiChangeNative(sWlan0Index, sScanCmdId, settings) == false) {
+                    sSignificantWifiChangeHandler = null;
+                    return false;
+                }
+
+                return true;
+            } else {
+                return false;
+            }
+
+        }
+    }
+
+    synchronized static void untrackSignificantWifiChange() {
+        synchronized (mLock) {
+            if (isHalStarted()) {
+                if (sSignificantWifiChangeCmdId != 0) {
+                    untrackSignificantWifiChangeNative(sWlan0Index, sSignificantWifiChangeCmdId);
+                    sSignificantWifiChangeCmdId = 0;
+                    sSignificantWifiChangeHandler = null;
+                }
+            }
+        }
+    }
+
+    synchronized static void onSignificantWifiChange(int id, ScanResult[] results) {
+        synchronized (mLock) {
+            if (sSignificantWifiChangeCmdId != 0) {
+                sSignificantWifiChangeHandler.onChangesFound(results);
+            } else {
+            /* this can happen because of race conditions */
+                Log.d(TAG, "Ignoring significant wifi change");
+            }
+        }
+    }
+
+    synchronized public static WifiLinkLayerStats getWifiLinkLayerStats(String iface) {
+        // TODO: use correct iface name to Index translation
+        if (iface == null) return null;
+        synchronized (mLock) {
+            if (isHalStarted()) {
+                return getWifiLinkLayerStatsNative(sWlan0Index);
+            } else {
+                return null;
+            }
+        }
+    }
+
+    synchronized public static void setWifiLinkLayerStats(String iface, int enable) {
+        if (iface == null) return;
+        synchronized (mLock) {
+            if (isHalStarted()) {
+                setWifiLinkLayerStatsNative(sWlan0Index, enable);
+            }
+        }
+    }
+
     public static native int getSupportedFeatureSetNative(int iface);
     synchronized public static int getSupportedFeatureSet() {
-        return getSupportedFeatureSetNative(sWlan0Index);
+        synchronized (mLock) {
+            if (isHalStarted()) {
+                return getSupportedFeatureSetNative(sWlan0Index);
+            } else {
+                Log.d(TAG, "Failing getSupportedFeatureset because HAL isn't started");
+                return 0;
+            }
+        }
     }
 
     /* Rtt related commands/events */
@@ -1547,7 +1833,7 @@
             sRttEventHandler.onRttResults(results);
             sRttCmdId = 0;
         } else {
-            Log.d(TAG, "Received event for unknown cmd = " + id + ", current id = " + sRttCmdId);
+            Log.d(TAG, "RTT Received event for unknown cmd = " + id + ", current id = " + sRttCmdId);
         }
     }
 
@@ -1559,25 +1845,39 @@
     synchronized public static boolean requestRtt(
             RttManager.RttParams[] params, RttEventHandler handler) {
         synchronized (mLock) {
-            if (sRttCmdId != 0) {
-                return false;
+            if (isHalStarted()) {
+                if (sRttCmdId != 0) {
+                    Log.v("TAG", "Last one is still under measurement!");
+                    return false;
+                } else {
+                    sRttCmdId = getNewCmdIdLocked();
+                }
+                sRttEventHandler = handler;
+                Log.v(TAG, "native issue RTT request");
+                return requestRangeNative(sWlan0Index, sRttCmdId, params);
             } else {
-                sRttCmdId = getNewCmdIdLocked();
+                return false;
             }
-            sRttEventHandler = handler;
-            return requestRangeNative(sWlan0Index, sRttCmdId, params);
         }
     }
 
     synchronized public static boolean cancelRtt(RttManager.RttParams[] params) {
         synchronized(mLock) {
-            if (sRttCmdId == 0) {
-                return false;
-            }
+            if (isHalStarted()) {
+                if (sRttCmdId == 0) {
+                    return false;
+                }
 
-            if (cancelRangeRequestNative(sWlan0Index, sRttCmdId, params)) {
-                sRttEventHandler = null;
-                return true;
+                sRttCmdId = 0;
+
+                if (cancelRangeRequestNative(sWlan0Index, sRttCmdId, params)) {
+                    sRttEventHandler = null;
+                    Log.v(TAG, "RTT cancel Request Successfully");
+                    return true;
+                } else {
+                    Log.e(TAG, "RTT cancel Request failed");
+                    return false;
+                }
             } else {
                 return false;
             }
@@ -1588,7 +1888,7 @@
 
     synchronized public static boolean setScanningMacOui(byte[] oui) {
         synchronized (mLock) {
-            if (startHal()) {
+            if (isHalStarted()) {
                 return setScanningMacOuiNative(sWlan0Index, oui);
             } else {
                 return false;
@@ -1601,11 +1901,494 @@
 
     synchronized public static int [] getChannelsForBand(int band) {
         synchronized (mLock) {
-            if (startHal()) {
+            if (isHalStarted()) {
                 return getChannelsForBandNative(sWlan0Index, band);
+	    } else {
+                return null;
+            }
+        }
+    }
+
+    private static native boolean isGetChannelsForBandSupportedNative();
+    synchronized public static boolean isGetChannelsForBandSupported(){
+        synchronized (mLock) {
+            if (isHalStarted()) {
+                return isGetChannelsForBandSupportedNative();
+	    } else {
+                return false;
+            }
+        }
+    }
+
+    private static native boolean setDfsFlagNative(int iface, boolean dfsOn);
+    synchronized public static boolean setDfsFlag(boolean dfsOn) {
+        synchronized (mLock) {
+            if (isHalStarted()) {
+                return setDfsFlagNative(sWlan0Index, dfsOn);
+            } else {
+                return false;
+            }
+        }
+    }
+
+    private static native boolean toggleInterfaceNative(int on);
+    synchronized public static boolean toggleInterface(int on) {
+        synchronized (mLock) {
+            if (isHalStarted()) {
+                return toggleInterfaceNative(0);
+            } else {
+                return false;
+            }
+        }
+    }
+
+    private static native RttManager.RttCapabilities getRttCapabilitiesNative(int iface);
+    synchronized public static RttManager.RttCapabilities getRttCapabilities() {
+        synchronized (mLock) {
+            if (isHalStarted()) {
+                return getRttCapabilitiesNative(sWlan0Index);
+            }else {
+                return null;
+            }
+        }
+    }
+
+    private static native boolean setCountryCodeHalNative(int iface, String CountryCode);
+    synchronized public static boolean setCountryCodeHal( String CountryCode) {
+        synchronized (mLock) {
+            if (isHalStarted()) {
+                return setCountryCodeHalNative(sWlan0Index, CountryCode);
+            } else {
+                return false;
+            }
+        }
+    }
+
+    /* Rtt related commands/events */
+    public abstract class TdlsEventHandler {
+        abstract public void onTdlsStatus(String macAddr, int status, int reason);
+    }
+
+    private static TdlsEventHandler sTdlsEventHandler;
+
+    private static native boolean enableDisableTdlsNative(int iface, boolean enable,
+            String macAddr);
+    synchronized public static boolean enableDisableTdls(boolean enable, String macAdd,
+            TdlsEventHandler tdlsCallBack) {
+        synchronized (mLock) {
+            sTdlsEventHandler = tdlsCallBack;
+            return enableDisableTdlsNative(sWlan0Index, enable, macAdd);
+        }
+    }
+
+    // Once TDLS per mac and event feature is implemented, this class definition should be
+    // moved to the right place, like WifiManager etc
+    public static class TdlsStatus {
+        int channel;
+        int global_operating_class;
+        int state;
+        int reason;
+    }
+    private static native TdlsStatus getTdlsStatusNative(int iface, String macAddr);
+    synchronized public static TdlsStatus getTdlsStatus (String macAdd) {
+        synchronized (mLock) {
+            if (isHalStarted()) {
+                return getTdlsStatusNative(sWlan0Index, macAdd);
             } else {
                 return null;
             }
         }
     }
+
+    //ToFix: Once TDLS per mac and event feature is implemented, this class definition should be
+    // moved to the right place, like WifiStateMachine etc
+    public static class TdlsCapabilities {
+        /* Maximum TDLS session number can be supported by the Firmware and hardware */
+        int maxConcurrentTdlsSessionNumber;
+        boolean isGlobalTdlsSupported;
+        boolean isPerMacTdlsSupported;
+        boolean isOffChannelTdlsSupported;
+    }
+
+
+
+    private static native TdlsCapabilities getTdlsCapabilitiesNative(int iface);
+    synchronized public static TdlsCapabilities getTdlsCapabilities () {
+        synchronized (mLock) {
+            if (isHalStarted()) {
+                return getTdlsCapabilitiesNative(sWlan0Index);
+            } else {
+                return null;
+            }
+        }
+    }
+
+    synchronized private static boolean onTdlsStatus(String macAddr, int status, int reason) {
+         if (sTdlsEventHandler == null) {
+             return false;
+         } else {
+             sTdlsEventHandler.onTdlsStatus(macAddr, status, reason);
+             return true;
+         }
+    }
+
+    //---------------------------------------------------------------------------------
+
+    /* Wifi Logger commands/events */
+
+    public static native boolean startLogging(int iface);
+
+    public static interface WifiLoggerEventHandler {
+        void onRingBufferData(RingBufferStatus status, byte[] buffer);
+        void onWifiAlert(int errorCode, byte[] buffer);
+    }
+
+    private static WifiLoggerEventHandler sWifiLoggerEventHandler = null;
+
+    private static void onRingBufferData(RingBufferStatus status, byte[] buffer) {
+        if (sWifiLoggerEventHandler != null)
+            sWifiLoggerEventHandler.onRingBufferData(status, buffer);
+    }
+
+    private static void onWifiAlert(byte[] buffer, int errorCode) {
+        if (sWifiLoggerEventHandler != null)
+            sWifiLoggerEventHandler.onWifiAlert(errorCode, buffer);
+    }
+
+    private static int sLogCmdId = -1;
+    private static native boolean setLoggingEventHandlerNative(int iface, int id);
+    synchronized public static boolean setLoggingEventHandler(WifiLoggerEventHandler handler) {
+        synchronized (mLock) {
+            if (isHalStarted()) {
+                int oldId =  sLogCmdId;
+                sLogCmdId = getNewCmdIdLocked();
+                if (!setLoggingEventHandlerNative(sWlan0Index, sLogCmdId)) {
+                    sLogCmdId = oldId;
+                    return false;
+                }
+                sWifiLoggerEventHandler = handler;
+                return true;
+            } else {
+                return false;
+            }
+        }
+    }
+
+    private static native boolean startLoggingRingBufferNative(int iface, int verboseLevel,
+            int flags, int minIntervalSec ,int minDataSize, String ringName);
+    synchronized public static boolean startLoggingRingBuffer(int verboseLevel, int flags, int maxInterval,
+            int minDataSize, String ringName){
+        synchronized (mLock) {
+            if (isHalStarted()) {
+                return startLoggingRingBufferNative(sWlan0Index, verboseLevel, flags, maxInterval,
+                        minDataSize, ringName);
+            } else {
+                return false;
+            }
+        }
+    }
+
+    private static native int getSupportedLoggerFeatureSetNative(int iface);
+    synchronized public static int getSupportedLoggerFeatureSet() {
+        synchronized (mLock) {
+            if (isHalStarted()) {
+                return getSupportedLoggerFeatureSetNative(sWlan0Index);
+            } else {
+                return 0;
+            }
+        }
+    }
+
+    private static native boolean resetLogHandlerNative(int iface, int id);
+    synchronized public static boolean resetLogHandler() {
+        synchronized (mLock) {
+            if (isHalStarted()) {
+                if (sLogCmdId == -1) {
+                    Log.e(TAG,"Can not reset handler Before set any handler");
+                    return false;
+                }
+                sWifiLoggerEventHandler = null;
+                if (resetLogHandlerNative(sWlan0Index, sLogCmdId)) {
+                    sLogCmdId = -1;
+                    return true;
+                } else {
+                    return false;
+                }
+            } else {
+                return false;
+            }
+        }
+    }
+
+    private static native String getDriverVersionNative(int iface);
+    synchronized public static String getDriverVersion() {
+        synchronized (mLock) {
+            if (isHalStarted()) {
+                return getDriverVersionNative(sWlan0Index);
+            } else {
+                return "";
+            }
+        }
+    }
+
+
+    private static native String getFirmwareVersionNative(int iface);
+    synchronized public static String getFirmwareVersion() {
+        synchronized (mLock) {
+            if (isHalStarted()) {
+                return getFirmwareVersionNative(sWlan0Index);
+            } else {
+                return "";
+            }
+        }
+    }
+
+    public static class RingBufferStatus{
+        String name;
+        int flag;
+        int ringBufferId;
+        int ringBufferByteSize;
+        int verboseLevel;
+        int writtenBytes;
+        int readBytes;
+        int writtenRecords;
+
+        @Override
+        public String toString() {
+            return "name: " + name + " flag: " + flag + " ringBufferId: " + ringBufferId +
+                    " ringBufferByteSize: " +ringBufferByteSize + " verboseLevel: " +verboseLevel +
+                    " writtenBytes: " + writtenBytes + " readBytes: " + readBytes +
+                    " writtenRecords: " + writtenRecords;
+        }
+    }
+
+    private static native RingBufferStatus[] getRingBufferStatusNative(int iface);
+    synchronized public static RingBufferStatus[] getRingBufferStatus() {
+        synchronized (mLock) {
+            if (isHalStarted()) {
+                return getRingBufferStatusNative(sWlan0Index);
+            } else {
+                return null;
+            }
+        }
+    }
+
+    private static native boolean getRingBufferDataNative(int iface, String ringName);
+    synchronized public static boolean getRingBufferData(String ringName) {
+        synchronized (mLock) {
+            if (isHalStarted()) {
+                return getRingBufferDataNative(sWlan0Index, ringName);
+            } else {
+                return false;
+            }
+        }
+    }
+
+    static private byte[] mFwMemoryDump;
+    private static void onWifiFwMemoryAvailable(byte[] buffer) {
+        mFwMemoryDump = buffer;
+        if (DBG) {
+            Log.d(TAG, "onWifiFwMemoryAvailable is called and buffer length is: " +
+                    (buffer == null ? 0 :  buffer.length));
+        }
+    }
+
+    private static native boolean getFwMemoryDumpNative(int iface);
+    synchronized public static byte[] getFwMemoryDump() {
+        synchronized (mLock) {
+            if (isHalStarted()) {
+                if(getFwMemoryDumpNative(sWlan0Index)) {
+                    byte[] fwMemoryDump = mFwMemoryDump;
+                    mFwMemoryDump = null;
+                    return fwMemoryDump;
+                } else {
+                    return null;
+                }
+            }
+
+            return null;
+        }
+    }
+
+    //---------------------------------------------------------------------------------
+    /* Configure ePNO */
+
+    public class WifiPnoNetwork {
+        String SSID;
+        int rssi_threshold;
+        int flags;
+        int auth;
+        String configKey; // kept for reference
+
+        WifiPnoNetwork(WifiConfiguration config, int threshold) {
+            if (config.SSID == null) {
+                this.SSID = "";
+                this.flags = 1;
+            } else {
+                this.SSID = config.SSID;
+            }
+            this.rssi_threshold = threshold;
+            if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
+                auth |= 2;
+            } else if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP) ||
+                    config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X)) {
+                auth |= 4;
+            } else if (config.wepKeys[0] != null) {
+                auth |= 1;
+            } else {
+                auth |= 1;
+            }
+//            auth = 0;
+            flags |= 6; //A and G
+            configKey = config.configKey();
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sbuf = new StringBuilder();
+            sbuf.append(this.SSID);
+            sbuf.append(" flags=").append(this.flags);
+            sbuf.append(" rssi=").append(this.rssi_threshold);
+            sbuf.append(" auth=").append(this.auth);
+            return sbuf.toString();
+        }
+    }
+
+    public static interface WifiPnoEventHandler {
+        void onPnoNetworkFound(ScanResult results[]);
+    }
+
+    private static WifiPnoEventHandler sWifiPnoEventHandler;
+
+    private static int sPnoCmdId = 0;
+
+    private native static boolean setPnoListNative(int iface, int id, WifiPnoNetwork list[]);
+
+    synchronized public static boolean setPnoList(WifiPnoNetwork list[],
+                                                  WifiPnoEventHandler eventHandler) {
+        Log.e(TAG, "setPnoList cmd " + sPnoCmdId);
+
+        synchronized (mLock) {
+            if (isHalStarted()) {
+
+                sPnoCmdId = getNewCmdIdLocked();
+
+                sWifiPnoEventHandler = eventHandler;
+                if (setPnoListNative(sWlan0Index, sPnoCmdId, list)) {
+                    return true;
+                }
+            }
+
+            sWifiPnoEventHandler = null;
+            return false;
+        }
+    }
+
+    synchronized public static void onPnoNetworkFound(int id, ScanResult[] results) {
+
+        if (results == null) {
+            Log.e(TAG, "onPnoNetworkFound null results");
+            return;
+
+        }
+        Log.d(TAG, "WifiNative.onPnoNetworkFound result " + results.length);
+
+        //Log.e(TAG, "onPnoNetworkFound length " + results.length);
+        //return;
+        for (int i=0; i<results.length; i++) {
+            Log.e(TAG, "onPnoNetworkFound SSID " + results[i].SSID
+                    + " " + results[i].level + " " + results[i].frequency);
+
+            populateScanResult(results[i], results[i].bytes, "onPnoNetworkFound ");
+            results[i].wifiSsid = WifiSsid.createFromAsciiEncoded(results[i].SSID);
+        }
+        synchronized (mLock) {
+            if (sPnoCmdId != 0 && sWifiPnoEventHandler != null) {
+                sWifiPnoEventHandler.onPnoNetworkFound(results);
+            } else {
+                /* this can happen because of race conditions */
+                Log.d(TAG, "Ignoring Pno Network found event");
+            }
+        }
+    }
+
+    public class WifiLazyRoamParams {
+        int A_band_boost_threshold;
+        int A_band_penalty_threshold;
+        int A_band_boost_factor;
+        int A_band_penalty_factor;
+        int A_band_max_boost;
+        int lazy_roam_hysteresis;
+        int alert_roam_rssi_trigger;
+
+        WifiLazyRoamParams() {
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sbuf = new StringBuilder();
+            sbuf.append(" A_band_boost_threshold=").append(this.A_band_boost_threshold);
+            sbuf.append(" A_band_penalty_threshold=").append(this.A_band_penalty_threshold);
+            sbuf.append(" A_band_boost_factor=").append(this.A_band_boost_factor);
+            sbuf.append(" A_band_penalty_factor=").append(this.A_band_penalty_factor);
+            sbuf.append(" A_band_max_boost=").append(this.A_band_max_boost);
+            sbuf.append(" lazy_roam_hysteresis=").append(this.lazy_roam_hysteresis);
+            sbuf.append(" alert_roam_rssi_trigger=").append(this.alert_roam_rssi_trigger);
+            return sbuf.toString();
+        }
+    }
+
+    private native static boolean setLazyRoamNative(int iface, int id,
+                                              boolean enabled, WifiLazyRoamParams param);
+
+    synchronized public static boolean setLazyRoam(boolean enabled, WifiLazyRoamParams params) {
+        synchronized (mLock) {
+            if (isHalStarted()) {
+                sPnoCmdId = getNewCmdIdLocked();
+                return setLazyRoamNative(sWlan0Index, sPnoCmdId, enabled, params);
+            } else {
+                return false;
+            }
+        }
+    }
+
+    private native static boolean setBssidBlacklistNative(int iface, int id,
+                                              String list[]);
+
+    synchronized public static boolean setBssidBlacklist(String list[]) {
+        int size = 0;
+        if (list != null) {
+            size = list.length;
+        }
+        Log.e(TAG, "setBssidBlacklist cmd " + sPnoCmdId + " size " + size);
+
+        synchronized (mLock) {
+            if (isHalStarted()) {
+                sPnoCmdId = getNewCmdIdLocked();
+                return setBssidBlacklistNative(sWlan0Index, sPnoCmdId, list);
+            } else {
+                return false;
+            }
+        }
+    }
+
+    private native static boolean setSsidWhitelistNative(int iface, int id, String list[]);
+
+    synchronized public static boolean setSsidWhitelist(String list[]) {
+        int size = 0;
+        if (list != null) {
+            size = list.length;
+        }
+        Log.e(TAG, "setSsidWhitelist cmd " + sPnoCmdId + " size " + size);
+
+        synchronized (mLock) {
+            if (isHalStarted()) {
+                sPnoCmdId = getNewCmdIdLocked();
+
+                return setSsidWhitelistNative(sWlan0Index, sPnoCmdId, list);
+            } else {
+                return false;
+            }
+        }
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiNetworkScoreCache.java b/service/java/com/android/server/wifi/WifiNetworkScoreCache.java
index 0a7df0b..cf14c5f 100644
--- a/service/java/com/android/server/wifi/WifiNetworkScoreCache.java
+++ b/service/java/com/android/server/wifi/WifiNetworkScoreCache.java
@@ -139,7 +139,7 @@
     }
 
      private String buildNetworkKey(ScoredNetwork network) {
-        if (network.networkKey == null) return null;
+        if (network == null || network.networkKey == null) return null;
         if (network.networkKey.wifiKey == null) return null;
         if (network.networkKey.type == NetworkKey.TYPE_WIFI) {
             String key = network.networkKey.wifiKey.ssid;
@@ -153,7 +153,7 @@
     }
 
     private String buildNetworkKey(ScanResult result) {
-        if (result.SSID == null) {
+        if (result == null || result.SSID == null) {
             return null;
         }
         StringBuilder key = new StringBuilder("\"");
diff --git a/service/java/com/android/server/wifi/WifiNotificationController.java b/service/java/com/android/server/wifi/WifiNotificationController.java
index 20d64c9..406a764 100644
--- a/service/java/com/android/server/wifi/WifiNotificationController.java
+++ b/service/java/com/android/server/wifi/WifiNotificationController.java
@@ -65,7 +65,7 @@
     /**
      * The Notification object given to the NotificationManager.
      */
-    private Notification mNotification;
+    private Notification.Builder mNotificationBuilder;
     /**
      * Whether the notification is being shown, as set by us. That is, if the
      * user cancels the notification, we will not receive the callback so this
@@ -229,31 +229,32 @@
                 return;
             }
 
-            if (mNotification == null) {
-                // Cache the Notification object.
-                mNotification = new Notification();
-                mNotification.when = 0;
-                mNotification.icon = ICON_NETWORKS_AVAILABLE;
-                mNotification.flags = Notification.FLAG_AUTO_CANCEL;
-                mNotification.contentIntent = TaskStackBuilder.create(mContext)
-                        .addNextIntentWithParentStack(
-                                new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK))
-                        .getPendingIntent(0, 0, null, UserHandle.CURRENT);
+            if (mNotificationBuilder == null) {
+                // Cache the Notification builder object.
+                mNotificationBuilder = new Notification.Builder(mContext)
+                        .setWhen(0)
+                        .setSmallIcon(ICON_NETWORKS_AVAILABLE)
+                        .setAutoCancel(true)
+                        .setContentIntent(TaskStackBuilder.create(mContext)
+                                .addNextIntentWithParentStack(
+                                        new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK))
+                                .getPendingIntent(0, 0, null, UserHandle.CURRENT))
+                        .setColor(mContext.getResources().getColor(
+                                com.android.internal.R.color.system_notification_accent_color));
             }
 
             CharSequence title = mContext.getResources().getQuantityText(
                     com.android.internal.R.plurals.wifi_available, numNetworks);
             CharSequence details = mContext.getResources().getQuantityText(
                     com.android.internal.R.plurals.wifi_available_detailed, numNetworks);
-            mNotification.tickerText = title;
-            mNotification.color = mContext.getResources().getColor(
-                    com.android.internal.R.color.system_notification_accent_color);
-            mNotification.setLatestEventInfo(mContext, title, details, mNotification.contentIntent);
+            mNotificationBuilder.setTicker(title);
+            mNotificationBuilder.setContentTitle(title);
+            mNotificationBuilder.setContentText(details);
 
             mNotificationRepeatTime = System.currentTimeMillis() + NOTIFICATION_REPEAT_DELAY_MS;
 
-            notificationManager.notifyAsUser(null, ICON_NETWORKS_AVAILABLE, mNotification,
-                    UserHandle.ALL);
+            notificationManager.notifyAsUser(null, ICON_NETWORKS_AVAILABLE,
+                    mNotificationBuilder.build(), UserHandle.ALL);
         } else {
             notificationManager.cancelAsUser(null, ICON_NETWORKS_AVAILABLE, UserHandle.ALL);
         }
diff --git a/service/java/com/android/server/wifi/WifiScanningServiceImpl.java b/service/java/com/android/server/wifi/WifiScanningServiceImpl.java
index 6dacfb4..8f2cd3b 100644
--- a/service/java/com/android/server/wifi/WifiScanningServiceImpl.java
+++ b/service/java/com/android/server/wifi/WifiScanningServiceImpl.java
@@ -16,18 +16,24 @@
 
 package com.android.server.wifi;
 
+import android.Manifest;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
 import android.net.wifi.IWifiScanner;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiScanner;
+import android.net.wifi.WifiScanner.BssidInfo;
+import android.net.wifi.WifiScanner.ChannelSpec;
+import android.net.wifi.WifiScanner.ScanData;
 import android.net.wifi.WifiScanner.ScanSettings;
 import android.net.wifi.WifiSsid;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -36,13 +42,16 @@
 import android.os.Messenger;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.os.WorkSource;
+import android.util.LocalLog;
 import android.util.Log;
-import android.util.Slog;
 
+import com.android.internal.app.IBatteryStats;
 import com.android.internal.util.AsyncChannel;
 import com.android.internal.util.Protocol;
 import com.android.internal.util.StateMachine;
 import com.android.internal.util.State;
+import com.android.server.am.BatteryStatsService;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -51,26 +60,49 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
-import java.util.List;
 import java.util.Map;
 
 public class WifiScanningServiceImpl extends IWifiScanner.Stub {
 
     private static final String TAG = "WifiScanningService";
-    private static final boolean DBG = true;
+    private static final boolean DBG = false;
+    private static final boolean VDBG = false;
+
     private static final int INVALID_KEY = 0;                               // same as WifiScanner
     private static final int MIN_PERIOD_PER_CHANNEL_MS = 200;               // DFS needs 120 ms
+    private static final int UNKNOWN_PID = -1;
+
+    private static final LocalLog mLocalLog = new LocalLog(1024);
+
+    private static void localLog(String message) {
+        mLocalLog.log(message);
+    }
+
+    private static void logw(String message) {
+        Log.w(TAG, message);
+        mLocalLog.log(message);
+    }
+
+    private static void loge(String message) {
+        Log.e(TAG, message);
+        mLocalLog.log(message);
+    }
 
     @Override
     public Messenger getMessenger() {
-        return new Messenger(mClientHandler);
+        if (mClientHandler != null) {
+            return new Messenger(mClientHandler);
+        } else {
+            loge("WifiScanningServiceImpl trying to get messenger w/o initialization");
+            return null;
+        }
     }
 
     @Override
     public Bundle getAvailableChannels(int band) {
-        WifiScanner.ChannelSpec channelSpecs[] = getChannelsForBand(band);
+        ChannelSpec channelSpecs[] = getChannelsForBand(band);
         ArrayList<Integer> list = new ArrayList<Integer>(channelSpecs.length);
-        for (WifiScanner.ChannelSpec channelSpec : channelSpecs) {
+        for (ChannelSpec channelSpec : channelSpecs) {
             list.add(channelSpec.frequency);
         }
         Bundle b = new Bundle();
@@ -78,10 +110,11 @@
         return b;
     }
 
-    private void enforceConnectivityInternalPermission() {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.CONNECTIVITY_INTERNAL,
-                "WifiScanningServiceImpl");
+    private void enforceLocationHardwarePermission(int uid) {
+        mContext.enforcePermission(
+                Manifest.permission.LOCATION_HARDWARE,
+                UNKNOWN_PID, uid,
+                "LocationHardware");
     }
 
     private class ClientHandler extends Handler {
@@ -93,50 +126,53 @@
         @Override
         public void handleMessage(Message msg) {
 
-            if (DBG) Log.d(TAG, "ClientHandler got" + msg);
+            if (DBG) localLog("ClientHandler got" + msg);
 
             switch (msg.what) {
 
                 case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
-                    if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
-                        AsyncChannel c = (AsyncChannel) msg.obj;
-                        if (DBG) Slog.d(TAG, "New client listening to asynchronous messages: " +
-                                msg.replyTo);
-                        ClientInfo cInfo = new ClientInfo(c, msg.replyTo);
-                        mClients.put(msg.replyTo, cInfo);
-                    } else {
-                        Slog.e(TAG, "Client connection failure, error=" + msg.arg1);
-                    }
-                    return;
-                case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
-                    if (msg.arg1 == AsyncChannel.STATUS_SEND_UNSUCCESSFUL) {
-                        Slog.e(TAG, "Send failed, client connection lost");
-                    } else {
-                        if (DBG) Slog.d(TAG, "Client connection lost with reason: " + msg.arg1);
-                    }
-                    if (DBG) Slog.d(TAG, "closing client " + msg.replyTo);
-                    ClientInfo ci = mClients.remove(msg.replyTo);
-                    if (ci != null) {                       /* can be null if send failed above */
-                        ci.cleanup();
+                    if (msg.arg1 != AsyncChannel.STATUS_SUCCESSFUL) {
+                        loge("Client connection failure, error=" + msg.arg1);
                     }
                     return;
                 case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
                     AsyncChannel ac = new AsyncChannel();
                     ac.connect(mContext, this, msg.replyTo);
+                    if (DBG) localLog("New client connected : " + msg.sendingUid + msg.replyTo);
+                    ClientInfo cInfo = new ClientInfo(msg.sendingUid, ac, msg.replyTo);
+                    mClients.put(msg.replyTo, cInfo);
+                    return;
+                case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
+                    if (msg.arg1 == AsyncChannel.STATUS_SEND_UNSUCCESSFUL) {
+                        loge("Send failed, client connection lost");
+                    } else {
+                        if (DBG) localLog("Client connection lost with reason: " + msg.arg1);
+                    }
+                    if (DBG) localLog("closing client " + msg.replyTo);
+                    ClientInfo ci = mClients.remove(msg.replyTo);
+                    if (ci != null) {                       /* can be null if send failed above */
+                        if (DBG) localLog("closing client " + ci.mUid);
+                        ci.cleanup();
+                    }
                     return;
             }
 
-            ClientInfo ci = mClients.get(msg.replyTo);
-            if (ci == null) {
-                Slog.e(TAG, "Could not find client info for message " + msg.replyTo);
-                replyFailed(msg, WifiScanner.REASON_INVALID_LISTENER, "Could not find listener");
+            try {
+                enforceLocationHardwarePermission(msg.sendingUid);
+            } catch (SecurityException e) {
+                localLog("failed to authorize app: " + e);
+                replyFailed(msg, WifiScanner.REASON_NOT_AUTHORIZED, "Not authorized");
                 return;
             }
 
-            try {
-                enforceConnectivityInternalPermission();
-            } catch (SecurityException e) {
-                replyFailed(msg, WifiScanner.REASON_NOT_AUTHORIZED, "Not authorized");
+            if (msg.what == WifiScanner.CMD_GET_SCAN_RESULTS) {
+                mStateMachine.sendMessage(Message.obtain(msg));
+                return;
+            }
+            ClientInfo ci = mClients.get(msg.replyTo);
+            if (ci == null) {
+                loge("Could not find client info for message " + msg.replyTo);
+                replyFailed(msg, WifiScanner.REASON_INVALID_LISTENER, "Could not find listener");
                 return;
             }
 
@@ -144,6 +180,8 @@
                     WifiScanner.CMD_SCAN,
                     WifiScanner.CMD_START_BACKGROUND_SCAN,
                     WifiScanner.CMD_STOP_BACKGROUND_SCAN,
+                    WifiScanner.CMD_START_SINGLE_SCAN,
+                    WifiScanner.CMD_STOP_SINGLE_SCAN,
                     WifiScanner.CMD_SET_HOTLIST,
                     WifiScanner.CMD_RESET_HOTLIST,
                     WifiScanner.CMD_CONFIGURE_WIFI_CHANGE,
@@ -173,10 +211,13 @@
     private static final int CMD_DRIVER_UNLOADED                     = BASE + 7;
     private static final int CMD_SCAN_PAUSED                         = BASE + 8;
     private static final int CMD_SCAN_RESTARTED                      = BASE + 9;
+    private static final int CMD_STOP_SCAN_INTERNAL                  = BASE + 10;
 
     private Context mContext;
     private WifiScanningStateMachine mStateMachine;
     private ClientHandler mClientHandler;
+    private IBatteryStats mBatteryStats;
+    private final WifiNative.ScanCapabilities mScanCapabilities = new WifiNative.ScanCapabilities();
 
     WifiScanningServiceImpl() { }
 
@@ -193,6 +234,7 @@
         mClientHandler = new ClientHandler(thread.getLooper());
         mStateMachine = new WifiScanningStateMachine(thread.getLooper());
         mWifiChangeStateMachine = new WifiChangeStateMachine(thread.getLooper());
+        mBatteryStats = BatteryStatsService.getService();
 
         mContext.registerReceiver(
                 new BroadcastReceiver() {
@@ -200,7 +242,7 @@
                     public void onReceive(Context context, Intent intent) {
                         int state = intent.getIntExtra(
                                 WifiManager.EXTRA_SCAN_AVAILABLE, WifiManager.WIFI_STATE_DISABLED);
-                        if (DBG) Log.d(TAG, "SCAN_AVAILABLE : " + state);
+                        if (DBG) localLog("SCAN_AVAILABLE : " + state);
                         if (state == WifiManager.WIFI_STATE_ENABLED) {
                             mStateMachine.sendMessage(CMD_DRIVER_LOADED);
                         } else if (state == WifiManager.WIFI_STATE_DISABLED) {
@@ -236,62 +278,67 @@
 
         @Override
         public void onScanResultsAvailable() {
-            if (DBG) Log.d(TAG, "onScanResultAvailable event received");
+            if (DBG) localLog("onScanResultAvailable event received");
             sendMessage(CMD_SCAN_RESULTS_AVAILABLE);
         }
 
         @Override
-        public void onSingleScanComplete() {
-            if (DBG) Log.d(TAG, "onSingleScanComplete event received");
+        public void onScanStatus() {
+            if (DBG) localLog("onScanStatus event received");
             sendMessage(CMD_SCAN_RESULTS_AVAILABLE);
         }
 
         @Override
         public void onFullScanResult(ScanResult fullScanResult) {
-            if (DBG) Log.d(TAG, "Full scanresult received");
+            if (DBG) localLog("Full scanresult received");
             sendMessage(CMD_FULL_SCAN_RESULTS, 0, 0, fullScanResult);
         }
 
         @Override
-        public void onScanPaused() {
-            sendMessage(CMD_SCAN_PAUSED);
+        public void onScanPaused(ScanData scanData[]) {
+            sendMessage(CMD_SCAN_PAUSED, scanData);
         }
 
         @Override
         public void onScanRestarted() {
+            if (DBG) localLog("onScanRestarted() event received");
             sendMessage(CMD_SCAN_RESTARTED);
         }
 
         @Override
         public void onHotlistApFound(ScanResult[] results) {
-            if (DBG) Log.d(TAG, "HotlistApFound event received");
+            if (DBG) localLog("HotlistApFound event received");
             sendMessage(CMD_HOTLIST_AP_FOUND, 0, 0, results);
         }
 
         @Override
+        public void onHotlistApLost(ScanResult[] results) {
+            if (DBG) localLog("HotlistApLost event received");
+            sendMessage(CMD_HOTLIST_AP_LOST, 0, 0, results);
+        }
+
+        @Override
         public void onChangesFound(ScanResult[] results) {
-            if (DBG) Log.d(TAG, "onWifiChangesFound event received");
+            if (DBG) localLog("onWifiChangesFound event received");
             sendMessage(CMD_WIFI_CHANGE_DETECTED, 0, 0, results);
         }
 
         class DefaultState extends State {
             @Override
             public void enter() {
-                if (DBG) Log.d(TAG, "DefaultState");
+                if (DBG) localLog("DefaultState");
             }
             @Override
             public boolean processMessage(Message msg) {
 
-                if (DBG) Log.d(TAG, "DefaultState got" + msg);
+                if (DBG) localLog("DefaultState got" + msg);
 
                 ClientInfo ci = mClients.get(msg.replyTo);
 
                 switch (msg.what) {
                     case CMD_DRIVER_LOADED:
-                        if (WifiNative.startHal() && WifiNative.getInterfaces() != 0) {
-                            WifiNative.ScanCapabilities capabilities =
-                                    new WifiNative.ScanCapabilities();
-                            if (WifiNative.getScanCapabilities(capabilities)) {
+                        if (WifiNative.getInterfaces() != 0) {
+                            if (WifiNative.getScanCapabilities(mScanCapabilities)) {
                                 transitionTo(mStartedState);
                             } else {
                                 loge("could not get scan capabilities");
@@ -303,20 +350,23 @@
                     case WifiScanner.CMD_SCAN:
                     case WifiScanner.CMD_START_BACKGROUND_SCAN:
                     case WifiScanner.CMD_STOP_BACKGROUND_SCAN:
+                    case WifiScanner.CMD_START_SINGLE_SCAN:
+                    case WifiScanner.CMD_STOP_SINGLE_SCAN:
                     case WifiScanner.CMD_SET_HOTLIST:
                     case WifiScanner.CMD_RESET_HOTLIST:
                     case WifiScanner.CMD_CONFIGURE_WIFI_CHANGE:
                     case WifiScanner.CMD_START_TRACKING_CHANGE:
                     case WifiScanner.CMD_STOP_TRACKING_CHANGE:
+                    case WifiScanner.CMD_GET_SCAN_RESULTS:
                         replyFailed(msg, WifiScanner.REASON_UNSPECIFIED, "not available");
                         break;
 
                     case CMD_SCAN_RESULTS_AVAILABLE:
-                        if (DBG) log("ignored scan results available event");
+                        if (DBG) localLog("ignored scan results available event");
                         break;
 
                     case CMD_FULL_SCAN_RESULTS:
-                        if (DBG) log("ignored full scan result event");
+                        if (DBG) localLog("ignored full scan result event");
                         break;
 
                     default:
@@ -331,13 +381,13 @@
 
             @Override
             public void enter() {
-                if (DBG) Log.d(TAG, "StartedState");
+                if (DBG) localLog("StartedState");
             }
 
             @Override
             public boolean processMessage(Message msg) {
 
-                if (DBG) Log.d(TAG, "StartedState got" + msg);
+                if (DBG) localLog("StartedState got" + msg);
 
                 ClientInfo ci = mClients.get(msg.replyTo);
 
@@ -350,7 +400,7 @@
                         break;
                     case WifiScanner.CMD_START_BACKGROUND_SCAN:
                         if (addScanRequest(ci, msg.arg2, (ScanSettings) msg.obj)) {
-                            replySucceeded(msg, null);
+                            replySucceeded(msg);
                         } else {
                             replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request");
                         }
@@ -359,18 +409,33 @@
                         removeScanRequest(ci, msg.arg2);
                         break;
                     case WifiScanner.CMD_GET_SCAN_RESULTS:
-                        replySucceeded(msg, getScanResults(ci));
+                        reportScanResults();
+                        replySucceeded(msg);
+                        break;
+                    case WifiScanner.CMD_START_SINGLE_SCAN:
+                        if (addSingleScanRequest(ci, msg.arg2, (ScanSettings) msg.obj)) {
+                            replySucceeded(msg);
+                        } else {
+                            replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request");
+                        }
+                        break;
+                    case WifiScanner.CMD_STOP_SINGLE_SCAN:
+                        removeScanRequest(ci, msg.arg2);
+                        break;
+                    case CMD_STOP_SCAN_INTERNAL:
+                        localLog("Removing single shot scan");
+                        removeScanRequest((ClientInfo) msg.obj, msg.arg2);
                         break;
                     case WifiScanner.CMD_SET_HOTLIST:
                         setHotlist(ci, msg.arg2, (WifiScanner.HotlistSettings) msg.obj);
-                        replySucceeded(msg, null);
+                        replySucceeded(msg);
                         break;
                     case WifiScanner.CMD_RESET_HOTLIST:
                         resetHotlist(ci, msg.arg2);
                         break;
                     case WifiScanner.CMD_START_TRACKING_CHANGE:
                         trackWifiChanges(ci, msg.arg2);
-                        replySucceeded(msg, null);
+                        replySucceeded(msg);
                         break;
                     case WifiScanner.CMD_STOP_TRACKING_CHANGE:
                         untrackWifiChanges(ci, msg.arg2);
@@ -379,7 +444,7 @@
                         configureWifiChange((WifiScanner.WifiChangeSettings) msg.obj);
                         break;
                     case CMD_SCAN_RESULTS_AVAILABLE: {
-                            ScanResult[] results = WifiNative.getScanResults();
+                            ScanData[] results = WifiNative.getScanResults(/* flush = */ true);
                             Collection<ClientInfo> clients = mClients.values();
                             for (ClientInfo ci2 : clients) {
                                 ci2.reportScanResults(results);
@@ -388,7 +453,7 @@
                         break;
                     case CMD_FULL_SCAN_RESULTS: {
                             ScanResult result = (ScanResult) msg.obj;
-                            if (DBG) Log.d(TAG, "reporting fullscan result for " + result.SSID);
+                            if (DBG) localLog("reporting fullscan result for " + result.SSID);
                             Collection<ClientInfo> clients = mClients.values();
                             for (ClientInfo ci2 : clients) {
                                 ci2.reportFullScanResult(result);
@@ -398,10 +463,19 @@
 
                     case CMD_HOTLIST_AP_FOUND: {
                             ScanResult[] results = (ScanResult[])msg.obj;
-                            if (DBG) Log.d(TAG, "Found " + results.length + " results");
+                            if (DBG) localLog("Found " + results.length + " results");
                             Collection<ClientInfo> clients = mClients.values();
                             for (ClientInfo ci2 : clients) {
-                                ci2.reportHotlistResults(results);
+                                ci2.reportHotlistResults(WifiScanner.CMD_AP_FOUND, results);
+                            }
+                        }
+                        break;
+                    case CMD_HOTLIST_AP_LOST: {
+                            ScanResult[] results = (ScanResult[])msg.obj;
+                            if (DBG) localLog("Lost " + results.length + " results");
+                            Collection<ClientInfo> clients = mClients.values();
+                            for (ClientInfo ci2 : clients) {
+                                ci2.reportHotlistResults(WifiScanner.CMD_AP_LOST, results);
                             }
                         }
                         break;
@@ -415,7 +489,15 @@
                             reportWifiStabilized(results);
                         }
                         break;
-
+                    case CMD_SCAN_PAUSED: {
+                            ScanData results[] = (ScanData[]) msg.obj;
+                            Collection<ClientInfo> clients = mClients.values();
+                            for (ClientInfo ci2 : clients) {
+                                ci2.reportScanResults(results);
+                            }
+                            transitionTo(mPausedState);
+                        }
+                        break;
                     default:
                         return NOT_HANDLED;
                 }
@@ -427,13 +509,13 @@
         class PausedState extends State {
             @Override
             public void enter() {
-                if (DBG) Log.d(TAG, "PausedState");
+                if (DBG) localLog("PausedState");
             }
 
             @Override
             public boolean processMessage(Message msg) {
 
-                if (DBG) Log.d(TAG, "PausedState got" + msg);
+                if (DBG) localLog("PausedState got" + msg);
 
                 switch (msg.what) {
                     case CMD_SCAN_RESTARTED:
@@ -450,11 +532,17 @@
 
         @Override
         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-            super.dump(fd, pw, args);
             pw.println("number of clients : " + mClients.size());
+            for (ClientInfo client : mClients.values()) {
+                client.dump(fd, pw, args);
+                pw.append("------\n");
+            }
             pw.println();
+            pw.println("localLog : ");
+            mLocalLog.dump(fd, pw, args);
+            pw.append("\n\n");
+            super.dump(fd, pw, args);
         }
-
     }
 
     /* client management */
@@ -464,28 +552,93 @@
         private static final int MAX_LIMIT = 16;
         private final AsyncChannel mChannel;
         private final Messenger mMessenger;
+        private final int mUid;
+        private final WorkSource mWorkSource;
+        private boolean mScanWorkReported = false;
 
-        ClientInfo(AsyncChannel c, Messenger m) {
+        ClientInfo(int uid, AsyncChannel c, Messenger m) {
             mChannel = c;
             mMessenger = m;
-            if (DBG) Slog.d(TAG, "New client, channel: " + c + " messenger: " + m);
+            mUid = uid;
+            mWorkSource = new WorkSource(uid, TAG);
+            if (DBG) localLog("New client, channel: " + c + " messenger: " + m);
+        }
+
+        void reportBatchedScanStart() {
+            if (mUid == 0)
+                return;
+
+            int csph = getCsph();
+
+            try {
+                mBatteryStats.noteWifiBatchedScanStartedFromSource(mWorkSource, csph);
+                localLog("started scanning for UID " + mUid + ", csph = " + csph);
+            } catch (RemoteException e) {
+                logw("failed to report scan work: " + e.toString());
+            }
+        }
+
+        void reportBatchedScanStop() {
+            if (mUid == 0)
+                return;
+
+            try {
+                mBatteryStats.noteWifiBatchedScanStoppedFromSource(mWorkSource);
+                localLog("stopped scanning for UID " + mUid);
+            } catch (RemoteException e) {
+                logw("failed to cleanup scan work: " + e.toString());
+            }
+        }
+
+        int getCsph() {
+            int csph = 0;
+            for (ScanSettings settings : getScanSettings()) {
+                int num_channels = settings.channels == null ? 0 : settings.channels.length;
+                if (num_channels == 0 && settings.band != 0) {
+                    num_channels = getChannelsForBand(settings.band).length;
+                }
+
+                int scans_per_Hour = settings.periodInMs == 0 ? 1 : (3600 * 1000) / settings.periodInMs;
+                csph += num_channels * scans_per_Hour;
+            }
+
+            return csph;
+        }
+
+        void reportScanWorkUpdate() {
+            if (mScanWorkReported) {
+                reportBatchedScanStop();
+                mScanWorkReported = false;
+            }
+            if (mScanSettings.isEmpty() == false) {
+                reportBatchedScanStart();
+                mScanWorkReported = true;
+            }
         }
 
         @Override
         public String toString() {
             StringBuffer sb = new StringBuffer();
             sb.append("mChannel ").append(mChannel).append("\n");
-            sb.append("mMessenger ").append(mMessenger).append("\n");
+            sb.append("mMessenger ").append(mMessenger);
+            return sb.toString();
+        }
+
+        void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            StringBuilder sb = new StringBuilder();
+            sb.append(toString());
 
             Iterator<Map.Entry<Integer, ScanSettings>> it = mScanSettings.entrySet().iterator();
             for (; it.hasNext(); ) {
                 Map.Entry<Integer, ScanSettings> entry = it.next();
-                sb.append("[ScanId ").append(entry.getKey()).append("\n");
-                sb.append("ScanSettings ").append(entry.getValue()).append("\n");
-                sb.append("]");
+                sb.append("ScanId ").append(entry.getKey()).append("\n");
+
+                ScanSettings scanSettings = entry.getValue();
+                sb.append(describe(scanSettings));
+                sb.append("\n");
             }
 
-            return sb.toString();
+            pw.println(sb.toString());
         }
 
         HashMap<Integer, ScanSettings> mScanSettings = new HashMap<Integer, ScanSettings>(4);
@@ -493,13 +646,19 @@
 
         void addScanRequest(ScanSettings settings, int id) {
             mScanSettings.put(id, settings);
+            reportScanWorkUpdate();
         }
 
         void removeScanRequest(int id) {
-            mScanSettings.remove(id);
+            ScanSettings settings = mScanSettings.remove(id);
+            if (settings != null && settings.periodInMs == 0) {
+                /* this was a single shot scan */
+                mChannel.sendMessage(WifiScanner.CMD_SINGLE_SCAN_COMPLETED, 0, id);
+            }
+            reportScanWorkUpdate();
         }
 
-        Iterator<Map.Entry<Integer, WifiScanner.ScanSettings>> getScans() {
+        Iterator<Map.Entry<Integer, ScanSettings>> getScans() {
             return mScanSettings.entrySet().iterator();
         }
 
@@ -507,7 +666,7 @@
             return mScanSettings.values();
         }
 
-        void reportScanResults(ScanResult[] results) {
+        void reportScanResults(ScanData[] results) {
             Iterator<Integer> it = mScanSettings.keySet().iterator();
             while (it.hasNext()) {
                 int handler = it.next();
@@ -515,9 +674,9 @@
             }
         }
 
-        void reportScanResults(ScanResult[] results, int handler) {
+        void reportScanResults(ScanData[] results, int handler) {
             ScanSettings settings = mScanSettings.get(handler);
-            WifiScanner.ChannelSpec desiredChannels[] = settings.channels;
+            ChannelSpec desiredChannels[] = settings.channels;
             if (settings.band != WifiScanner.WIFI_BAND_UNSPECIFIED
                     || desiredChannels == null || desiredChannels.length == 0)  {
                 desiredChannels = getChannelsForBand(settings.band);
@@ -525,42 +684,62 @@
 
             // check the channels this client asked for ..
             int num_results = 0;
-            for (ScanResult result : results) {
-                for (WifiScanner.ChannelSpec channelSpec : desiredChannels) {
-                    if (channelSpec.frequency == result.frequency) {
+            for (ScanData result : results) {
+                boolean copyScanData = false;
+                for (ScanResult scanResult : result.getResults()) {
+                    for (ChannelSpec channelSpec : desiredChannels) {
+                        if (channelSpec.frequency == scanResult.frequency) {
+                            copyScanData = true;
+                            break;
+                        }
+                    }
+                    if (copyScanData) {
                         num_results++;
                         break;
                     }
                 }
             }
 
-            if (num_results == 0) {
-                // nothing to report
-                return;
-            }
+            localLog("results = " + results.length + ", num_results = " + num_results);
 
-            ScanResult results2[] = new ScanResult[num_results];
+            ScanData results2[] = new ScanData[num_results];
             int index = 0;
-            for (ScanResult result : results) {
-                for (WifiScanner.ChannelSpec channelSpec : desiredChannels) {
-                    if (channelSpec.frequency == result.frequency) {
-                        WifiSsid wifiSsid = WifiSsid.createFromAsciiEncoded(result.SSID);
-                        ScanResult newResult = new ScanResult(wifiSsid, result.BSSID, "",
-                                result.level, result.frequency, result.timestamp);
-                        results2[index] = newResult;
-                        index++;
+            for (ScanData result : results) {
+                boolean copyScanData = false;
+                for (ScanResult scanResult : result.getResults()) {
+                    for (ChannelSpec channelSpec : desiredChannels) {
+                        if (channelSpec.frequency == scanResult.frequency) {
+                            copyScanData = true;
+                            break;
+                        }
+                    }
+                    if (copyScanData) {
                         break;
                     }
                 }
+
+                if (copyScanData) {
+                    if (VDBG) {
+                        localLog("adding at " + index);
+                    }
+                    results2[index] = new WifiScanner.ScanData(result);
+                    index++;
+                }
             }
+            
+            localLog("delivering results, num = " + results2.length);
 
             deliverScanResults(handler, results2);
+            if (settings.periodInMs == 0) {
+                /* this is a single shot scan; stop the scan now */
+                mStateMachine.sendMessage(CMD_STOP_SCAN_INTERNAL, 0, handler, this);
+            }
         }
 
-        void deliverScanResults(int handler, ScanResult results[]) {
-            WifiScanner.ParcelableScanResults parcelableScanResults =
-                    new WifiScanner.ParcelableScanResults(results);
-            mChannel.sendMessage(WifiScanner.CMD_SCAN_RESULT, 0, handler, parcelableScanResults);
+        void deliverScanResults(int handler, ScanData results[]) {
+            WifiScanner.ParcelableScanData parcelableScanData =
+                    new WifiScanner.ParcelableScanData(results);
+            mChannel.sendMessage(WifiScanner.CMD_SCAN_RESULT, 0, handler, parcelableScanData);
         }
 
         void reportFullScanResult(ScanResult result) {
@@ -568,17 +747,15 @@
             while (it.hasNext()) {
                 int handler = it.next();
                 ScanSettings settings = mScanSettings.get(handler);
-                WifiScanner.ChannelSpec desiredChannels[] = settings.channels;
+                ChannelSpec desiredChannels[] = settings.channels;
                 if (settings.band != WifiScanner.WIFI_BAND_UNSPECIFIED
                         || desiredChannels == null || desiredChannels.length == 0)  {
                     desiredChannels = getChannelsForBand(settings.band);
                 }
-                for (WifiScanner.ChannelSpec channelSpec : desiredChannels) {
+                for (ChannelSpec channelSpec : desiredChannels) {
                     if (channelSpec.frequency == result.frequency) {
-                        WifiSsid wifiSsid = WifiSsid.createFromAsciiEncoded(result.SSID);
-                        ScanResult newResult = new ScanResult(wifiSsid, result.BSSID, "",
-                                result.level, result.frequency, result.timestamp);
-                        if (DBG) Log.d(TAG, "sending it to " + handler);
+                        ScanResult newResult = new ScanResult(result);
+                        if (DBG) localLog("sending it to " + handler);
                         newResult.informationElements = result.informationElements.clone();
                         mChannel.sendMessage(
                                 WifiScanner.CMD_FULL_SCAN_RESULT, 0, handler, newResult);
@@ -614,7 +791,7 @@
             return mHotlistSettings.values();
         }
 
-        void reportHotlistResults(ScanResult[] results) {
+        void reportHotlistResults(int what, ScanResult[] results) {
             Iterator<Map.Entry<Integer, WifiScanner.HotlistSettings>> it =
                     mHotlistSettings.entrySet().iterator();
             while (it.hasNext()) {
@@ -623,7 +800,7 @@
                 WifiScanner.HotlistSettings settings = entry.getValue();
                 int num_results = 0;
                 for (ScanResult result : results) {
-                    for (WifiScanner.BssidInfo BssidInfo : settings.bssidInfos) {
+                    for (BssidInfo BssidInfo : settings.bssidInfos) {
                         if (result.BSSID.equalsIgnoreCase(BssidInfo.bssid)) {
                             num_results++;
                             break;
@@ -639,7 +816,7 @@
                 ScanResult results2[] = new ScanResult[num_results];
                 int index = 0;
                 for (ScanResult result : results) {
-                    for (WifiScanner.BssidInfo BssidInfo : settings.bssidInfos) {
+                    for (BssidInfo BssidInfo : settings.bssidInfos) {
                         if (result.BSSID.equalsIgnoreCase(BssidInfo.bssid)) {
                             results2[index] = result;
                             index++;
@@ -650,7 +827,7 @@
                 WifiScanner.ParcelableScanResults parcelableScanResults =
                         new WifiScanner.ParcelableScanResults(results2);
 
-                mChannel.sendMessage(WifiScanner.CMD_AP_FOUND, 0, handler, parcelableScanResults);
+                mChannel.sendMessage(what, 0, handler, parcelableScanResults);
             }
         }
 
@@ -701,16 +878,15 @@
             }
 
             mSignificantWifiHandlers.clear();
-            Log.d(TAG, "Successfully stopped all requests for client " + this);
+            localLog("Successfully stopped all requests for client " + this);
         }
     }
 
-    void replySucceeded(Message msg, Object obj) {
+    void replySucceeded(Message msg) {
         if (msg.replyTo != null) {
             Message reply = Message.obtain();
             reply.what = WifiScanner.CMD_OP_SUCCEEDED;
             reply.arg2 = msg.arg2;
-            reply.obj = obj;
             try {
                 msg.replyTo.send(reply);
             } catch (RemoteException e) {
@@ -737,9 +913,9 @@
         }
     }
 
-    private static class SettingsComputer {
+    private class SettingsComputer {
 
-        private static class TimeBucket {
+        private class TimeBucket {
             int periodInSecond;
             int periodMinInSecond;
             int periodMaxInSecond;
@@ -751,53 +927,49 @@
             }
         }
 
-        private static final TimeBucket[] mTimeBuckets = new TimeBucket[] {
+        private final TimeBucket[] mTimeBuckets = new TimeBucket[] {
                 new TimeBucket( 1, 0, 5 ),
                 new TimeBucket( 5, 5, 10 ),
                 new TimeBucket( 10, 10, 25 ),
                 new TimeBucket( 30, 25, 55 ),
-                new TimeBucket( 60, 55, 100),
+                new TimeBucket( 60, 55, 240),
                 new TimeBucket( 300, 240, 500),
                 new TimeBucket( 600, 500, 1500),
                 new TimeBucket( 1800, 1500, WifiScanner.MAX_SCAN_PERIOD_MS) };
 
-        private static final int MAX_BUCKETS = 8;
-        private static final int MAX_CHANNELS = 8;
-        private static final int DEFAULT_MAX_AP_PER_SCAN = 10;
-        private static final int DEFAULT_REPORT_THRESHOLD = 10;
+        private static final int MAX_CHANNELS = 32;
         private static final int DEFAULT_BASE_PERIOD_MS = 5000;
+        private static final int DEFAULT_REPORT_THRESHOLD_NUM_SCANS = 10;
+        private static final int DEFAULT_REPORT_THRESHOLD_PERCENT = 100;
 
         private WifiNative.ScanSettings mSettings;
         {
             mSettings = new WifiNative.ScanSettings();
-            mSettings.max_ap_per_scan = DEFAULT_MAX_AP_PER_SCAN;
+            mSettings.max_ap_per_scan = mScanCapabilities.max_ap_cache_per_scan;
             mSettings.base_period_ms = DEFAULT_BASE_PERIOD_MS;
-            mSettings.report_threshold = DEFAULT_REPORT_THRESHOLD;
+            mSettings.report_threshold_percent = DEFAULT_REPORT_THRESHOLD_PERCENT;
+            mSettings.report_threshold_num_scans = DEFAULT_REPORT_THRESHOLD_NUM_SCANS;
 
-            mSettings.buckets = new WifiNative.BucketSettings[MAX_BUCKETS];
+            mSettings.buckets = new WifiNative.BucketSettings[mScanCapabilities.max_scan_buckets];
             for (int i = 0; i < mSettings.buckets.length; i++) {
                 WifiNative.BucketSettings bucketSettings = new WifiNative.BucketSettings();
                 bucketSettings.bucket = i;
                 bucketSettings.report_events = 0;
                 bucketSettings.channels = new WifiNative.ChannelSettings[MAX_CHANNELS];
                 bucketSettings.num_channels = 0;
-                for (int j = 0; j < bucketSettings.channels.length; j++) {
-                    WifiNative.ChannelSettings channelSettings = new WifiNative.ChannelSettings();
-                    bucketSettings.channels[j] = channelSettings;
-                }
                 mSettings.buckets[i] = bucketSettings;
             }
         }
 
         HashMap<Integer, Integer> mChannelToBucketMap = new HashMap<Integer, Integer>();
 
-        private int getBestBucket(WifiScanner.ScanSettings settings) {
+        private int getBestBucket(ScanSettings settings) {
 
             // check to see if any of the channels are being scanned already
             // and find the smallest bucket index (it represents the quickest
             // period of scan)
 
-            WifiScanner.ChannelSpec channels[] = settings.channels;
+            ChannelSpec channels[] = settings.channels;
             if (channels == null) {
                 // set channels based on band
                 channels = getChannelsForBand(settings.band);
@@ -805,13 +977,13 @@
 
             if (channels == null) {
                 // still no channels; then there's nothing to scan
-                Log.e(TAG, "No channels to scan!!");
+                loge("No channels to scan!!");
                 return -1;
             }
 
             int mostFrequentBucketIndex = mTimeBuckets.length;
 
-            for (WifiScanner.ChannelSpec desiredChannelSpec : channels) {
+            for (ChannelSpec desiredChannelSpec : channels) {
                 if (mChannelToBucketMap.containsKey(desiredChannelSpec.frequency)) {
                     int bucket = mChannelToBucketMap.get(desiredChannelSpec.frequency);
                     if (bucket < mostFrequentBucketIndex) {
@@ -832,36 +1004,36 @@
             }
 
             if (mostFrequentBucketIndex < bestBucketIndex) {
-                for (WifiScanner.ChannelSpec desiredChannelSpec : channels) {
+                for (ChannelSpec desiredChannelSpec : channels) {
                     mChannelToBucketMap.put(desiredChannelSpec.frequency, mostFrequentBucketIndex);
                 }
-                Log.d(TAG, "returning mf bucket number " + mostFrequentBucketIndex);
+                localLog("returning mf bucket number " + mostFrequentBucketIndex);
                 return mostFrequentBucketIndex;
             } else if (bestBucketIndex != -1) {
-                for (WifiScanner.ChannelSpec desiredChannelSpec : channels) {
+                for (ChannelSpec desiredChannelSpec : channels) {
                     mChannelToBucketMap.put(desiredChannelSpec.frequency, bestBucketIndex);
                 }
-                Log.d(TAG, "returning best bucket number " + bestBucketIndex);
+                localLog("returning best bucket number " + bestBucketIndex);
                 return bestBucketIndex;
             }
 
-            Log.e(TAG, "Could not find suitable bucket for period " + settings.periodInMs);
+            loge("Could not find suitable bucket for period " + settings.periodInMs);
             return -1;
         }
 
-        void prepChannelMap(WifiScanner.ScanSettings settings) {
+        void prepChannelMap(ScanSettings settings) {
             getBestBucket(settings);
         }
 
-        int addScanRequestToBucket(WifiScanner.ScanSettings settings) {
+        int addScanRequestToBucket(ScanSettings settings) {
 
             int bucketIndex = getBestBucket(settings);
             if (bucketIndex == -1) {
-                Log.e(TAG, "Ignoring invalid settings");
+                loge("Ignoring invalid settings");
                 return -1;
             }
 
-            WifiScanner.ChannelSpec desiredChannels[] = settings.channels;
+            ChannelSpec desiredChannels[] = settings.channels;
             if (settings.band != WifiScanner.WIFI_BAND_UNSPECIFIED
                     || desiredChannels == null
                     || desiredChannels.length == 0) {
@@ -869,28 +1041,29 @@
                 desiredChannels = getChannelsForBand(settings.band);
                 if (desiredChannels == null) {
                     // still no channels; then there's nothing to scan
-                    Log.e(TAG, "No channels to scan!!");
+                    loge("No channels to scan!!");
                     return -1;
                 }
             }
 
             // merge the channel lists for these buckets
-            Log.d(TAG, "merging " + desiredChannels.length + " channels "
-                    + " for period " + settings.periodInMs);
+            localLog("merging " + desiredChannels.length + " channels "
+                    + " for period " + settings.periodInMs
+                    + " maxScans " + settings.maxScansToCache);
 
             WifiNative.BucketSettings bucket = mSettings.buckets[bucketIndex];
             boolean added = (bucket.num_channels == 0)
                     && (bucket.band == WifiScanner.WIFI_BAND_UNSPECIFIED);
-            Log.d(TAG, "existing " + bucket.num_channels + " channels ");
+            localLog("existing " + bucket.num_channels + " channels ");
 
-            HashSet<WifiScanner.ChannelSpec> newChannels = new HashSet<WifiScanner.ChannelSpec>();
-            for (WifiScanner.ChannelSpec desiredChannelSpec : desiredChannels) {
+            HashSet<ChannelSpec> newChannels = new HashSet<ChannelSpec>();
+            for (ChannelSpec desiredChannelSpec : desiredChannels) {
 
-                Log.d(TAG, "desired channel " + desiredChannelSpec.frequency);
+                if (DBG) localLog("desired channel " + desiredChannelSpec.frequency);
 
                 boolean found = false;
-                for (WifiNative.ChannelSettings existingChannelSpec : bucket.channels) {
-                    if (desiredChannelSpec.frequency == existingChannelSpec.frequency) {
+                for (int i = 0; i < bucket.num_channels; i++) {
+                    if (desiredChannelSpec.frequency == bucket.channels[i].frequency) {
                         found = true;
                         break;
                     }
@@ -899,7 +1072,7 @@
                 if (!found) {
                     newChannels.add(desiredChannelSpec);
                 } else {
-                    if (DBG) Log.d(TAG, "Already scanning channel " + desiredChannelSpec.frequency);
+                    if (DBG) localLog("Already scanning channel " + desiredChannelSpec.frequency);
                 }
             }
 
@@ -910,24 +1083,25 @@
                 bucket.band = getBandFromChannels(bucket.channels)
                         | getBandFromChannels(desiredChannels);
                 bucket.channels = new WifiNative.ChannelSettings[0];
-                Log.d(TAG, "switching to using band " + bucket.band);
+                localLog("switching to using band " + bucket.band);
             } else {
-                for (WifiScanner.ChannelSpec desiredChannelSpec : newChannels) {
+                for (ChannelSpec desiredChannelSpec : newChannels) {
 
-                    Log.d(TAG, "adding new channel spec " + desiredChannelSpec.frequency);
+                    localLog("adding new channel spec " + desiredChannelSpec.frequency);
 
-                    WifiNative.ChannelSettings channelSettings = bucket.channels[bucket.num_channels];
+                    WifiNative.ChannelSettings channelSettings = new WifiNative.ChannelSettings();
                     channelSettings.frequency = desiredChannelSpec.frequency;
+                    bucket.channels[bucket.num_channels] = channelSettings;
                     bucket.num_channels++;
                     mChannelToBucketMap.put(bucketIndex, channelSettings.frequency);
                 }
             }
 
             if (bucket.report_events < settings.reportEvents) {
-                if (DBG) Log.d(TAG, "setting report_events to " + settings.reportEvents);
+                if (DBG) localLog("setting report_events to " + settings.reportEvents);
                 bucket.report_events = settings.reportEvents;
             } else {
-                if (DBG) Log.d(TAG, "report_events is " + settings.reportEvents);
+                if (DBG) localLog("report_events is " + settings.reportEvents);
             }
 
             if (added) {
@@ -935,8 +1109,16 @@
                 mSettings.num_buckets++;
             }
 
-            if (mSettings.max_ap_per_scan < settings.numBssidsPerScan) {
-                mSettings.max_ap_per_scan = settings.numBssidsPerScan;
+            if ( settings.numBssidsPerScan != 0) {
+                if (mSettings.max_ap_per_scan > settings.numBssidsPerScan) {
+                    mSettings.max_ap_per_scan = settings.numBssidsPerScan;
+                }
+            }
+
+            if (settings.maxScansToCache != 0) {
+                if (mSettings.report_threshold_num_scans > settings.maxScansToCache) {
+                    mSettings.report_threshold_num_scans = settings.maxScansToCache;
+                }
             }
 
             return bucket.period_ms;
@@ -980,13 +1162,13 @@
         for (ClientInfo ci : clients) {
             Iterator it = ci.getScans();
             while (it.hasNext()) {
-                Map.Entry<Integer, WifiScanner.ScanSettings> entry =
-                        (Map.Entry<Integer,WifiScanner.ScanSettings>)it.next();
+                Map.Entry<Integer, ScanSettings> entry =
+                        (Map.Entry<Integer,ScanSettings>)it.next();
                 int id = entry.getKey();
                 ScanSettings s = entry.getValue();
                 int newPeriodInMs = c.addScanRequestToBucket(s);
                 if (newPeriodInMs  == -1) {
-                    if (DBG) Log.d(TAG, "could not find a good bucket");
+                    if (DBG) localLog("could not find a good bucket");
                     return false;
                 }
                 if (newPeriodInMs != s.periodInMs) {
@@ -999,25 +1181,46 @@
 
         WifiNative.ScanSettings s = c.getComputedSettings();
         if (s.num_buckets == 0) {
-            if (DBG) Log.d(TAG, "Stopping scan because there are no buckets");
+            if (DBG) localLog("Stopping scan because there are no buckets");
             WifiNative.stopScan();
             return true;
         } else {
             if (WifiNative.startScan(s, mStateMachine)) {
-                if (DBG) Log.d(TAG, "Successfully started scan of " + s.num_buckets + " buckets at"
-                        + "time = " + SystemClock.elapsedRealtimeNanos()/1000);
+                localLog("Successfully started scan of " + s.num_buckets + " buckets at"
+                        + "time = " + SystemClock.elapsedRealtimeNanos() / 1000 + " period "
+                        + s.base_period_ms);
                 return true;
             } else {
-                if (DBG) Log.d(TAG, "Failed to start scan of " + s.num_buckets + " buckets");
+                loge("Failed to start scan of " + s.num_buckets + " buckets");
                 return false;
             }
         }
     }
 
+    void logScanRequest(String request, ClientInfo ci, int id, ScanSettings settings) {
+        StringBuffer sb = new StringBuffer();
+        sb.append(request);
+        sb.append("\nClient ");
+        sb.append(ci.toString());
+        sb.append("\nId ");
+        sb.append(id);
+        sb.append("\n");
+        if (settings != null) {
+            sb.append(describe(settings));
+            sb.append("\n");
+        }
+        sb.append("\n");
+        localLog(sb.toString());
+    }
+
     boolean addScanRequest(ClientInfo ci, int handler, ScanSettings settings) {
         // sanity check the input
+        if (ci == null) {
+            Log.d(TAG, "Failing scan request ClientInfo not found " + handler);
+            return false;
+        }
         if (settings.periodInMs < WifiScanner.MIN_SCAN_PERIOD_MS) {
-            Log.d(TAG, "Failing scan request because periodInMs is " + settings.periodInMs);
+            localLog("Failing scan request because periodInMs is " + settings.periodInMs);
             return false;
         }
 
@@ -1040,30 +1243,63 @@
         }
 
         if (settings.periodInMs < minSupportedPeriodMs) {
-            Log.d(TAG, "Failing scan request because minSupportedPeriodMs is "
+            localLog("Failing scan request because minSupportedPeriodMs is "
                     + minSupportedPeriodMs + " but the request wants " + settings.periodInMs);
             return false;
         }
 
+        logScanRequest("addScanRequest", ci, handler, settings);
         ci.addScanRequest(settings, handler);
         if (resetBuckets()) {
             return true;
         } else {
             ci.removeScanRequest(handler);
-            Log.d(TAG, "Failing scan request because failed to reset scan");
+            localLog("Failing scan request because failed to reset scan");
+            return false;
+        }
+    }
+
+    boolean addSingleScanRequest(ClientInfo ci, int handler, ScanSettings settings) {
+        if (ci == null) {
+            Log.d(TAG, "Failing single scan request ClientInfo not found " + handler);
+            return false;
+        }
+        if (settings.reportEvents == 0) {
+            settings.reportEvents = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN;
+        }
+        if (settings.periodInMs == 0) {
+            settings.periodInMs = 10000;        // 10s - although second scan should never happen
+        }
+
+        logScanRequest("addSingleScanRequest", ci, handler, settings);
+        ci.addScanRequest(settings, handler);
+        if (resetBuckets()) {
+            /* reset periodInMs to 0 to indicate single shot scan */
+            settings.periodInMs = 0;
+            return true;
+        } else {
+            ci.removeScanRequest(handler);
+            localLog("Failing scan request because failed to reset scan");
             return false;
         }
     }
 
     void removeScanRequest(ClientInfo ci, int handler) {
-        ci.removeScanRequest(handler);
-        resetBuckets();
+        if (ci != null) {
+            logScanRequest("removeScanRequest", ci, handler, null);
+            ci.removeScanRequest(handler);
+            resetBuckets();
+        }
     }
 
-    ScanResult[] getScanResults(ClientInfo ci) {
-        ScanResult results[] = WifiNative.getScanResults();
-        ci.reportScanResults(results);
-        return results;
+    boolean reportScanResults() {
+        ScanData results[] = WifiNative.getScanResults(/* flush = */ true);
+        Collection<ClientInfo> clients = mClients.values();
+        for (ClientInfo ci2 : clients) {
+            ci2.reportScanResults(results);
+        }
+
+        return true;
     }
 
     void resetHotlist() {
@@ -1080,7 +1316,7 @@
         if (num_hotlist_ap == 0) {
             WifiNative.resetHotlist();
         } else {
-            WifiScanner.BssidInfo bssidInfos[] = new WifiScanner.BssidInfo[num_hotlist_ap];
+            BssidInfo bssidInfos[] = new BssidInfo[num_hotlist_ap];
             int index = 0;
             for (ClientInfo ci : clients) {
                 Collection<WifiScanner.HotlistSettings> settings = ci.getHotlistSettings();
@@ -1220,12 +1456,12 @@
         class DefaultState extends State {
             @Override
             public void enter() {
-                if (DBG) Log.d(TAG, "Entering IdleState");
+                if (DBG) localLog("Entering IdleState");
             }
 
             @Override
             public boolean processMessage(Message msg) {
-                if (DBG) Log.d(TAG, "DefaultState state got " + msg);
+                if (DBG) localLog("DefaultState state got " + msg);
                 switch (msg.what) {
                     case WIFI_CHANGE_CMD_ENABLE :
                         transitionTo(mMovingState);
@@ -1250,24 +1486,24 @@
         class StationaryState extends State {
             @Override
             public void enter() {
-                if (DBG) Log.d(TAG, "Entering StationaryState");
+                if (DBG) localLog("Entering StationaryState");
                 reportWifiStabilized(mCurrentBssids);
             }
 
             @Override
             public boolean processMessage(Message msg) {
-                if (DBG) Log.d(TAG, "Stationary state got " + msg);
+                if (DBG) localLog("Stationary state got " + msg);
                 switch (msg.what) {
                     case WIFI_CHANGE_CMD_ENABLE :
                         // do nothing
                         break;
                     case WIFI_CHANGE_CMD_CHANGE_DETECTED:
-                        if (DBG) Log.d(TAG, "Got wifi change detected");
+                        if (DBG) localLog("Got wifi change detected");
                         reportWifiChanged((ScanResult[])msg.obj);
                         transitionTo(mMovingState);
                         break;
                     case WIFI_CHANGE_CMD_DISABLE:
-                        if (DBG) Log.d(TAG, "Got Disable Wifi Change");
+                        if (DBG) localLog("Got Disable Wifi Change");
                         mCurrentBssids = null;
                         removeScanRequest();
                         untrackSignificantWifiChange();
@@ -1290,29 +1526,30 @@
 
             @Override
             public void enter() {
-                if (DBG) Log.d(TAG, "Entering MovingState");
+                if (DBG) localLog("Entering MovingState");
                 issueFullScan();
             }
 
             @Override
             public boolean processMessage(Message msg) {
-                if (DBG) Log.d(TAG, "MovingState state got " + msg);
+                if (DBG) localLog("MovingState state got " + msg);
                 switch (msg.what) {
                     case WIFI_CHANGE_CMD_ENABLE :
                         // do nothing
                         break;
                     case WIFI_CHANGE_CMD_DISABLE:
-                        if (DBG) Log.d(TAG, "Got Disable Wifi Change");
+                        if (DBG) localLog("Got Disable Wifi Change");
                         mCurrentBssids = null;
                         removeScanRequest();
                         untrackSignificantWifiChange();
                         transitionTo(mDefaultState);
                         break;
                     case WIFI_CHANGE_CMD_NEW_SCAN_RESULTS:
-                        if (DBG) Log.d(TAG, "Got scan results");
+                        if (DBG) localLog("Got scan results");
                         if (mScanResultsPending) {
-                            if (DBG) Log.d(TAG, "reconfiguring scan");
-                            reconfigureScan((ScanResult[])msg.obj, STATIONARY_SCAN_PERIOD_MS);
+                            if (DBG) localLog("reconfiguring scan");
+                            reconfigureScan((ScanData[])msg.obj,
+                                    STATIONARY_SCAN_PERIOD_MS);
                             mWifiChangeDetected = false;
                             mAlarmManager.setExact(AlarmManager.RTC_WAKEUP,
                                     System.currentTimeMillis() + MOVING_STATE_TIMEOUT_MS,
@@ -1321,7 +1558,7 @@
                         }
                         break;
                     case WIFI_CHANGE_CMD_CONFIGURE:
-                        if (DBG) Log.d(TAG, "Got configuration from app");
+                        if (DBG) localLog("Got configuration from app");
                         WifiScanner.WifiChangeSettings settings =
                                 (WifiScanner.WifiChangeSettings) msg.obj;
                         reconfigureScan(settings);
@@ -1333,14 +1570,14 @@
                                 mTimeoutIntent);
                         break;
                     case WIFI_CHANGE_CMD_CHANGE_DETECTED:
-                        if (DBG) Log.d(TAG, "Change detected");
+                        if (DBG) localLog("Change detected");
                         mAlarmManager.cancel(mTimeoutIntent);
                         reportWifiChanged((ScanResult[])msg.obj);
                         mWifiChangeDetected = true;
                         issueFullScan();
                         break;
                     case WIFI_CHANGE_CMD_CHANGE_TIMEOUT:
-                        if (DBG) Log.d(TAG, "Got timeout event");
+                        if (DBG) localLog("Got timeout event");
                         if (mWifiChangeDetected == false) {
                             transitionTo(mStationaryState);
                         }
@@ -1357,8 +1594,8 @@
             }
 
             void issueFullScan() {
-                if (DBG) Log.d(TAG, "Issuing full scan");
-                WifiScanner.ScanSettings settings = new WifiScanner.ScanSettings();
+                if (DBG) localLog("Issuing full scan");
+                ScanSettings settings = new ScanSettings();
                 settings.band = WifiScanner.WIFI_BAND_BOTH;
                 settings.periodInMs = MOVING_SCAN_PERIOD_MS;
                 settings.reportEvents = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN;
@@ -1368,10 +1605,10 @@
 
         }
 
-        void reconfigureScan(ScanResult[] results, int period) {
+        void reconfigureScan(ScanData[] results, int period) {
             // find brightest APs and set them as sentinels
             if (results.length < MAX_APS_TO_TRACK) {
-                Log.d(TAG, "too few APs (" + results.length + ") available to track wifi change");
+                localLog("too few APs (" + results.length + ") available to track wifi change");
                 return;
             }
 
@@ -1379,7 +1616,7 @@
 
             // remove duplicate BSSIDs
             HashMap<String, ScanResult> bssidToScanResult = new HashMap<String, ScanResult>();
-            for (ScanResult result : results) {
+            for (ScanResult result : results[0].getResults()) {
                 ScanResult saved = bssidToScanResult.get(result.BSSID);
                 if (saved == null) {
                     bssidToScanResult.put(result.BSSID, result);
@@ -1418,14 +1655,14 @@
                 }
             }
 
-            if (DBG) Log.d(TAG, "Found " + channels.size() + " channels");
+            if (DBG) localLog("Found " + channels.size() + " channels");
 
             // set scanning schedule
-            WifiScanner.ScanSettings settings = new WifiScanner.ScanSettings();
+            ScanSettings settings = new ScanSettings();
             settings.band = WifiScanner.WIFI_BAND_UNSPECIFIED;
-            settings.channels = new WifiScanner.ChannelSpec[channels.size()];
+            settings.channels = new ChannelSpec[channels.size()];
             for (int i = 0; i < channels.size(); i++) {
-                settings.channels[i] = new WifiScanner.ChannelSpec(channels.get(i));
+                settings.channels[i] = new ChannelSpec(channels.get(i));
             }
 
             settings.periodInMs = period;
@@ -1436,17 +1673,17 @@
             settings2.lostApSampleSize = 3;
             settings2.unchangedSampleSize = 3;
             settings2.minApsBreachingThreshold = 2;
-            settings2.bssidInfos = new WifiScanner.BssidInfo[brightest.length];
+            settings2.bssidInfos = new BssidInfo[brightest.length];
 
             for (int i = 0; i < brightest.length; i++) {
-                WifiScanner.BssidInfo BssidInfo = new WifiScanner.BssidInfo();
+                BssidInfo BssidInfo = new BssidInfo();
                 BssidInfo.bssid = brightest[i].BSSID;
                 int threshold = (100 + brightest[i].level) / 32 + 2;
                 BssidInfo.low = brightest[i].level - threshold;
                 BssidInfo.high = brightest[i].level + threshold;
                 settings2.bssidInfos[i] = BssidInfo;
 
-                if (DBG) Log.d(TAG, "Setting bssid=" + BssidInfo.bssid + ", " +
+                if (DBG) localLog("Setting bssid=" + BssidInfo.bssid + ", " +
                         "low=" + BssidInfo.low + ", high=" + BssidInfo.high);
             }
 
@@ -1457,12 +1694,12 @@
         void reconfigureScan(WifiScanner.WifiChangeSettings settings) {
 
             if (settings.bssidInfos.length < MAX_APS_TO_TRACK) {
-                Log.d(TAG, "too few APs (" + settings.bssidInfos.length
+                localLog("too few APs (" + settings.bssidInfos.length
                         + ") available to track wifi change");
                 return;
             }
 
-            if (DBG) Log.d(TAG, "Setting configuration specified by app");
+            if (DBG) localLog("Setting configuration specified by app");
 
             mCurrentBssids = new ScanResult[settings.bssidInfos.length];
             HashSet<Integer> channels = new HashSet<Integer>();
@@ -1478,12 +1715,12 @@
             removeScanRequest();
 
             // set new scanning schedule
-            WifiScanner.ScanSettings settings2 = new WifiScanner.ScanSettings();
+            ScanSettings settings2 = new ScanSettings();
             settings2.band = WifiScanner.WIFI_BAND_UNSPECIFIED;
-            settings2.channels = new WifiScanner.ChannelSpec[channels.size()];
+            settings2.channels = new ChannelSpec[channels.size()];
             int i = 0;
             for (Integer channel : channels) {
-                settings2.channels[i++] = new WifiScanner.ChannelSpec(channel);
+                settings2.channels[i++] = new ChannelSpec(channel);
             }
 
             settings2.periodInMs = settings.periodInMs;
@@ -1495,11 +1732,11 @@
 
         class ClientInfoLocal extends ClientInfo {
             ClientInfoLocal() {
-                super(null, null);
+                super(0, null, null);
             }
             @Override
-            void deliverScanResults(int handler, ScanResult results[]) {
-                if (DBG) Log.d(TAG, "Delivering messages directly");
+            void deliverScanResults(int handler, ScanData results[]) {
+                if (DBG) localLog("Delivering messages directly");
                 sendMessage(WIFI_CHANGE_CMD_NEW_SCAN_RESULTS, 0, 0, results);
             }
             @Override
@@ -1516,8 +1753,8 @@
         ClientInfo mClientInfo = new ClientInfoLocal();
         private static final int SCAN_COMMAND_ID = 1;
 
-        void addScanRequest(WifiScanner.ScanSettings settings) {
-            if (DBG) Log.d(TAG, "Starting scans");
+        void addScanRequest(ScanSettings settings) {
+            if (DBG) localLog("Starting scans");
             Message msg = Message.obtain();
             msg.what = WifiScanner.CMD_START_BACKGROUND_SCAN;
             msg.arg2 = SCAN_COMMAND_ID;
@@ -1526,7 +1763,7 @@
         }
 
         void removeScanRequest() {
-            if (DBG) Log.d(TAG, "Stopping scans");
+            if (DBG) localLog("Stopping scans");
             Message msg = Message.obtain();
             msg.what = WifiScanner.CMD_STOP_BACKGROUND_SCAN;
             msg.arg2 = SCAN_COMMAND_ID;
@@ -1544,44 +1781,156 @@
 
     }
 
-    private static WifiScanner.ChannelSpec[] getChannelsForBand(int band) {
-        int channels[] = WifiNative.getChannelsForBand(band);
-        if (channels != null) {
-            WifiScanner.ChannelSpec channelSpecs[] = new WifiScanner.ChannelSpec[channels.length];
-            for (int i = 0; i < channels.length; i++) {
-                channelSpecs[i] = new WifiScanner.ChannelSpec(channels[i]);
-            }
-            return channelSpecs;
-        } else {
-            return new WifiScanner.ChannelSpec[0];
+    private static ChannelSpec mChannels[][];
+
+    private static void copyChannels(
+            ChannelSpec channelSpec[], int offset, int channels[]) {
+        for (int i = 0; i < channels.length; i++) {
+            channelSpec[offset +i] = new ChannelSpec(channels[i]);
         }
     }
 
-    private static int getBandFromChannels(WifiScanner.ChannelSpec[] channels) {
+    private static boolean initChannels() {
+        if (mChannels != null) {
+            /* already initialized */
+            return true;
+        }
+
+        int channels24[] = WifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_24_GHZ);
+        if (channels24 == null) {
+            loge("Could not get channels for 2.4 GHz");
+            return false;
+        }
+
+        int channels5[] = WifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ);
+        if (channels5 == null) {
+            loge("Could not get channels for 5 GHz");
+            return false;
+        }
+
+        int channelsDfs[] = WifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY);
+        if (channelsDfs == null) {
+            loge("Could not get channels for DFS");
+            return false;
+        }
+
+        mChannels = new ChannelSpec[8][];
+
+        mChannels[0] = new ChannelSpec[0];
+
+        mChannels[1] = new ChannelSpec[channels24.length];
+        copyChannels(mChannels[1], 0, channels24);
+
+        mChannels[2] = new ChannelSpec[channels5.length];
+        copyChannels(mChannels[2], 0, channels5);
+
+        mChannels[3] = new ChannelSpec[channels24.length + channels5.length];
+        copyChannels(mChannels[3], 0, channels24);
+        copyChannels(mChannels[3], channels24.length, channels5);
+
+        mChannels[4] = new ChannelSpec[channelsDfs.length];
+        copyChannels(mChannels[4], 0, channelsDfs);
+
+        mChannels[5] = new ChannelSpec[channels24.length + channelsDfs.length];
+        copyChannels(mChannels[5], 0, channels24);
+        copyChannels(mChannels[5], channels24.length, channelsDfs);
+
+        mChannels[6] = new ChannelSpec[channels5.length + channelsDfs.length];
+        copyChannels(mChannels[6], 0, channels5);
+        copyChannels(mChannels[6], channels5.length, channelsDfs);
+
+        mChannels[7] = new ChannelSpec[
+                channels24.length + channels5.length + channelsDfs.length];
+        copyChannels(mChannels[7], 0, channels24);
+        copyChannels(mChannels[7], channels24.length, channels5);
+        copyChannels(mChannels[7], channels24.length + channels5.length, channelsDfs);
+
+        return true;
+    }
+
+    private static ChannelSpec[] getChannelsForBand(int band) {
+        initChannels();
+
+        if (band < WifiScanner.WIFI_BAND_24_GHZ || band > WifiScanner.WIFI_BAND_BOTH_WITH_DFS)
+            /* invalid value for band */
+            return mChannels[0];
+        else
+            return mChannels[band];
+    }
+
+    private static boolean isDfs(int channel) {
+        ChannelSpec[] dfsChannels = getChannelsForBand(WifiScanner
+                .WIFI_BAND_5_GHZ_DFS_ONLY);
+        for (int i = 0; i < dfsChannels.length; i++) {
+            if (channel == dfsChannels[i].frequency) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static int getBandFromChannels(ChannelSpec[] channels) {
         int band = WifiScanner.WIFI_BAND_UNSPECIFIED;
-        for (WifiScanner.ChannelSpec channel : channels) {
+        for (ChannelSpec channel : channels) {
             if (2400 <= channel.frequency && channel.frequency < 2500) {
                 band |= WifiScanner.WIFI_BAND_24_GHZ;
+            } else if ( isDfs(channel.frequency)) {
+                band |= WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY;
             } else if (5100 <= channel.frequency && channel.frequency < 6000) {
                 band |= WifiScanner.WIFI_BAND_5_GHZ;
-            } else {
-                /* TODO: Add DFS Range */
             }
         }
         return band;
     }
+
     private static int getBandFromChannels(WifiNative.ChannelSettings[] channels) {
         int band = WifiScanner.WIFI_BAND_UNSPECIFIED;
         for (WifiNative.ChannelSettings channel : channels) {
-            if (2400 <= channel.frequency && channel.frequency < 2500) {
-                band |= WifiScanner.WIFI_BAND_24_GHZ;
-            } else if (5100 <= channel.frequency && channel.frequency < 6000) {
-                band |= WifiScanner.WIFI_BAND_5_GHZ;
-            } else {
-                /* TODO: Add DFS Range */
+            if (channel != null) {
+                if (2400 <= channel.frequency && channel.frequency < 2500) {
+                    band |= WifiScanner.WIFI_BAND_24_GHZ;
+                } else if ( isDfs(channel.frequency)) {
+                    band |= WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY;
+                } else if (5100 <= channel.frequency && channel.frequency < 6000) {
+                    band |= WifiScanner.WIFI_BAND_5_GHZ;
+                }
             }
         }
         return band;
     }
 
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+                != PackageManager.PERMISSION_GRANTED) {
+            pw.println("Permission Denial: can't dump WifiScanner from from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + " without permission "
+                    + android.Manifest.permission.DUMP);
+            return;
+        }
+        mStateMachine.dump(fd, pw, args);
+    }
+
+    static String describe(ScanSettings scanSettings) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("  band:").append(scanSettings.band);
+        sb.append("  period:").append(scanSettings.periodInMs);
+        sb.append("  reportEvents:").append(scanSettings.reportEvents);
+        sb.append("  numBssidsPerScan:").append(scanSettings.numBssidsPerScan);
+        sb.append("  maxScansToCache:").append(scanSettings.maxScansToCache).append("\n");
+
+        sb.append("  channels: ");
+
+        if (scanSettings.channels != null) {
+            for (int i = 0; i < scanSettings.channels.length; i++) {
+                sb.append(scanSettings.channels[i].frequency);
+                sb.append(" ");
+            }
+        }
+        sb.append("\n");
+        return sb.toString();
+    }
+
 }
diff --git a/service/java/com/android/server/wifi/WifiServiceImpl.java b/service/java/com/android/server/wifi/WifiServiceImpl.java
index cc5824e..ca85fbc 100644
--- a/service/java/com/android/server/wifi/WifiServiceImpl.java
+++ b/service/java/com/android/server/wifi/WifiServiceImpl.java
@@ -30,45 +30,71 @@
 import android.net.ConnectivityManager;
 import android.net.DhcpInfo;
 import android.net.DhcpResults;
-import android.net.IpConfiguration.ProxySettings;
-import android.net.LinkAddress;
+import android.net.Network;
+import android.net.NetworkScorerAppManager;
 import android.net.NetworkUtils;
-import android.net.RouteInfo;
-import android.net.wifi.*;
+import android.net.Uri;
+import android.net.wifi.BatchedScanResult;
+import android.net.wifi.BatchedScanSettings;
 import android.net.wifi.IWifiManager;
+import android.net.wifi.ScanResult;
+import android.net.wifi.ScanSettings;
+import android.net.wifi.WifiActivityEnergyInfo;
+import android.net.wifi.WifiChannel;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiConnectionStatistics;
+import android.net.wifi.WifiEnterpriseConfig;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiLinkLayerStats;
+import android.net.wifi.WifiManager;
 import android.os.AsyncTask;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Message;
 import android.os.Messenger;
+import android.os.PowerManager;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.WorkSource;
 import android.provider.Settings;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.Slog;
 
-import java.io.FileNotFoundException;
-import java.io.BufferedReader;
-import java.io.FileDescriptor;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.lang.Override;
-import java.net.InetAddress;
-import java.net.Inet4Address;
-import java.util.ArrayList;
-import java.util.List;
-
 import com.android.internal.R;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.util.AsyncChannel;
 import com.android.server.am.BatteryStatsService;
+import com.android.server.wifi.configparse.ConfigBuilder;
+
+import org.xml.sax.SAXException;
+
+import java.io.BufferedReader;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.cert.CertPath;
+import java.security.cert.CertPathValidator;
+import java.security.cert.CertPathValidatorException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.PKIXParameters;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 
 import static com.android.server.wifi.WifiController.CMD_AIRPLANE_TOGGLED;
 import static com.android.server.wifi.WifiController.CMD_BATTERY_CHANGED;
@@ -89,6 +115,7 @@
 public final class WifiServiceImpl extends IWifiManager.Stub {
     private static final String TAG = "WifiService";
     private static final boolean DBG = true;
+    private static final boolean VDBG = false;
 
     final WifiStateMachine mWifiStateMachine;
 
@@ -109,7 +136,9 @@
     private int mMulticastDisabled;
 
     private final IBatteryStats mBatteryStats;
+    private final PowerManager mPowerManager;
     private final AppOpsManager mAppOps;
+    private final UserManager mUserManager;
 
     private String mInterfaceName;
 
@@ -123,8 +152,6 @@
     /* Tracks the persisted states for wi-fi & airplane mode */
     final WifiSettingsStore mSettingsStore;
 
-    final boolean mBatchedScanSupported;
-
     /**
      * Asynchronous channel to WifiStateMachine
      */
@@ -173,31 +200,21 @@
                     WifiConfiguration config = (WifiConfiguration) msg.obj;
                     int networkId = msg.arg1;
                     if (msg.what == WifiManager.SAVE_NETWORK) {
-                        if (config != null) {
-                            if (config.networkId == WifiConfiguration.INVALID_NETWORK_ID) {
-                                config.creatorUid = Binder.getCallingUid();
-                            } else {
-                                config.lastUpdateUid = Binder.getCallingUid();
-                            }
-                        }
                         Slog.e("WiFiServiceImpl ", "SAVE"
                                 + " nid=" + Integer.toString(networkId)
-                                + " uid=" + Integer.toString(config.creatorUid)
-                                + "/" + Integer.toString(config.lastUpdateUid));
+                                + " uid=" + msg.sendingUid
+                                + " name="
+                                + mContext.getPackageManager().getNameForUid(msg.sendingUid));
                     }
                     if (msg.what == WifiManager.CONNECT_NETWORK) {
-                        if (config != null) {
-                            if (config.networkId == WifiConfiguration.INVALID_NETWORK_ID) {
-                                config.creatorUid = Binder.getCallingUid();
-                            } else {
-                                config.lastUpdateUid = Binder.getCallingUid();
-                            }
-                        }
                         Slog.e("WiFiServiceImpl ", "CONNECT "
                                 + " nid=" + Integer.toString(networkId)
-                                + " uid=" + Binder.getCallingUid());
+                                + " uid=" + msg.sendingUid
+                                + " name="
+                                + mContext.getPackageManager().getNameForUid(msg.sendingUid));
                     }
-                    if (config != null && config.isValid()) {
+
+                    if (config != null && isValid(config)) {
                         if (DBG) Slog.d(TAG, "Connect with config" + config);
                         mWifiStateMachine.sendMessage(Message.obtain(msg));
                     } else if (config == null
@@ -306,7 +323,9 @@
         mWifiStateMachine = new WifiStateMachine(mContext, mInterfaceName, mTrafficPoller);
         mWifiStateMachine.enableRssiPolling(true);
         mBatteryStats = BatteryStatsService.getService();
+        mPowerManager = context.getSystemService(PowerManager.class);
         mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
+        mUserManager = UserManager.get(mContext);
 
         mNotificationController = new WifiNotificationController(mContext, mWifiStateMachine);
         mSettingsStore = new WifiSettingsStore(mContext);
@@ -316,9 +335,6 @@
         mClientHandler = new ClientHandler(wifiThread.getLooper());
         mWifiStateMachineHandler = new WifiStateMachineHandler(wifiThread.getLooper());
         mWifiController = new WifiController(mContext, this, wifiThread.getLooper());
-
-        mBatchedScanSupported = mContext.getResources().getBoolean(
-                R.bool.config_wifi_batched_scan_supported);
     }
 
 
@@ -350,6 +366,8 @@
         // can result in race conditions when apps toggle wifi in the background
         // without active user involvement. Always receive broadcasts.
         registerForBroadcasts();
+        registerForPackageOrUserRemoval();
+        mInIdleMode = mPowerManager.isDeviceIdleMode();
 
         mWifiController.start();
 
@@ -390,6 +408,7 @@
 
     // Start a location scan.
     // L release: A location scan is implemented as a normal scan and avoids scanning DFS channels
+    // Deprecated: Will soon remove implementation
     public void startLocationRestrictedScan(WorkSource workSource) {
         enforceChangePermission();
         enforceLocationHardwarePermission();
@@ -421,6 +440,23 @@
      */
     public void startScan(ScanSettings settings, WorkSource workSource) {
         enforceChangePermission();
+        synchronized (this) {
+            if (mInIdleMode) {
+                // Need to send an immediate scan result broadcast in case the
+                // caller is waiting for a result ..
+
+                // clear calling identity to send broadcast
+                long callingIdentity = Binder.clearCallingIdentity();
+                try {
+                    mWifiStateMachine.sendScanResultsAvailableBroadcast(/* scanSucceeded = */ false);
+                } finally {
+                    // restore calling identity
+                    Binder.restoreCallingIdentity(callingIdentity);
+                }
+                mScanPending = true;
+                return;
+            }
+        }
         if (settings != null) {
             settings = new ScanSettings(settings);
             if (!settings.isValid()) {
@@ -438,42 +474,11 @@
                 settings, workSource);
     }
 
-    private class BatchedScanRequest extends DeathRecipient {
-        final BatchedScanSettings settings;
-        final int uid;
-        final int pid;
-        final WorkSource workSource;
-
-        BatchedScanRequest(BatchedScanSettings settings, IBinder binder, WorkSource ws) {
-            super(0, null, binder, null);
-            this.settings = settings;
-            this.uid = getCallingUid();
-            this.pid = getCallingPid();
-            workSource = ws;
-        }
-        public void binderDied() {
-            stopBatchedScan(settings, uid, pid);
-        }
-        public String toString() {
-            return "BatchedScanRequest{settings=" + settings + ", binder=" + mBinder + "}";
-        }
-
-        public boolean isSameApp(int uid, int pid) {
-            return (this.uid == uid && this.pid == pid);
-        }
-    }
-
-    private final List<BatchedScanRequest> mBatchedScanners = new ArrayList<BatchedScanRequest>();
-
     public boolean isBatchedScanSupported() {
-        return mBatchedScanSupported;
+        return false;
     }
 
-    public void pollBatchedScan() {
-        enforceChangePermission();
-        if (mBatchedScanSupported == false) return;
-        mWifiStateMachine.requestBatchedScanPoll();
-    }
+    public void pollBatchedScan() { }
 
     public String getWpsNfcConfigurationToken(int netId) {
         enforceConnectivityInternalPermission();
@@ -485,145 +490,36 @@
      */
     public boolean requestBatchedScan(BatchedScanSettings requested, IBinder binder,
             WorkSource workSource) {
-        enforceChangePermission();
-        if (workSource != null) {
-            enforceWorkSourcePermission();
-            // WifiManager currently doesn't use names, so need to clear names out of the
-            // supplied WorkSource to allow future WorkSource combining.
-            workSource.clearNames();
-        }
-        if (mBatchedScanSupported == false) return false;
-        requested = new BatchedScanSettings(requested);
-        if (requested.isInvalid()) return false;
-        BatchedScanRequest r = new BatchedScanRequest(requested, binder, workSource);
-        synchronized(mBatchedScanners) {
-            mBatchedScanners.add(r);
-            resolveBatchedScannersLocked();
-        }
-        return true;
+        return false;
     }
 
     public List<BatchedScanResult> getBatchedScanResults(String callingPackage) {
-        enforceAccessPermission();
-        if (mBatchedScanSupported == false) return new ArrayList<BatchedScanResult>();
-        int uid = Binder.getCallingUid();
-        int userId = UserHandle.getCallingUserId();
-        boolean hasInteractUsersFull = checkInteractAcrossUsersFull();
-        long ident = Binder.clearCallingIdentity();
-        try {
-            if (mAppOps.noteOp(AppOpsManager.OP_WIFI_SCAN, uid, callingPackage)
-                    != AppOpsManager.MODE_ALLOWED) {
-                return new ArrayList<BatchedScanResult>();
-            }
-            if (!isCurrentProfile(userId) && !hasInteractUsersFull) {
-                return new ArrayList<BatchedScanResult>();
-            }
-            return mWifiStateMachine.syncGetBatchedScanResultsList();
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
+        return null;
     }
 
-    public void stopBatchedScan(BatchedScanSettings settings) {
-        enforceChangePermission();
-        if (mBatchedScanSupported == false) return;
-        stopBatchedScan(settings, getCallingUid(), getCallingPid());
-    }
+    public void stopBatchedScan(BatchedScanSettings settings) { }
 
-    private void stopBatchedScan(BatchedScanSettings settings, int uid, int pid) {
-        ArrayList<BatchedScanRequest> found = new ArrayList<BatchedScanRequest>();
-        synchronized(mBatchedScanners) {
-            for (BatchedScanRequest r : mBatchedScanners) {
-                if (r.isSameApp(uid, pid) && (settings == null || settings.equals(r.settings))) {
-                    found.add(r);
-                    if (settings != null) break;
-                }
-            }
-            for (BatchedScanRequest r : found) {
-                mBatchedScanners.remove(r);
-            }
-            if (found.size() != 0) {
-                resolveBatchedScannersLocked();
-            }
-        }
-    }
+    boolean mInIdleMode;
+    boolean mScanPending;
 
-    private void resolveBatchedScannersLocked() {
-        BatchedScanSettings setting = new BatchedScanSettings();
-        WorkSource responsibleWorkSource = null;
-        int responsibleUid = 0;
-        double responsibleCsph = 0; // Channel Scans Per Hour
-
-        if (mBatchedScanners.size() == 0) {
-            mWifiStateMachine.setBatchedScanSettings(null, 0, 0, null);
-            return;
-        }
-        for (BatchedScanRequest r : mBatchedScanners) {
-            BatchedScanSettings s = r.settings;
-
-            // evaluate responsibility
-            int currentChannelCount;
-            int currentScanInterval;
-            double currentCsph;
-
-            if (s.channelSet == null || s.channelSet.isEmpty()) {
-                // all channels - 11 B and 9 A channels roughly.
-                currentChannelCount = 9 + 11;
-            } else {
-                currentChannelCount = s.channelSet.size();
-                // these are rough est - no real need to correct for reg-domain;
-                if (s.channelSet.contains("A")) currentChannelCount += (9 - 1);
-                if (s.channelSet.contains("B")) currentChannelCount += (11 - 1);
-
-            }
-            if (s.scanIntervalSec == BatchedScanSettings.UNSPECIFIED) {
-                currentScanInterval = BatchedScanSettings.DEFAULT_INTERVAL_SEC;
-            } else {
-                currentScanInterval = s.scanIntervalSec;
-            }
-            currentCsph = 60 * 60 * currentChannelCount / currentScanInterval;
-
-            if (currentCsph > responsibleCsph) {
-                responsibleUid = r.uid;
-                responsibleWorkSource = r.workSource;
-                responsibleCsph = currentCsph;
-            }
-
-            if (s.maxScansPerBatch != BatchedScanSettings.UNSPECIFIED &&
-                    s.maxScansPerBatch < setting.maxScansPerBatch) {
-                setting.maxScansPerBatch = s.maxScansPerBatch;
-            }
-            if (s.maxApPerScan != BatchedScanSettings.UNSPECIFIED &&
-                    (setting.maxApPerScan == BatchedScanSettings.UNSPECIFIED ||
-                    s.maxApPerScan > setting.maxApPerScan)) {
-                setting.maxApPerScan = s.maxApPerScan;
-            }
-            if (s.scanIntervalSec != BatchedScanSettings.UNSPECIFIED &&
-                    s.scanIntervalSec < setting.scanIntervalSec) {
-                setting.scanIntervalSec = s.scanIntervalSec;
-            }
-            if (s.maxApForDistance != BatchedScanSettings.UNSPECIFIED &&
-                    (setting.maxApForDistance == BatchedScanSettings.UNSPECIFIED ||
-                    s.maxApForDistance > setting.maxApForDistance)) {
-                setting.maxApForDistance = s.maxApForDistance;
-            }
-            if (s.channelSet != null && s.channelSet.size() != 0) {
-                if (setting.channelSet == null || setting.channelSet.size() != 0) {
-                    if (setting.channelSet == null) setting.channelSet = new ArrayList<String>();
-                    for (String i : s.channelSet) {
-                        if (setting.channelSet.contains(i) == false) setting.channelSet.add(i);
+    void handleIdleModeChanged() {
+        boolean doScan = false;
+        synchronized (this) {
+            boolean idle = mPowerManager.isDeviceIdleMode();
+            if (mInIdleMode != idle) {
+                mInIdleMode = idle;
+                if (!idle) {
+                    if (mScanPending) {
+                        mScanPending = false;
+                        doScan = true;
                     }
-                } // else, ignore the constraint - we already use all channels
-            } else {
-                if (setting.channelSet == null || setting.channelSet.size() != 0) {
-                    setting.channelSet = new ArrayList<String>();
                 }
             }
         }
-
-        setting.constrain();
-        mWifiStateMachine.setBatchedScanSettings(setting, responsibleUid, (int)responsibleCsph,
-                responsibleWorkSource);
+        if (doScan) {
+            // Someone requested a scan while we were idle; do a full scan now.
+            startScan(null, null);
+        }
     }
 
     private void enforceAccessPermission() {
@@ -633,7 +529,7 @@
 
     private void enforceChangePermission() {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE,
-                                                "WifiService");
+                "WifiService");
     }
 
     private void enforceLocationHardwarePermission() {
@@ -719,12 +615,11 @@
     public void setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
         enforceChangePermission();
         ConnectivityManager.enforceTetherChangePermission(mContext);
-        UserManager um = UserManager.get(mContext);
-        if (um.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING)) {
+        if (mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING)) {
             throw new SecurityException("DISALLOW_CONFIG_TETHERING is enabled for this user.");
         }
         // null wifiConfig is a meaningful input for CMD_SET_AP
-        if (wifiConfig == null || wifiConfig.isValid()) {
+        if (wifiConfig == null || isValid(wifiConfig)) {
             mWifiController.obtainMessage(CMD_SET_AP, enabled ? 1 : 0, 0, wifiConfig).sendToTarget();
         } else {
             Slog.e(TAG, "Invalid WifiConfiguration");
@@ -754,6 +649,25 @@
     }
 
     /**
+     * see {@link WifiManager#buildWifiConfig()}
+     * @return a WifiConfiguration.
+     */
+    public WifiConfiguration buildWifiConfig(String uriString, String mimeType, byte[] data) {
+        if (mimeType.equals(ConfigBuilder.WifiConfigType)) {
+            try {
+                return ConfigBuilder.buildConfig(uriString, data, mContext);
+            }
+            catch (IOException | GeneralSecurityException | SAXException e) {
+                Log.e(TAG, "Failed to parse wi-fi configuration: " + e);
+            }
+        }
+        else {
+            Log.i(TAG, "Unknown wi-fi config type: " + mimeType);
+        }
+        return null;
+    }
+
+    /**
      * see {@link WifiManager#setWifiApConfiguration(WifiConfiguration)}
      * @param wifiConfig WifiConfiguration details for soft access point
      */
@@ -761,7 +675,7 @@
         enforceChangePermission();
         if (wifiConfig == null)
             return;
-        if (wifiConfig.isValid()) {
+        if (isValid(wifiConfig)) {
             mWifiStateMachine.setWifiApConfiguration(wifiConfig);
         } else {
             Slog.e(TAG, "Invalid WifiConfiguration");
@@ -816,7 +730,7 @@
     }
 
     /**
-     * see {@link android.net.wifi.WifiAdapter#reportActivityInfo}
+     * see {@link android.net.wifi.WifiManager#getControllerActivityEnergyInfo(int)}
      */
     public WifiActivityEnergyInfo reportActivityInfo() {
         enforceAccessPermission();
@@ -825,11 +739,39 @@
         if (mWifiStateMachineChannel != null) {
             stats = mWifiStateMachine.syncGetLinkLayerStats(mWifiStateMachineChannel);
             if (stats != null) {
+                final long rxIdleCurrent = mContext.getResources().getInteger(
+                        com.android.internal.R.integer.config_wifi_idle_receive_cur_ma);
+                final long rxCurrent = mContext.getResources().getInteger(
+                        com.android.internal.R.integer.config_wifi_active_rx_cur_ma);
+                final long txCurrent = mContext.getResources().getInteger(
+                        com.android.internal.R.integer.config_wifi_tx_cur_ma);
+                final double voltage = mContext.getResources().getInteger(
+                        com.android.internal.R.integer.config_wifi_operating_voltage_mv)
+                        / 1000.0;
+
+                final long rxIdleTime = stats.on_time - stats.tx_time - stats.rx_time;
+                final long energyUsed = (long)((stats.tx_time * txCurrent +
+                        stats.rx_time * rxCurrent +
+                        rxIdleTime * rxIdleCurrent) * voltage);
+                if (VDBG || rxIdleTime < 0 || stats.on_time < 0 || stats.tx_time < 0 ||
+                        stats.rx_time < 0 || energyUsed < 0) {
+                    StringBuilder sb = new StringBuilder();
+                    sb.append(" rxIdleCur=" + rxIdleCurrent);
+                    sb.append(" rxCur=" + rxCurrent);
+                    sb.append(" txCur=" + txCurrent);
+                    sb.append(" voltage=" + voltage);
+                    sb.append(" on_time=" + stats.on_time);
+                    sb.append(" tx_time=" + stats.tx_time);
+                    sb.append(" rx_time=" + stats.rx_time);
+                    sb.append(" rxIdleTime=" + rxIdleTime);
+                    sb.append(" energy=" + energyUsed);
+                    Log.e(TAG, " reportActivityInfo: " + sb.toString());
+                }
+
                 // Convert the LinkLayerStats into EnergyActivity
-                energyInfo = new WifiActivityEnergyInfo(
+                energyInfo = new WifiActivityEnergyInfo(SystemClock.elapsedRealtime(),
                         WifiActivityEnergyInfo.STACK_STATE_STATE_IDLE, stats.tx_time,
-                        stats.rx_time, stats.on_time - stats.tx_time - stats.rx_time,
-                        0 /* TBD */);
+                        stats.rx_time, rxIdleTime, energyUsed);
             }
             return energyInfo;
         } else {
@@ -869,15 +811,47 @@
     }
 
     /**
+     * Returns a WifiConfiguration matching this ScanResult
+     * @param scanResult scanResult that represents the BSSID
+     * @return {@link WifiConfiguration} that matches this BSSID or null
+     */
+    public WifiConfiguration getMatchingWifiConfig(ScanResult scanResult) {
+        enforceAccessPermission();
+        return mWifiStateMachine.syncGetMatchingWifiConfig(scanResult, mWifiStateMachineChannel);
+    }
+
+
+    /**
      * see {@link android.net.wifi.WifiManager#addOrUpdateNetwork(WifiConfiguration)}
      * @return the supplicant-assigned identifier for the new or updated
      * network if the operation succeeds, or {@code -1} if it fails
      */
     public int addOrUpdateNetwork(WifiConfiguration config) {
         enforceChangePermission();
-        if (config.isValid()) {
+        if (isValid(config) && isValidPasspoint(config)) {
+
+            WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
+
+            if (config.isPasspoint() &&
+                    (enterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.TLS ||
+                enterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.TTLS)) {
+                try {
+                    verifyCert(enterpriseConfig.getCaCertificate());
+                } catch (CertPathValidatorException cpve) {
+                    Slog.e(TAG, "CA Cert " +
+                            enterpriseConfig.getCaCertificate().getSubjectX500Principal() +
+                            " untrusted: " + cpve.getMessage());
+                    return -1;
+                } catch (GeneralSecurityException | IOException e) {
+                    Slog.e(TAG, "Failed to verify certificate" +
+                            enterpriseConfig.getCaCertificate().getSubjectX500Principal() +
+                            ": " + e);
+                    return -1;
+                }
+            }
+
             //TODO: pass the Uid the WifiStateMachine as a message parameter
-            Slog.e("addOrUpdateNetwork", " uid = " + Integer.toString(Binder.getCallingUid())
+            Slog.i("addOrUpdateNetwork", " uid = " + Integer.toString(Binder.getCallingUid())
                     + " SSID " + config.SSID
                     + " nid=" + Integer.toString(config.networkId));
             if (config.networkId == WifiConfiguration.INVALID_NETWORK_ID) {
@@ -897,7 +871,21 @@
         }
     }
 
-     /**
+    public static void verifyCert(X509Certificate caCert)
+            throws GeneralSecurityException, IOException {
+        CertificateFactory factory = CertificateFactory.getInstance("X.509");
+        CertPathValidator validator =
+                CertPathValidator.getInstance(CertPathValidator.getDefaultType());
+        CertPath path = factory.generateCertPath(
+                Arrays.asList(caCert));
+        KeyStore ks = KeyStore.getInstance("AndroidCAStore");
+        ks.load(null, null);
+        PKIXParameters params = new PKIXParameters(ks);
+        params.setRevocationEnabled(false);
+        validator.validate(path, params);
+    }
+
+    /**
      * See {@link android.net.wifi.WifiManager#removeNetwork(int)}
      * @param netId the integer that identifies the network configuration
      * to the supplicant
@@ -975,9 +963,20 @@
         enforceAccessPermission();
         int userId = UserHandle.getCallingUserId();
         int uid = Binder.getCallingUid();
+        boolean canReadPeerMacAddresses = checkPeersMacAddress();
+        boolean isActiveNetworkScorer =
+                NetworkScorerAppManager.isCallerActiveScorer(mContext, uid);
         boolean hasInteractUsersFull = checkInteractAcrossUsersFull();
         long ident = Binder.clearCallingIdentity();
         try {
+            if (!canReadPeerMacAddresses && !isActiveNetworkScorer
+                    && !isLocationEnabled()) {
+                return new ArrayList<ScanResult>();
+            }
+            if (!canReadPeerMacAddresses && !isActiveNetworkScorer
+                    && !checkCallerCanAccessScanResults(callingPackage, uid)) {
+                return new ArrayList<ScanResult>();
+            }
             if (mAppOps.noteOp(AppOpsManager.OP_WIFI_SCAN, uid, callingPackage)
                     != AppOpsManager.MODE_ALLOWED) {
                 return new ArrayList<ScanResult>();
@@ -991,6 +990,11 @@
         }
     }
 
+    private boolean isLocationEnabled() {
+        return Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.LOCATION_MODE,
+                Settings.Secure.LOCATION_MODE_OFF) != Settings.Secure.LOCATION_MODE_OFF;
+    }
+
     /**
      * Returns true if the caller holds INTERACT_ACROSS_USERS_FULL.
      */
@@ -1001,6 +1005,14 @@
     }
 
     /**
+     * Returns true if the caller holds PEERS_MAC_ADDRESS.
+     */
+    private boolean checkPeersMacAddress() {
+        return mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.PEERS_MAC_ADDRESS) == PackageManager.PERMISSION_GRANTED;
+    }
+
+    /**
      * Returns true if the calling user is the current one or a profile of the
      * current user..
      */
@@ -1009,7 +1021,7 @@
         if (userId == currentUser) {
             return true;
         }
-        List<UserInfo> profiles = UserManager.get(mContext).getProfiles(currentUser);
+        List<UserInfo> profiles = mUserManager.getProfiles(currentUser);
         for (UserInfo user : profiles) {
             if (userId == user.id) {
                 return true;
@@ -1031,7 +1043,7 @@
             if (userId == ownerUser) {
                 return true;
             }
-            List<UserInfo> profiles = UserManager.get(mContext).getProfiles(ownerUser);
+            List<UserInfo> profiles = mUserManager.getProfiles(ownerUser);
             for (UserInfo profile : profiles) {
                 if (userId == profile.id) {
                     return true;
@@ -1083,6 +1095,15 @@
         }
     }
 
+     /**
+     * Get the country code
+     * @return ISO 3166 country code.
+     */
+    public String getCountryCode() {
+        enforceConnectivityInternalPermission();
+        String country = mWifiStateMachine.getCountryCode();
+        return country;
+    }
     /**
      * Set the operational frequency band
      * @param band One of
@@ -1350,6 +1371,8 @@
             } else if (action.equals(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED)) {
                 boolean emergencyMode = intent.getBooleanExtra("phoneinECMState", false);
                 mWifiController.sendMessage(CMD_EMERGENCY_MODE_CHANGED, emergencyMode ? 1 : 0, 0);
+            } else if (action.equals(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)) {
+                handleIdleModeChanged();
             }
         }
     };
@@ -1380,9 +1403,41 @@
         intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
         intentFilter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
         intentFilter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
+        intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
         mContext.registerReceiver(mReceiver, intentFilter);
     }
 
+    private void registerForPackageOrUserRemoval() {
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        intentFilter.addAction(Intent.ACTION_USER_REMOVED);
+        mContext.registerReceiverAsUser(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                switch (intent.getAction()) {
+                    case Intent.ACTION_PACKAGE_REMOVED: {
+                        if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+                            return;
+                        }
+                        int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+                        Uri uri = intent.getData();
+                        if (uid == -1 || uri == null) {
+                            return;
+                        }
+                        String pkgName = uri.getSchemeSpecificPart();
+                        mWifiStateMachine.removeAppConfigs(pkgName, uid);
+                        break;
+                    }
+                    case Intent.ACTION_USER_REMOVED: {
+                        int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
+                        mWifiStateMachine.removeUserConfigs(userHandle);
+                        break;
+                    }
+                }
+            }
+        }, UserHandle.ALL, intentFilter, null, null);
+    }
+
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
@@ -1398,6 +1453,8 @@
                                        Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0));
         pw.println("mMulticastEnabled " + mMulticastEnabled);
         pw.println("mMulticastDisabled " + mMulticastDisabled);
+        pw.println("mInIdleMode " + mInIdleMode);
+        pw.println("mScanPending " + mScanPending);
         mWifiController.dump(fd, pw, args);
         mSettingsStore.dump(fd, pw, args);
         mNotificationController.dump(fd, pw, args);
@@ -1439,6 +1496,12 @@
         pw.println("Locks held:");
         mLocks.dump(pw);
 
+        pw.println("Multicast Locks held:");
+        for (Multicaster l : mMulticasters) {
+            pw.print("    ");
+            pw.println(l);
+        }
+
         mWifiWatchdogStateMachine.dump(fd, pw, args);
         pw.println();
         mWifiStateMachine.dump(fd, pw, args);
@@ -1840,6 +1903,25 @@
         return mWifiStateMachine.getAllowScansWithTraffic();
     }
 
+    public boolean enableAutoJoinWhenAssociated(boolean enabled) {
+        enforceChangePermission();
+        return mWifiStateMachine.enableAutoJoinWhenAssociated(enabled);
+    }
+
+    public boolean getEnableAutoJoinWhenAssociated() {
+        enforceAccessPermission();
+        return mWifiStateMachine.getEnableAutoJoinWhenAssociated();
+    }
+    public void setHalBasedAutojoinOffload(int enabled) {
+        enforceConnectivityInternalPermission();
+        mWifiStateMachine.setHalBasedAutojoinOffload(enabled);
+    }
+
+    public int getHalBasedAutojoinOffload() {
+        enforceAccessPermission();
+        return mWifiStateMachine.getHalBasedAutojoinOffload();
+    }
+
     /* Return the Wifi Connection statistics object */
     public WifiConnectionStatistics getConnectionStatistics() {
         enforceAccessPermission();
@@ -1851,4 +1933,162 @@
             return null;
         }
     }
+
+    public void factoryReset() {
+        enforceConnectivityInternalPermission();
+
+        if (mUserManager.hasUserRestriction(UserManager.DISALLOW_NETWORK_RESET)) {
+            return;
+        }
+
+        if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING)) {
+            // Turn mobile hotspot off
+            setWifiApEnabled(null, false);
+        }
+
+        if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI)) {
+            // Enable wifi
+            setWifiEnabled(true);
+            // Delete all Wifi SSIDs
+            List<WifiConfiguration> networks = getConfiguredNetworks();
+            if (networks != null) {
+                for (WifiConfiguration config : networks) {
+                    removeNetwork(config.networkId);
+                }
+                saveConfiguration();
+            }
+        }
+    }
+
+    /* private methods */
+    static boolean logAndReturnFalse(String s) {
+        Log.d(TAG, s);
+        return false;
+    }
+
+    public static boolean isValid(WifiConfiguration config) {
+        String validity = checkValidity(config);
+        return validity == null || logAndReturnFalse(validity);
+    }
+
+    public static boolean isValidPasspoint(WifiConfiguration config) {
+        String validity = checkPasspointValidity(config);
+        return validity == null || logAndReturnFalse(validity);
+    }
+
+    public static String checkValidity(WifiConfiguration config) {
+        if (config.allowedKeyManagement == null)
+            return "allowed kmgmt";
+
+        if (config.allowedKeyManagement.cardinality() > 1) {
+            if (config.allowedKeyManagement.cardinality() != 2) {
+                return "cardinality != 2";
+            }
+            if (!config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP)) {
+                return "not WPA_EAP";
+            }
+            if ((!config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X))
+                    && (!config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK))) {
+                return "not PSK or 8021X";
+            }
+        }
+        return null;
+    }
+
+    public static String checkPasspointValidity(WifiConfiguration config) {
+        if (!TextUtils.isEmpty(config.FQDN)) {
+            /* this is passpoint configuration; it must not have an SSID */
+            if (!TextUtils.isEmpty(config.SSID)) {
+                return "SSID not expected for Passpoint: '" + config.SSID +
+                        "' FQDN " + toHexString(config.FQDN);
+            }
+            /* this is passpoint configuration; it must have a providerFriendlyName */
+            if (TextUtils.isEmpty(config.providerFriendlyName)) {
+                return "no provider friendly name";
+            }
+            WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
+            /* this is passpoint configuration; it must have enterprise config */
+            if (enterpriseConfig == null
+                    || enterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.NONE ) {
+                return "no enterprise config";
+            }
+            if ((enterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.TLS ||
+                    enterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.TTLS ||
+                    enterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.PEAP) &&
+                    enterpriseConfig.getCaCertificate() == null) {
+                return "no CA certificate";
+            }
+        }
+        return null;
+    }
+
+    public Network getCurrentNetwork() {
+        enforceAccessPermission();
+        return mWifiStateMachine.getCurrentNetwork();
+    }
+
+    public static String toHexString(String s) {
+        if (s == null) {
+            return "null";
+        }
+        StringBuilder sb = new StringBuilder();
+        sb.append('\'').append(s).append('\'');
+        for (int n = 0; n < s.length(); n++) {
+            sb.append(String.format(" %02x", s.charAt(n) & 0xffff));
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Checks that calling process has android.Manifest.permission.ACCESS_COARSE_LOCATION or
+     * android.Manifest.permission.ACCESS_FINE_LOCATION and a corresponding app op is allowed
+     */
+    private boolean checkCallerCanAccessScanResults(String callingPackage, int uid) {
+        if (ActivityManager.checkUidPermission(Manifest.permission.ACCESS_FINE_LOCATION, uid)
+                == PackageManager.PERMISSION_GRANTED
+                && isAppOppAllowed(AppOpsManager.OP_FINE_LOCATION, callingPackage, uid)) {
+            return true;
+        }
+
+        if (ActivityManager.checkUidPermission(Manifest.permission.ACCESS_COARSE_LOCATION, uid)
+                == PackageManager.PERMISSION_GRANTED
+                && isAppOppAllowed(AppOpsManager.OP_COARSE_LOCATION, callingPackage, uid)) {
+            return true;
+        }
+        // Enforce location permission for apps targeting M and later versions
+        boolean enforceLocationPermission = true;
+        try {
+            enforceLocationPermission = mContext.getPackageManager().getApplicationInfo(
+                    callingPackage, 0).targetSdkVersion >= Build.VERSION_CODES.M;
+        } catch (PackageManager.NameNotFoundException e) {
+            // In case of exception, enforce permission anyway
+        }
+        if (enforceLocationPermission) {
+            throw new SecurityException("Need ACCESS_COARSE_LOCATION or "
+                    + "ACCESS_FINE_LOCATION permission to get scan results");
+        }
+        // Pre-M apps running in the foreground should continue getting scan results
+        if (isForegroundApp(callingPackage)) {
+            return true;
+        }
+        Log.e(TAG, "Permission denial: Need ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION "
+                + "permission to get scan results");
+        return false;
+    }
+
+    private boolean isAppOppAllowed(int op, String callingPackage, int uid) {
+        return mAppOps.noteOp(op, uid, callingPackage) == AppOpsManager.MODE_ALLOWED;
+    }
+
+    /**
+     * Return true if the specified package name is a foreground app.
+     *
+     * @param pkgName application package name.
+     */
+    private boolean isForegroundApp(String pkgName) {
+        ActivityManager am = (ActivityManager)mContext.getSystemService(Context.ACTIVITY_SERVICE);
+        List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1);
+        return !tasks.isEmpty() && pkgName.equals(tasks.get(0).topActivity.getPackageName());
+    }
+
 }
diff --git a/service/java/com/android/server/wifi/WifiStateMachine.java b/service/java/com/android/server/wifi/WifiStateMachine.java
index fb9e9fd..4bc62b3 100644
--- a/service/java/com/android/server/wifi/WifiStateMachine.java
+++ b/service/java/com/android/server/wifi/WifiStateMachine.java
@@ -31,6 +31,7 @@
  */
 import static android.net.wifi.WifiManager.WIFI_STATE_UNKNOWN;
 
+import android.Manifest;
 import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
@@ -40,12 +41,18 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.database.ContentObserver;
 import android.net.ConnectivityManager;
 import android.net.DhcpResults;
+import android.net.BaseDhcpStateMachine;
 import android.net.DhcpStateMachine;
+import android.net.Network;
+import android.net.dhcp.DhcpClient;
 import android.net.InterfaceConfiguration;
+import android.net.IpReachabilityMonitor;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.NetworkAgent;
@@ -58,8 +65,6 @@
 import android.net.RouteInfo;
 import android.net.StaticIpConfiguration;
 import android.net.TrafficStats;
-import android.net.wifi.BatchedScanResult;
-import android.net.wifi.BatchedScanSettings;
 import android.net.wifi.RssiPacketCountInfo;
 import android.net.wifi.ScanResult;
 import android.net.wifi.ScanSettings;
@@ -71,12 +76,14 @@
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiLinkLayerStats;
 import android.net.wifi.WifiManager;
+import android.net.wifi.WifiScanner;
 import android.net.wifi.WifiSsid;
 import android.net.wifi.WpsInfo;
 import android.net.wifi.WpsResult;
 import android.net.wifi.WpsResult.Status;
 import android.net.wifi.p2p.IWifiP2pManager;
 import android.os.BatteryStats;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.INetworkManagementService;
@@ -104,6 +111,9 @@
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 import com.android.server.net.NetlinkTracker;
+import com.android.server.wifi.hotspot2.NetworkDetail;
+import com.android.server.wifi.hotspot2.SupplicantBridge;
+import com.android.server.wifi.hotspot2.Utils;
 import com.android.server.wifi.p2p.WifiP2pServiceImpl;
 
 import java.io.BufferedReader;
@@ -115,14 +125,15 @@
 import java.net.Inet4Address;
 import java.net.InetAddress;
 import java.util.ArrayList;
+import java.util.Calendar;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
 import java.util.Queue;
+import java.util.Random;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
-import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 /**
@@ -136,14 +147,16 @@
  *
  * @hide
  */
-public class WifiStateMachine extends StateMachine {
+public class WifiStateMachine extends StateMachine implements WifiNative.WifiPnoEventHandler {
 
     private static final String NETWORKTYPE = "WIFI";
     private static final String NETWORKTYPE_UNTRUSTED = "WIFI_UT";
     private static boolean DBG = false;
     private static boolean VDBG = false;
     private static boolean VVDBG = false;
+    private static boolean USE_PAUSE_SCANS = false;
     private static boolean mLogMessages = false;
+    private static final String TAG = "WifiStateMachine";
 
     private static final int ONE_HOUR_MILLI = 1000 * 60 * 60;
 
@@ -165,8 +178,11 @@
     protected void loge(String s) {
         Log.e(getName(), s);
     }
+    protected void logd(String s) {
+        Log.d(getName(), s);
+    }
     protected void log(String s) {;
-        Log.e(getName(), s);
+        Log.d(getName(), s);
     }
 
     private WifiMonitor mWifiMonitor;
@@ -175,35 +191,29 @@
     private WifiAutoJoinController mWifiAutoJoinController;
     private INetworkManagementService mNwService;
     private ConnectivityManager mCm;
-
+    private WifiLogger mWifiLogger;
+    private WifiApConfigStore mWifiApConfigStore;
     private final boolean mP2pSupported;
     private final AtomicBoolean mP2pConnected = new AtomicBoolean(false);
     private boolean mTemporarilyDisconnectWifi = false;
     private final String mPrimaryDeviceType;
 
     /* Scan results handling */
-    private List<ScanResult> mScanResults = new ArrayList<ScanResult>();
+    private List<ScanDetail> mScanResults = new ArrayList<>();
     private static final Pattern scanResultPattern = Pattern.compile("\t+");
     private static final int SCAN_RESULT_CACHE_SIZE = 160;
-    private final LruCache<String, ScanResult> mScanResultCache;
+    private final LruCache<NetworkDetail, ScanDetail> mScanResultCache;
     // For debug, number of known scan results that were found as part of last scan result event,
     // as well the number of scans results returned by the supplicant with that message
     private int mNumScanResultsKnown;
     private int mNumScanResultsReturned;
 
-    /* Batch scan results */
-    private final List<BatchedScanResult> mBatchedScanResults =
-            new ArrayList<BatchedScanResult>();
-    private int mBatchedScanOwnerUid = UNKNOWN_SCAN_SOURCE;
-    private int mExpectedBatchedScans = 0;
-    private long mBatchedScanMinPollTime = 0;
-
     private boolean mScreenOn = false;
 
     /* Chipset supports background scan */
     private final boolean mBackgroundScanSupported;
 
-    private String mInterfaceName;
+    private final String mInterfaceName;
     /* Tethering interface could be separate from wlan interface */
     private String mTetherInterfaceName;
 
@@ -212,12 +222,66 @@
     private int mLastNetworkId; // The network Id we successfully joined
     private boolean linkDebouncing = false;
 
+    private boolean mHalBasedPnoDriverSupported = false;
+
+    // Below booleans are configurations coming from the Developper Settings
+    private boolean mEnableAssociatedNetworkSwitchingInDevSettings = true;
+    private boolean mHalBasedPnoEnableInDevSettings = false;
+
+
+    private int mHalFeatureSet = 0;
+    private static int mPnoResultFound = 0;
+
+    @Override
+    public void onPnoNetworkFound(ScanResult results[]) {
+        if (DBG) {
+            Log.e(TAG, "onPnoNetworkFound event received num = " + results.length);
+            for (int i = 0; i < results.length; i++) {
+                Log.e(TAG, results[i].toString());
+            }
+        }
+        sendMessage(CMD_PNO_NETWORK_FOUND, results.length, 0, results);
+    }
+
+    public void processPnoNetworkFound(ScanResult results[]) {
+        ScanSettings settings = new ScanSettings();
+        settings.channelSet = new ArrayList<WifiChannel>();
+        StringBuilder sb = new StringBuilder();
+        sb.append("");
+        for (int i=0; i<results.length; i++) {
+            WifiChannel channel = new WifiChannel();
+            channel.freqMHz = results[i].frequency;
+            settings.channelSet.add(channel);
+            sb.append(results[i].SSID).append(" ");
+        }
+
+        stopPnoOffload();
+
+        Log.e(TAG, "processPnoNetworkFound starting scan cnt=" + mPnoResultFound);
+        startScan(PNO_NETWORK_FOUND_SOURCE, mPnoResultFound,  settings, null);
+        mPnoResultFound ++;
+        //sendMessage(CMD_SCAN_RESULTS_AVAILABLE);
+        int delay = 30 * 1000;
+        // reconfigure Pno after 1 minutes if we're still in disconnected state
+        sendMessageDelayed(CMD_RESTART_AUTOJOIN_OFFLOAD, delay,
+                mRestartAutoJoinOffloadCounter, " processPnoNetworkFound " + sb.toString(),
+                (long)delay);
+        mRestartAutoJoinOffloadCounter++;
+    }
+
+    public void registerNetworkDisabled(int netId) {
+        // Restart legacy PNO and autojoin offload if needed
+        sendMessage(CMD_RESTART_AUTOJOIN_OFFLOAD, 0,
+                mRestartAutoJoinOffloadCounter, " registerNetworkDisabled " + netId);
+        mRestartAutoJoinOffloadCounter++;
+    }
+
     // Testing various network disconnect cases by sending lots of spurious
     // disconnect to supplicant
     private boolean testNetworkDisconnect = false;
 
     private boolean mEnableRssiPolling = false;
-    private boolean mEnableBackgroundScan = false;
+    private boolean mLegacyPnoEnabled = false;
     private int mRssiPollToken = 0;
     /* 3 operational states for STA operation: CONNECT_MODE, SCAN_ONLY_MODE, SCAN_ONLY_WIFI_OFF_MODE
     * In CONNECT_MODE, the STA can scan and connect to an access point
@@ -237,15 +301,13 @@
     private static final int SET_ALLOW_UNTRUSTED_SOURCE = -4;
     private static final int ENABLE_WIFI = -5;
     public static final int DFS_RESTRICTED_SCAN_REQUEST = -6;
+    public static final int PNO_NETWORK_FOUND_SOURCE = -7;
 
     private static final int SCAN_REQUEST_BUFFER_MAX_SIZE = 10;
     private static final String CUSTOMIZED_SCAN_SETTING = "customized_scan_settings";
     private static final String CUSTOMIZED_SCAN_WORKSOURCE = "customized_scan_worksource";
     private static final String SCAN_REQUEST_TIME = "scan_request_time";
 
-    private static final String BATCHED_SETTING = "batched_settings";
-    private static final String BATCHED_WORKSOURCE = "batched_worksource";
-
     /* Tracks if state machine has received any screen state change broadcast yet.
      * We can miss one of these at boot.
      */
@@ -298,6 +360,12 @@
     private int mDriverStartToken = 0;
 
     /**
+     * Don't select new network when previous network selection is
+     * pending connection for this much time
+     */
+    private static final int CONNECT_TIMEOUT_MSEC = 3000;
+
+    /**
      * The link properties of the wifi interface.
      * Do not modify this directly; use updateLinkProperties instead.
      */
@@ -313,11 +381,13 @@
 
     private final Object mDhcpResultsLock = new Object();
     private DhcpResults mDhcpResults;
+
+    // NOTE: Do not return to clients - use #getWiFiInfoForUid(int)
     private WifiInfo mWifiInfo;
     private NetworkInfo mNetworkInfo;
     private NetworkCapabilities mNetworkCapabilities;
     private SupplicantStateTracker mSupplicantStateTracker;
-    private DhcpStateMachine mDhcpStateMachine;
+    private BaseDhcpStateMachine mDhcpStateMachine;
     private boolean mDhcpActive = false;
 
     private int mWifiLinkLayerStatsSupported = 4; // Temporary disable
@@ -346,6 +416,9 @@
     // Used as debug to indicate which configuration last was removed
     private WifiConfiguration lastForgetConfigurationAttempt = null;
 
+    //Random used by softAP channel Selection
+    private static Random mRandom = new Random(Calendar.getInstance().getTimeInMillis());
+
     boolean isRoaming() {
         return mAutoRoaming == WifiAutoJoinController.AUTO_JOIN_ROAMING
                 || mAutoRoaming == WifiAutoJoinController.AUTO_JOIN_EXTENDED_ROAMING;
@@ -367,12 +440,11 @@
         if (!mTargetRoamBSSID.equals("any") && bssid.equals("any")) {
             // Changing to ANY
             if (!mWifiConfigStore.roamOnAny) {
-                ret =  false; // Nothing to do
+                ret = false; // Nothing to do
             }
         }
         if (VDBG) {
-           loge("autoRoamSetBSSID " + bssid
-                   + " key=" + config.configKey());
+            logd("autoRoamSetBSSID " + bssid + " key=" + config.configKey());
         }
         config.autoJoinBSSID = bssid;
         mTargetRoamBSSID = bssid;
@@ -381,17 +453,71 @@
     }
 
     /**
+     * Save the UID correctly depending on if this is a new or existing network.
+     * @return true if operation is authorized, false otherwise
+     */
+    boolean recordUidIfAuthorized(WifiConfiguration config, int uid, boolean onlyAnnotate) {
+        if (!mWifiConfigStore.isNetworkConfigured(config)) {
+            config.creatorUid = uid;
+            config.creatorName = mContext.getPackageManager().getNameForUid(uid);
+        } else if (!mWifiConfigStore.canModifyNetwork(uid, config, onlyAnnotate)) {
+            return false;
+        }
+
+        config.lastUpdateUid = uid;
+        config.lastUpdateName = mContext.getPackageManager().getNameForUid(uid);
+
+        return true;
+
+    }
+
+    /**
+     * Checks to see if user has specified if the apps configuration is connectable.
+     * If the user hasn't specified we query the user and return true.
+     *
+     * @param message The message to be deferred
+     * @param netId Network id of the configuration to check against
+     * @param allowOverride If true we won't defer to the user if the uid of the message holds the
+     *                      CONFIG_OVERRIDE_PERMISSION
+     * @return True if we are waiting for user feedback or netId is invalid. False otherwise.
+     */
+    boolean deferForUserInput(Message message, int netId, boolean allowOverride){
+        final WifiConfiguration config = mWifiConfigStore.getWifiConfiguration(netId);
+
+        // We can only evaluate saved configurations.
+        if (config == null) {
+            logd("deferForUserInput: configuration for netId=" + netId + " not stored");
+            return true;
+        }
+
+        switch (config.userApproved) {
+            case WifiConfiguration.USER_APPROVED:
+            case WifiConfiguration.USER_BANNED:
+                return false;
+            case WifiConfiguration.USER_PENDING:
+            default: // USER_UNSPECIFIED
+               /* the intention was to ask user here; but a dialog box is   *
+                * too invasive; so we are going to allow connection for now */
+                config.userApproved = WifiConfiguration.USER_APPROVED;
+                return false;
+        }
+    }
+
+    /**
      * Subset of link properties coming from netlink.
      * Currently includes IPv4 and IPv6 addresses. In the future will also include IPv6 DNS servers
      * and domains obtained from router advertisements (RFC 6106).
      */
     private NetlinkTracker mNetlinkTracker;
 
+    private IpReachabilityMonitor mIpReachabilityMonitor;
+
     private AlarmManager mAlarmManager;
     private PendingIntent mScanIntent;
     private PendingIntent mDriverStopIntent;
-    private PendingIntent mBatchedScanIntervalIntent;
+    private PendingIntent mPnoIntent;
 
+    private int mDisconnectedPnoAlarmCount = 0;
     /* Tracks current frequency mode */
     private AtomicInteger mFrequencyBand = new AtomicInteger(WifiManager.WIFI_FREQUENCY_BAND_AUTO);
 
@@ -407,11 +533,15 @@
     private AsyncChannel mWifiP2pChannel;
     private AsyncChannel mWifiApConfigChannel;
 
+    private WifiScanner mWifiScanner;
+
     private int mConnectionRequests = 0;
     private WifiNetworkFactory mNetworkFactory;
     private UntrustedWifiNetworkFactory mUntrustedNetworkFactory;
     private WifiNetworkAgent mNetworkAgent;
 
+    private String[] mWhiteListedSsids = null;
+
     // Keep track of various statistics, for retrieval by System Apps, i.e. under @SystemApi
     // We should really persist that into the networkHistory.txt file, and read it back when
     // WifiStateMachine starts up
@@ -423,87 +553,87 @@
     /* The base for wifi message types */
     static final int BASE = Protocol.BASE_WIFI;
     /* Start the supplicant */
-    static final int CMD_START_SUPPLICANT                 = BASE + 11;
+    static final int CMD_START_SUPPLICANT                               = BASE + 11;
     /* Stop the supplicant */
-    static final int CMD_STOP_SUPPLICANT                  = BASE + 12;
+    static final int CMD_STOP_SUPPLICANT                                = BASE + 12;
     /* Start the driver */
-    static final int CMD_START_DRIVER                     = BASE + 13;
+    static final int CMD_START_DRIVER                                   = BASE + 13;
     /* Stop the driver */
-    static final int CMD_STOP_DRIVER                      = BASE + 14;
+    static final int CMD_STOP_DRIVER                                    = BASE + 14;
     /* Indicates Static IP succeeded */
-    static final int CMD_STATIC_IP_SUCCESS                = BASE + 15;
+    static final int CMD_STATIC_IP_SUCCESS                              = BASE + 15;
     /* Indicates Static IP failed */
-    static final int CMD_STATIC_IP_FAILURE                = BASE + 16;
+    static final int CMD_STATIC_IP_FAILURE                              = BASE + 16;
     /* Indicates supplicant stop failed */
-    static final int CMD_STOP_SUPPLICANT_FAILED           = BASE + 17;
+    static final int CMD_STOP_SUPPLICANT_FAILED                         = BASE + 17;
     /* Delayed stop to avoid shutting down driver too quick*/
-    static final int CMD_DELAYED_STOP_DRIVER              = BASE + 18;
+    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;
+    static final int CMD_DRIVER_START_TIMED_OUT                         = BASE + 19;
 
     /* Start the soft access point */
-    static final int CMD_START_AP                         = BASE + 21;
+    static final int CMD_START_AP                                       = BASE + 21;
     /* Indicates soft ap start succeeded */
-    static final int CMD_START_AP_SUCCESS                 = BASE + 22;
+    static final int CMD_START_AP_SUCCESS                               = BASE + 22;
     /* Indicates soft ap start failed */
-    static final int CMD_START_AP_FAILURE                 = BASE + 23;
+    static final int CMD_START_AP_FAILURE                               = BASE + 23;
     /* Stop the soft access point */
-    static final int CMD_STOP_AP                          = BASE + 24;
+    static final int CMD_STOP_AP                                        = BASE + 24;
     /* Set the soft access point configuration */
-    static final int CMD_SET_AP_CONFIG                    = BASE + 25;
+    static final int CMD_SET_AP_CONFIG                                  = BASE + 25;
     /* Soft access point configuration set completed */
-    static final int CMD_SET_AP_CONFIG_COMPLETED          = BASE + 26;
+    static final int CMD_SET_AP_CONFIG_COMPLETED                        = BASE + 26;
     /* Request the soft access point configuration */
-    static final int CMD_REQUEST_AP_CONFIG                = BASE + 27;
+    static final int CMD_REQUEST_AP_CONFIG                              = BASE + 27;
     /* Response to access point configuration request */
-    static final int CMD_RESPONSE_AP_CONFIG               = BASE + 28;
+    static final int CMD_RESPONSE_AP_CONFIG                             = BASE + 28;
     /* Invoked when getting a tether state change notification */
-    static final int CMD_TETHER_STATE_CHANGE              = BASE + 29;
+    static final int CMD_TETHER_STATE_CHANGE                            = BASE + 29;
     /* A delayed message sent to indicate tether state change failed to arrive */
-    static final int CMD_TETHER_NOTIFICATION_TIMED_OUT    = BASE + 30;
+    static final int CMD_TETHER_NOTIFICATION_TIMED_OUT                  = BASE + 30;
 
-    static final int CMD_BLUETOOTH_ADAPTER_STATE_CHANGE   = BASE + 31;
+    static final int CMD_BLUETOOTH_ADAPTER_STATE_CHANGE                 = BASE + 31;
 
     /* Supplicant commands */
     /* Is supplicant alive ? */
-    static final int CMD_PING_SUPPLICANT                  = BASE + 51;
+    static final int CMD_PING_SUPPLICANT                                = BASE + 51;
     /* Add/update a network configuration */
-    static final int CMD_ADD_OR_UPDATE_NETWORK            = BASE + 52;
+    static final int CMD_ADD_OR_UPDATE_NETWORK                          = BASE + 52;
     /* Delete a network */
-    static final int CMD_REMOVE_NETWORK                   = BASE + 53;
+    static final int CMD_REMOVE_NETWORK                                 = BASE + 53;
     /* Enable a network. The device will attempt a connection to the given network. */
-    static final int CMD_ENABLE_NETWORK                   = BASE + 54;
+    static final int CMD_ENABLE_NETWORK                                 = BASE + 54;
     /* Enable all networks */
-    static final int CMD_ENABLE_ALL_NETWORKS              = BASE + 55;
+    static final int CMD_ENABLE_ALL_NETWORKS                            = BASE + 55;
     /* Blacklist network. De-prioritizes the given BSSID for connection. */
-    static final int CMD_BLACKLIST_NETWORK                = BASE + 56;
+    static final int CMD_BLACKLIST_NETWORK                              = BASE + 56;
     /* Clear the blacklist network list */
-    static final int CMD_CLEAR_BLACKLIST                  = BASE + 57;
+    static final int CMD_CLEAR_BLACKLIST                                = BASE + 57;
     /* Save configuration */
-    static final int CMD_SAVE_CONFIG                      = BASE + 58;
+    static final int CMD_SAVE_CONFIG                                    = BASE + 58;
     /* Get configured networks */
-    static final int CMD_GET_CONFIGURED_NETWORKS          = BASE + 59;
+    static final int CMD_GET_CONFIGURED_NETWORKS                        = BASE + 59;
     /* Get available frequencies */
-    static final int CMD_GET_CAPABILITY_FREQ              = BASE + 60;
+    static final int CMD_GET_CAPABILITY_FREQ                            = BASE + 60;
     /* Get adaptors */
-    static final int CMD_GET_SUPPORTED_FEATURES           = BASE + 61;
+    static final int CMD_GET_SUPPORTED_FEATURES                         = BASE + 61;
     /* Get configured networks with real preSharedKey */
-    static final int CMD_GET_PRIVILEGED_CONFIGURED_NETWORKS = BASE + 62;
+    static final int CMD_GET_PRIVILEGED_CONFIGURED_NETWORKS             = BASE + 62;
     /* Get Link Layer Stats thru HAL */
-    static final int CMD_GET_LINK_LAYER_STATS             = BASE + 63;
+    static final int CMD_GET_LINK_LAYER_STATS                           = BASE + 63;
     /* Supplicant commands after driver start*/
     /* Initiate a scan */
-    static final int CMD_START_SCAN                       = BASE + 71;
+    static final int CMD_START_SCAN                                     = BASE + 71;
     /* Set operational mode. CONNECT, SCAN ONLY, SCAN_ONLY with Wi-Fi off mode */
-    static final int CMD_SET_OPERATIONAL_MODE             = BASE + 72;
+    static final int CMD_SET_OPERATIONAL_MODE                           = BASE + 72;
     /* Disconnect from a network */
-    static final int CMD_DISCONNECT                       = BASE + 73;
+    static final int CMD_DISCONNECT                                     = BASE + 73;
     /* Reconnect to a network */
-    static final int CMD_RECONNECT                        = BASE + 74;
+    static final int CMD_RECONNECT                                      = BASE + 74;
     /* Reassociate to a network */
-    static final int CMD_REASSOCIATE                      = BASE + 75;
+    static final int CMD_REASSOCIATE                                    = BASE + 75;
     /* Get Connection Statistis */
-    static final int CMD_GET_CONNECTION_STATISTICS        = BASE + 76;
+    static final int CMD_GET_CONNECTION_STATISTICS                      = BASE + 76;
 
     /* Controls suspend mode optimizations
      *
@@ -516,38 +646,66 @@
      * - turn off roaming
      * - DTIM wake up settings
      */
-    static final int CMD_SET_HIGH_PERF_MODE               = BASE + 77;
+    static final int CMD_SET_HIGH_PERF_MODE                             = BASE + 77;
     /* Set the country code */
-    static final int CMD_SET_COUNTRY_CODE                 = BASE + 80;
+    static final int CMD_SET_COUNTRY_CODE                               = BASE + 80;
     /* Enables RSSI poll */
-    static final int CMD_ENABLE_RSSI_POLL                 = BASE + 82;
+    static final int CMD_ENABLE_RSSI_POLL                               = BASE + 82;
     /* RSSI poll */
-    static final int CMD_RSSI_POLL                        = BASE + 83;
+    static final int CMD_RSSI_POLL                                      = BASE + 83;
     /* Set up packet filtering */
-    static final int CMD_START_PACKET_FILTERING           = BASE + 84;
+    static final int CMD_START_PACKET_FILTERING                         = BASE + 84;
     /* Clear packet filter */
-    static final int CMD_STOP_PACKET_FILTERING            = BASE + 85;
+    static final int CMD_STOP_PACKET_FILTERING                          = BASE + 85;
     /* Enable suspend mode optimizations in the driver */
-    static final int CMD_SET_SUSPEND_OPT_ENABLED          = BASE + 86;
+    static final int CMD_SET_SUSPEND_OPT_ENABLED                        = BASE + 86;
     /* Delayed NETWORK_DISCONNECT */
-    static final int CMD_DELAYED_NETWORK_DISCONNECT       = BASE + 87;
+    static final int CMD_DELAYED_NETWORK_DISCONNECT                     = BASE + 87;
     /* 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;
+    static final int CMD_NO_NETWORKS_PERIODIC_SCAN                      = BASE + 88;
     /* Test network Disconnection NETWORK_DISCONNECT */
-    static final int CMD_TEST_NETWORK_DISCONNECT          = BASE + 89;
+    static final int CMD_TEST_NETWORK_DISCONNECT                        = BASE + 89;
+
     private int testNetworkDisconnectCounter = 0;
 
     /* arg1 values to CMD_STOP_PACKET_FILTERING and CMD_START_PACKET_FILTERING */
-    static final int MULTICAST_V6  = 1;
-    static final int MULTICAST_V4  = 0;
+    static final int MULTICAST_V6 = 1;
+    static final int MULTICAST_V4 = 0;
 
-   /* Set the frequency band */
-    static final int CMD_SET_FREQUENCY_BAND               = BASE + 90;
+    /* Set the frequency band */
+    static final int CMD_SET_FREQUENCY_BAND                             = BASE + 90;
     /* Enable TDLS on a specific MAC address */
-    static final int CMD_ENABLE_TDLS                      = BASE + 92;
+    static final int CMD_ENABLE_TDLS                                    = BASE + 92;
     /* DHCP/IP configuration watchdog */
-    static final int CMD_OBTAINING_IP_ADDRESS_WATCHDOG_TIMER    = BASE + 93;
+    static final int CMD_OBTAINING_IP_ADDRESS_WATCHDOG_TIMER            = BASE + 93;
+
+    /**
+     * Watchdog for protecting against b/16823537
+     * Leave time for 4-way handshake to succeed
+     */
+    static final int ROAM_GUARD_TIMER_MSEC = 15000;
+
+    int roamWatchdogCount = 0;
+    /* Roam state watchdog */
+    static final int CMD_ROAM_WATCHDOG_TIMER                            = BASE + 94;
+    /* Screen change intent handling */
+    static final int CMD_SCREEN_STATE_CHANGED                           = BASE + 95;
+
+    /* Disconnecting state watchdog */
+    static final int CMD_DISCONNECTING_WATCHDOG_TIMER                   = BASE + 96;
+
+    /* Remove a packages associated configrations */
+    static final int CMD_REMOVE_APP_CONFIGURATIONS                      = BASE + 97;
+
+    /* Disable an ephemeral network */
+    static final int CMD_DISABLE_EPHEMERAL_NETWORK                      = BASE + 98;
+
+    /* Get matching network */
+    static final int CMD_GET_MATCHING_CONFIG                            = BASE + 99;
+
+    /* alert from firmware */
+    static final int CMD_FIRMWARE_ALERT                                 = BASE + 100;
 
     /**
      * Make this timer 40 seconds, which is about the normal DHCP timeout.
@@ -560,84 +718,82 @@
 
     /* Commands from/to the SupplicantStateTracker */
     /* Reset the supplicant state tracker */
-    static final int CMD_RESET_SUPPLICANT_STATE           = BASE + 111;
-
-
-    /**
-     * Watchdog for protecting against b/16823537
-     * Leave time for 4-ways handshake to succeed
-     */
-    static final int ROAM_GUARD_TIMER_MSEC = 15000;
-
-    int roamWatchdogCount = 0;
-    /* Roam state watchdog */
-    static final int CMD_ROAM_WATCHDOG_TIMER    = BASE + 94;
-    /* Screen change intent handling */
-    static final int CMD_SCREEN_STATE_CHANGED              = BASE + 95;
+    static final int CMD_RESET_SUPPLICANT_STATE                         = BASE + 111;
 
     int disconnectingWatchdogCount = 0;
     static final int DISCONNECTING_GUARD_TIMER_MSEC = 5000;
 
-    /* Disconnecting state watchdog */
-    static final int CMD_DISCONNECTING_WATCHDOG_TIMER     = BASE + 96;
-
-    /* Disable an ephemeral network */
-    static final int CMD_DISABLE_EPHEMERAL_NETWORK = BASE + 98;
-
     /* P2p commands */
     /* We are ok with no response here since we wont do much with it anyway */
-    public static final int CMD_ENABLE_P2P                = BASE + 131;
+    public static final int CMD_ENABLE_P2P                              = BASE + 131;
     /* In order to shut down supplicant cleanly, we wait till p2p has
      * been disabled */
-    public static final int CMD_DISABLE_P2P_REQ           = BASE + 132;
-    public static final int CMD_DISABLE_P2P_RSP           = BASE + 133;
+    public static final int CMD_DISABLE_P2P_REQ                         = BASE + 132;
+    public static final int CMD_DISABLE_P2P_RSP                         = BASE + 133;
 
-    public static final int CMD_BOOT_COMPLETED            = BASE + 134;
-
-    /* change the batch scan settings.
-     * arg1 = responsible UID
-     * arg2 = csph (channel scans per hour)
-     * obj = bundle with the new settings and the optional worksource
-     */
-    public static final int CMD_SET_BATCHED_SCAN          = BASE + 135;
-    public static final int CMD_START_NEXT_BATCHED_SCAN   = BASE + 136;
-    public static final int CMD_POLL_BATCHED_SCAN         = BASE + 137;
+    public static final int CMD_BOOT_COMPLETED                          = BASE + 134;
 
     /* We now have a valid IP configuration. */
-    static final int CMD_IP_CONFIGURATION_SUCCESSFUL      = BASE + 138;
+    static final int CMD_IP_CONFIGURATION_SUCCESSFUL                    = BASE + 138;
     /* We no longer have a valid IP configuration. */
-    static final int CMD_IP_CONFIGURATION_LOST            = BASE + 139;
+    static final int CMD_IP_CONFIGURATION_LOST                          = BASE + 139;
     /* Link configuration (IP address, DNS, ...) changes notified via netlink */
-    static final int CMD_UPDATE_LINKPROPERTIES            = BASE + 140;
+    static final int CMD_UPDATE_LINKPROPERTIES                          = BASE + 140;
 
     /* Supplicant is trying to associate to a given BSSID */
-    static final int CMD_TARGET_BSSID                     = BASE + 141;
+    static final int CMD_TARGET_BSSID                                   = BASE + 141;
 
     /* Reload all networks and reconnect */
-    static final int CMD_RELOAD_TLS_AND_RECONNECT         = BASE + 142;
+    static final int CMD_RELOAD_TLS_AND_RECONNECT                       = BASE + 142;
 
-    static final int CMD_AUTO_CONNECT                     = BASE + 143;
+    static final int CMD_AUTO_CONNECT                                   = BASE + 143;
 
-    static final int network_status_unwanted_disconnect = 0;
-    static final int network_status_unwanted_disable_autojoin = 1;
+    private static final int NETWORK_STATUS_UNWANTED_DISCONNECT         = 0;
+    private static final int NETWORK_STATUS_UNWANTED_VALIDATION_FAILED  = 1;
+    private static final int NETWORK_STATUS_UNWANTED_DISABLE_AUTOJOIN   = 2;
 
-    static final int CMD_UNWANTED_NETWORK                 = BASE + 144;
+    static final int CMD_UNWANTED_NETWORK                               = BASE + 144;
 
-    static final int CMD_AUTO_ROAM                        = BASE + 145;
+    static final int CMD_AUTO_ROAM                                      = BASE + 145;
 
-    static final int CMD_AUTO_SAVE_NETWORK                = BASE + 146;
+    static final int CMD_AUTO_SAVE_NETWORK                              = BASE + 146;
 
-    static final int CMD_ASSOCIATED_BSSID                = BASE + 147;
+    static final int CMD_ASSOCIATED_BSSID                               = BASE + 147;
 
-    static final int CMD_NETWORK_STATUS                  = BASE + 148;
+    static final int CMD_NETWORK_STATUS                                 = BASE + 148;
+
+    /* A layer 3 neighbor on the Wi-Fi link became unreachable. */
+    static final int CMD_IP_REACHABILITY_LOST                           = BASE + 149;
+
+    /* Remove a packages associated configrations */
+    static final int CMD_REMOVE_USER_CONFIGURATIONS                     = BASE + 152;
+
+    static final int CMD_ACCEPT_UNVALIDATED                             = BASE + 153;
+
+    /* used to restart PNO when it was stopped due to association attempt */
+    static final int CMD_RESTART_AUTOJOIN_OFFLOAD                       = BASE + 154;
+
+    static int mRestartAutoJoinOffloadCounter = 0;
+
+    /* used to log if PNO was started */
+    static final int CMD_STARTED_PNO_DBG                                = BASE + 155;
+
+    static final int CMD_PNO_NETWORK_FOUND                              = BASE + 156;
+
+    /* used to log if PNO was started */
+    static final int CMD_UPDATE_ASSOCIATED_SCAN_PERMISSION              = BASE + 158;
+
+    /* used to log if GSCAN was started */
+    static final int CMD_STARTED_GSCAN_DBG                              = BASE + 159;
+
 
     /* Wifi state machine modes of operation */
     /* CONNECT_MODE - connect to any 'known' AP when it becomes available */
-    public static final int CONNECT_MODE                   = 1;
+    public static final int CONNECT_MODE = 1;
     /* SCAN_ONLY_MODE - don't connect to any APs; scan, but only while apps hold lock */
-    public static final int SCAN_ONLY_MODE                 = 2;
+    public static final int SCAN_ONLY_MODE = 2;
     /* SCAN_ONLY_WITH_WIFI_OFF - scan, but don't connect to any APs */
-    public static final int SCAN_ONLY_WITH_WIFI_OFF_MODE   = 3;
+    public static final int SCAN_ONLY_WITH_WIFI_OFF_MODE = 3;
 
     private static final int SUCCESS = 1;
     private static final int FAILURE = -1;
@@ -649,9 +805,9 @@
      */
     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;
+    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);
@@ -665,7 +821,11 @@
      */
     private final int mDefaultFrameworkScanIntervalMs;
 
-    private int mDisconnectedScanPeriodMs = 10000;
+
+    /**
+     * Scan period for the NO_NETWORKS_PERIIDOC_SCAN_FEATURE
+     */
+    private final int mNoNetworksPeriodicScan;
 
     /**
      * Supplicant scan interval in milliseconds.
@@ -690,7 +850,7 @@
      * autojoin while connected with screen lit
      * Max time is 5 minutes
      */
-    private static final long  maxFullBandConnectedTimeIntervalMilli = 1000 * 60 * 5;
+    private static final long maxFullBandConnectedTimeIntervalMilli = 1000 * 60 * 5;
 
     /**
      * Minimum time interval between enabling all networks.
@@ -710,13 +870,14 @@
     private int mDelayedStopCounter;
     private boolean mInDelayedStop = false;
 
-    // sometimes telephony gives us this data before boot is complete and we can't store it
-    // until after, so the write is deferred
-    private volatile String mPersistedCountryCode;
+    // there is a delay between StateMachine change country code and Supplicant change country code
+    // here save the current WifiStateMachine set country code
+    private volatile String mSetCountryCode = null;
 
     // Supplicant doesn't like setting the same country code multiple times (it may drop
-    // currently connected network), so we save the country code here to avoid redundency
-    private String mLastSetCountryCode;
+    // currently connected network), so we save the current device set country code here to avoid
+    // redundency
+    private String mDriverSetCountryCode = null;
 
     /* Default parent state */
     private State mDefaultState = new DefaultState();
@@ -773,9 +934,40 @@
     /* Waiting for untether confirmation before stopping soft Ap */
     private State mUntetheringState = new UntetheringState();
 
+
+
+    private class WifiScanListener implements WifiScanner.ScanListener {
+        @Override
+        public void onSuccess() {
+            Log.e(TAG, "WifiScanListener onSuccess");
+        };
+        @Override
+        public void onFailure(int reason, String description) {
+            Log.e(TAG, "WifiScanListener onFailure");
+        };
+        @Override
+        public void onPeriodChanged(int periodInMs) {
+            Log.e(TAG, "WifiScanListener onPeriodChanged  period=" + periodInMs);
+        }
+        @Override
+        public void onResults(WifiScanner.ScanData[] results) {
+            Log.e(TAG, "WifiScanListener onResults2 "  + results.length);
+        }
+        @Override
+        public void onFullResult(ScanResult fullScanResult) {
+            Log.e(TAG, "WifiScanListener onFullResult " + fullScanResult.toString());
+        }
+
+        WifiScanListener() {}
+    }
+
+    WifiScanListener mWifiScanListener = new WifiScanListener();
+
+
     private class TetherStateChange {
         ArrayList<String> available;
         ArrayList<String> active;
+
         TetherStateChange(ArrayList<String> av, ArrayList<String> ac) {
             available = av;
             active = ac;
@@ -786,50 +978,42 @@
         int networkId;
         int protocol;
         String ssid;
-        String[] challenges;
-    }
-
-    public static class SimAuthResponseData {
-        int id;
-        String Kc1;
-        String SRES1;
-        String Kc2;
-        String SRES2;
-        String Kc3;
-        String SRES3;
+        // EAP-SIM: data[] contains the 3 rand, one for each of the 3 challenges
+        // EAP-AKA/AKA': data[] contains rand & authn couple for the single challenge
+        String[] data;
     }
 
     /**
      * One of  {@link WifiManager#WIFI_STATE_DISABLED},
-     *         {@link WifiManager#WIFI_STATE_DISABLING},
-     *         {@link WifiManager#WIFI_STATE_ENABLED},
-     *         {@link WifiManager#WIFI_STATE_ENABLING},
-     *         {@link WifiManager#WIFI_STATE_UNKNOWN}
-     *
+     * {@link WifiManager#WIFI_STATE_DISABLING},
+     * {@link WifiManager#WIFI_STATE_ENABLED},
+     * {@link WifiManager#WIFI_STATE_ENABLING},
+     * {@link WifiManager#WIFI_STATE_UNKNOWN}
      */
     private final AtomicInteger mWifiState = new AtomicInteger(WIFI_STATE_DISABLED);
 
     /**
      * One of  {@link WifiManager#WIFI_AP_STATE_DISABLED},
-     *         {@link WifiManager#WIFI_AP_STATE_DISABLING},
-     *         {@link WifiManager#WIFI_AP_STATE_ENABLED},
-     *         {@link WifiManager#WIFI_AP_STATE_ENABLING},
-     *         {@link WifiManager#WIFI_AP_STATE_FAILED}
-     *
+     * {@link WifiManager#WIFI_AP_STATE_DISABLING},
+     * {@link WifiManager#WIFI_AP_STATE_ENABLED},
+     * {@link WifiManager#WIFI_AP_STATE_ENABLING},
+     * {@link WifiManager#WIFI_AP_STATE_FAILED}
      */
     private final AtomicInteger mWifiApState = new AtomicInteger(WIFI_AP_STATE_DISABLED);
 
     private static final int SCAN_REQUEST = 0;
     private static final String ACTION_START_SCAN =
-        "com.android.server.WifiManager.action.START_SCAN";
+            "com.android.server.WifiManager.action.START_SCAN";
+
+    private static final int PNO_START_REQUEST = 0;
+    private static final String ACTION_START_PNO =
+            "com.android.server.WifiManager.action.START_PNO";
 
     private static final String DELAYED_STOP_COUNTER = "DelayedStopCounter";
     private static final int DRIVER_STOP_REQUEST = 0;
     private static final String ACTION_DELAYED_DRIVER_STOP =
-        "com.android.server.WifiManager.action.DELAYED_DRIVER_STOP";
+            "com.android.server.WifiManager.action.DELAYED_DRIVER_STOP";
 
-    private static final String ACTION_REFRESH_BATCHED_SCAN =
-            "com.android.server.WifiManager.action.REFRESH_BATCHED_SCAN";
     /**
      * Keep track of whether WIFI is running.
      */
@@ -852,17 +1036,6 @@
 
     private final IBatteryStats mBatteryStats;
 
-    private BatchedScanSettings mBatchedScanSettings = null;
-
-    /**
-     * Track the worksource/cost of the current settings and track what's been noted
-     * to the battery stats, so we can mark the end of the previous when changing.
-     */
-    private WorkSource mBatchedScanWorkSource = null;
-    private int mBatchedScanCsph = 0;
-    private WorkSource mNotedBatchedScanWorkSource = null;
-    private int mNotedBatchedScanCsph = 0;
-
     private String mTcpBufferSizes = null;
 
     // Used for debug and stats gathering
@@ -870,10 +1043,17 @@
 
     final static int frameworkMinScanIntervalSaneValue = 10000;
 
+    boolean mPnoEnabled;
+    boolean mLazyRoamEnabled;
+    long mGScanStartTimeMilli;
+    long mGScanPeriodMilli;
+
     public WifiStateMachine(Context context, String wlanInterface,
-            WifiTrafficPoller trafficPoller){
+                            WifiTrafficPoller trafficPoller) {
         super("WifiStateMachine");
         mContext = context;
+        mSetCountryCode = Settings.Global.getString(
+                mContext.getContentResolver(), Settings.Global.WIFI_COUNTRY_CODE);
         mInterfaceName = wlanInterface;
         mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, NETWORKTYPE, "");
         mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
@@ -886,17 +1066,21 @@
                 PackageManager.FEATURE_WIFI_DIRECT);
 
         mWifiNative = new WifiNative(mInterfaceName);
-        mWifiConfigStore = new WifiConfigStore(context, mWifiNative);
+        mWifiConfigStore = new WifiConfigStore(context,this,  mWifiNative);
         mWifiAutoJoinController = new WifiAutoJoinController(context, this,
                 mWifiConfigStore, mWifiConnectionStatistics, mWifiNative);
         mWifiMonitor = new WifiMonitor(this, mWifiNative);
+        mWifiLogger = new WifiLogger(this);
+
         mWifiInfo = new WifiInfo();
         mSupplicantStateTracker = new SupplicantStateTracker(context, this, mWifiConfigStore,
                 getHandler());
         mLinkProperties = new LinkProperties();
 
         IBinder s1 = ServiceManager.getService(Context.WIFI_P2P_SERVICE);
-        mWifiP2pServiceImpl = (WifiP2pServiceImpl)IWifiP2pManager.Stub.asInterface(s1);
+        mWifiP2pServiceImpl = (WifiP2pServiceImpl) IWifiP2pManager.Stub.asInterface(s1);
+
+        IBinder s2 = ServiceManager.getService(Context.WIFI_PASSPOINT_SERVICE);
 
         mNetworkInfo.setIsAvailable(false);
         mLastBssid = null;
@@ -914,9 +1098,9 @@
             loge("Couldn't register netlink tracker: " + e.toString());
         }
 
-        mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
+        mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
         mScanIntent = getPrivateBroadcast(ACTION_START_SCAN, SCAN_REQUEST);
-        mBatchedScanIntervalIntent = getPrivateBroadcast(ACTION_REFRESH_BATCHED_SCAN, 0);
+        mPnoIntent = getPrivateBroadcast(ACTION_START_PNO, PNO_START_REQUEST);
 
         // Make sure the interval is not configured less than 10 seconds
         int period = mContext.getResources().getInteger(
@@ -925,6 +1109,10 @@
             period = frameworkMinScanIntervalSaneValue;
         }
         mDefaultFrameworkScanIntervalMs = period;
+
+        mNoNetworksPeriodicScan = mContext.getResources().getInteger(
+                R.integer.config_wifi_no_network_periodic_scan_interval);
+
         mDriverStopDelayMs = mContext.getResources().getInteger(
                 R.integer.config_wifi_driver_stop_delay);
 
@@ -935,7 +1123,7 @@
                 R.string.config_wifi_p2p_device_type);
 
         mUserWantsSuspendOpt.set(Settings.Global.getInt(mContext.getContentResolver(),
-                    Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED, 1) == 1);
+                Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED, 1) == 1);
 
         mNetworkCapabilitiesFilter.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
         mNetworkCapabilitiesFilter.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
@@ -946,16 +1134,16 @@
         mNetworkCapabilities = new NetworkCapabilities(mNetworkCapabilitiesFilter);
 
         mContext.registerReceiver(
-            new BroadcastReceiver() {
-                @Override
-                public void onReceive(Context context, Intent intent) {
-                    ArrayList<String> available = intent.getStringArrayListExtra(
-                            ConnectivityManager.EXTRA_AVAILABLE_TETHER);
-                    ArrayList<String> active = intent.getStringArrayListExtra(
-                            ConnectivityManager.EXTRA_ACTIVE_TETHER);
-                    sendMessage(CMD_TETHER_STATE_CHANGE, new TetherStateChange(available, active));
-                }
-            },new IntentFilter(ConnectivityManager.ACTION_TETHER_STATE_CHANGED));
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        ArrayList<String> available = intent.getStringArrayListExtra(
+                                ConnectivityManager.EXTRA_AVAILABLE_TETHER);
+                        ArrayList<String> active = intent.getStringArrayListExtra(
+                                ConnectivityManager.EXTRA_ACTIVE_TETHER);
+                        sendMessage(CMD_TETHER_STATE_CHANGE, new TetherStateChange(available, active));
+                    }
+                }, new IntentFilter(ConnectivityManager.ACTION_TETHER_STATE_CHANGED));
 
         mContext.registerReceiver(
                 new BroadcastReceiver() {
@@ -964,15 +1152,27 @@
                         sScanAlarmIntentCount++; // Used for debug only
                         startScan(SCAN_ALARM_SOURCE, mDelayedScanCounter.incrementAndGet(), null, null);
                         if (VDBG)
-                            loge("WiFiStateMachine SCAN ALARM -> " + mDelayedScanCounter.get());
+                            logd("SCAN ALARM -> " + mDelayedScanCounter.get());
                     }
                 },
                 new IntentFilter(ACTION_START_SCAN));
 
+        mContext.registerReceiver(
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        sendMessage(CMD_RESTART_AUTOJOIN_OFFLOAD, 0,
+                                mRestartAutoJoinOffloadCounter, "pno alarm");
+                        if (DBG)
+                            logd("PNO START ALARM sent");
+                    }
+                },
+                new IntentFilter(ACTION_START_PNO));
+
+
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_SCREEN_ON);
         filter.addAction(Intent.ACTION_SCREEN_OFF);
-        filter.addAction(ACTION_REFRESH_BATCHED_SCAN);
         mContext.registerReceiver(
                 new BroadcastReceiver() {
                     @Override
@@ -983,8 +1183,6 @@
                             sendMessage(CMD_SCREEN_STATE_CHANGED, 1);
                         } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
                             sendMessage(CMD_SCREEN_STATE_CHANGED, 0);
-                        } else if (action.equals(ACTION_REFRESH_BATCHED_SCAN)) {
-                            startNextBatchedScanAsync();
                         }
                     }
                 }, filter);
@@ -993,14 +1191,14 @@
                 new BroadcastReceiver() {
                     @Override
                     public void onReceive(Context context, Intent intent) {
-                       int counter = intent.getIntExtra(DELAYED_STOP_COUNTER, 0);
-                       sendMessage(CMD_DELAYED_STOP_DRIVER, counter, 0);
+                        int counter = intent.getIntExtra(DELAYED_STOP_COUNTER, 0);
+                        sendMessage(CMD_DELAYED_STOP_DRIVER, counter, 0);
                     }
                 },
                 new IntentFilter(ACTION_DELAYED_DRIVER_STOP));
 
         mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor(
-                Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED), false,
+                        Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED), false,
                 new ContentObserver(getHandler()) {
                     @Override
                     public void onChange(boolean selfChange) {
@@ -1018,9 +1216,9 @@
                 },
                 new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
 
-        mScanResultCache = new LruCache<String, ScanResult>(SCAN_RESULT_CACHE_SIZE);
+        mScanResultCache = new LruCache<>(SCAN_RESULT_CACHE_SIZE);
 
-        PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
+        PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
         mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getName());
 
         mSuspendWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WifiSuspend");
@@ -1074,7 +1272,8 @@
     PendingIntent getPrivateBroadcast(String action, int requestCode) {
         Intent intent = new Intent(action, null);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        intent.setPackage(this.getClass().getPackage().getName());
+        //intent.setPackage(this.getClass().getPackage().getName());
+        intent.setPackage("android");
         return PendingIntent.getBroadcast(mContext, requestCode, intent, 0);
     }
 
@@ -1099,6 +1298,7 @@
             mLogMessages = false;
             mWifiNative.setSupplicantLogLevel("INFO");
         }
+        mWifiLogger.startLogging(mVerboseLoggingLevel > 0);
         mWifiAutoJoinController.enableVerboseLogging(verbose);
         mWifiMonitor.enableVerboseLogging(verbose);
         mWifiNative.enableVerboseLogging(verbose);
@@ -1106,6 +1306,99 @@
         mSupplicantStateTracker.enableVerboseLogging(verbose);
     }
 
+    public void setHalBasedAutojoinOffload(int enabled) {
+        // Shoult be used for debug only, triggered form developper settings
+        // enabling HAl based PNO dynamically is not safe and not a normal operation
+        mHalBasedPnoEnableInDevSettings = enabled > 0;
+        mWifiConfigStore.enableHalBasedPno.set(mHalBasedPnoEnableInDevSettings);
+        mWifiConfigStore.enableSsidWhitelist.set(mHalBasedPnoEnableInDevSettings);
+        sendMessage(CMD_DISCONNECT);
+    }
+
+    int getHalBasedAutojoinOffload() {
+        return mHalBasedPnoEnableInDevSettings ? 1 : 0;
+    }
+
+    boolean useHalBasedAutoJoinOffload() {
+        // all three settings need to be true:
+        // - developper settings switch
+        // - driver support
+        // - config option
+        return mHalBasedPnoEnableInDevSettings
+                && mHalBasedPnoDriverSupported
+                && mWifiConfigStore.enableHalBasedPno.get();
+    }
+
+    boolean allowFullBandScanAndAssociated() {
+
+        if (!getEnableAutoJoinWhenAssociated()) {
+            if (DBG) {
+                Log.e(TAG, "allowFullBandScanAndAssociated: "
+                        + " enableAutoJoinWhenAssociated : disallow");
+            }
+            return false;
+        }
+
+        if (mWifiInfo.txSuccessRate >
+                mWifiConfigStore.maxTxPacketForFullScans
+                || mWifiInfo.rxSuccessRate >
+                mWifiConfigStore.maxRxPacketForFullScans) {
+            if (DBG) {
+                Log.e(TAG, "allowFullBandScanAndAssociated: packet rate tx"
+                        + mWifiInfo.txSuccessRate + "  rx "
+                        + mWifiInfo.rxSuccessRate
+                        + " allow scan with traffic " + getAllowScansWithTraffic());
+            }
+            // Too much traffic at the interface, hence no full band scan
+            if (getAllowScansWithTraffic() == 0) {
+                return false;
+            }
+        }
+
+        if (getCurrentState() != mConnectedState) {
+            if (DBG) {
+                Log.e(TAG, "allowFullBandScanAndAssociated: getCurrentState() : disallow");
+            }
+            return false;
+        }
+
+        return true;
+    }
+
+    long mLastScanPermissionUpdate = 0;
+    boolean mConnectedModeGScanOffloadStarted = false;
+    // Don't do a G-scan enable/re-enable cycle more than once within 20seconds
+    // The function updateAssociatedScanPermission() can be called quite frequently, hence
+    // we want to throttle the GScan Stop->Start transition
+    static final long SCAN_PERMISSION_UPDATE_THROTTLE_MILLI = 20000;
+    void updateAssociatedScanPermission() {
+
+        if (useHalBasedAutoJoinOffload()) {
+            boolean allowed = allowFullBandScanAndAssociated();
+
+            long now = System.currentTimeMillis();
+            if (mConnectedModeGScanOffloadStarted && !allowed) {
+                if (DBG) {
+                    Log.e(TAG, " useHalBasedAutoJoinOffload stop offload");
+                }
+                stopPnoOffload();
+                stopGScan(" useHalBasedAutoJoinOffload");
+            }
+            if (!mConnectedModeGScanOffloadStarted && allowed) {
+                if ((now - mLastScanPermissionUpdate) > SCAN_PERMISSION_UPDATE_THROTTLE_MILLI) {
+                    // Re-enable Gscan offload, this will trigger periodic scans and allow firmware
+                    // to look for 5GHz BSSIDs and better networks
+                    if (DBG) {
+                        Log.e(TAG, " useHalBasedAutoJoinOffload restart offload");
+                    }
+                    startGScanConnectedModeOffload("updatePermission "
+                            + (now - mLastScanPermissionUpdate) + "ms");
+                    mLastScanPermissionUpdate = now;
+                }
+            }
+        }
+    }
+
     private int mAggressiveHandover = 0;
 
     int getAggressiveHandover() {
@@ -1116,31 +1409,46 @@
         mAggressiveHandover = enabled;
     }
 
+    public void clearANQPCache() {
+        mWifiConfigStore.trimANQPCache(true);
+    }
+
     public void setAllowScansWithTraffic(int enabled) {
-        mWifiConfigStore.alwaysEnableScansWhileAssociated = enabled;
+        mWifiConfigStore.alwaysEnableScansWhileAssociated.set(enabled);
     }
 
     public int getAllowScansWithTraffic() {
-        return mWifiConfigStore.alwaysEnableScansWhileAssociated;
+        return mWifiConfigStore.alwaysEnableScansWhileAssociated.get();
     }
 
+    public boolean enableAutoJoinWhenAssociated(boolean enabled) {
+        boolean old_state = getEnableAutoJoinWhenAssociated();
+        mWifiConfigStore.enableAutoJoinWhenAssociated.set(enabled);
+        if (!old_state && enabled && mScreenOn && getCurrentState() == mConnectedState) {
+            startDelayedScan(mWifiConfigStore.wifiAssociatedShortScanIntervalMilli.get(), null,
+                    null);
+        }
+        return true;
+    }
+
+    public boolean getEnableAutoJoinWhenAssociated() {
+        return mWifiConfigStore.enableAutoJoinWhenAssociated.get();
+    }
     /*
      *
      * Framework scan control
      */
 
     private boolean mAlarmEnabled = false;
-    /* This is set from the overlay config file or from a secure setting.
-     * A value of 0 disables scanning in the framework.
-     */
-    private long mFrameworkScanIntervalMs = 10000;
 
     private AtomicInteger mDelayedScanCounter = new AtomicInteger();
 
     private void setScanAlarm(boolean enabled) {
         if (PDBG) {
-            loge("setScanAlarm " + enabled
-                    + " period " + mDefaultFrameworkScanIntervalMs
+            String state;
+            if (enabled) state = "enabled"; else state = "disabled";
+            logd("setScanAlarm " + state
+                    + " defaultperiod " + mDefaultFrameworkScanIntervalMs
                     + " mBackgroundScanSupported " + mBackgroundScanSupported);
         }
         if (mBackgroundScanSupported == false) {
@@ -1166,11 +1474,11 @@
 
     private void cancelDelayedScan() {
         mDelayedScanCounter.incrementAndGet();
-        loge("cancelDelayedScan -> " + mDelayedScanCounter);
     }
 
     private boolean checkAndRestartDelayedScan(int counter, boolean restart, int milli,
-                                   ScanSettings settings, WorkSource workSource) {
+                                               ScanSettings settings, WorkSource workSource) {
+
         if (counter != mDelayedScanCounter.get()) {
             return false;
         }
@@ -1192,21 +1500,21 @@
         mDelayedScanCounter.incrementAndGet();
         if (mScreenOn &&
                 (getCurrentState() == mDisconnectedState
-                || getCurrentState() == mConnectedState)) {
+                        || getCurrentState() == mConnectedState)) {
             Bundle bundle = new Bundle();
             bundle.putParcelable(CUSTOMIZED_SCAN_SETTING, settings);
             bundle.putParcelable(CUSTOMIZED_SCAN_WORKSOURCE, workSource);
             bundle.putLong(SCAN_REQUEST_TIME, System.currentTimeMillis());
             sendMessageDelayed(CMD_START_SCAN, SCAN_ALARM_SOURCE,
                     mDelayedScanCounter.get(), bundle, milli);
-            if (DBG) loge("startDelayedScan send -> " + mDelayedScanCounter + " milli " + milli);
+            if (DBG) logd("startDelayedScan send -> " + mDelayedScanCounter + " milli " + milli);
         } else if (mBackgroundScanSupported == false
                 && !mScreenOn && getCurrentState() == mDisconnectedState) {
             setScanAlarm(true);
-            if (DBG) loge("startDelayedScan start scan alarm -> "
+            if (DBG) logd("startDelayedScan start scan alarm -> "
                     + mDelayedScanCounter + " milli " + milli);
         } else {
-            if (DBG) loge("startDelayedScan unhandled -> "
+            if (DBG) logd("startDelayedScan unhandled -> "
                     + mDelayedScanCounter + " milli " + milli);
         }
     }
@@ -1224,9 +1532,11 @@
         return mWifiNative.setScanningMacOui(ouiBytes);
     }
 
-    /*********************************************************
+    /**
+     * ******************************************************
      * Methods exposed for public use
-     ********************************************************/
+     * ******************************************************
+     */
 
     public Messenger getMessenger() {
         return new Messenger(getHandler());
@@ -1262,7 +1572,8 @@
                     try {
                         c.channelNum = Integer.parseInt(prop[1]);
                         c.freqMHz = Integer.parseInt(prop[3]);
-                    } catch (NumberFormatException e) { }
+                    } catch (NumberFormatException e) {
+                    }
                     c.isDFS = line.contains("(DFS)");
                     list.add(c);
                 } else if (line.contains("Mode[B] Channels:")) {
@@ -1289,7 +1600,7 @@
      * @param callingUid The uid initiating the wifi scan. Blame will be given here unless
      *                   workSource is specified.
      * @param workSource If not null, blame is given to workSource.
-     * @param settings Scan settings, see {@link ScanSettings}.
+     * @param settings   Scan settings, see {@link ScanSettings}.
      */
     public void startScan(int callingUid, int scanCounter,
                           ScanSettings settings, WorkSource workSource) {
@@ -1300,322 +1611,36 @@
         sendMessage(CMD_START_SCAN, callingUid, scanCounter, bundle);
     }
 
-    /**
-     * start or stop batched scanning using the given settings
-     */
-    public void setBatchedScanSettings(BatchedScanSettings settings, int callingUid, int csph,
-            WorkSource workSource) {
-        Bundle bundle = new Bundle();
-        bundle.putParcelable(BATCHED_SETTING, settings);
-        bundle.putParcelable(BATCHED_WORKSOURCE, workSource);
-        sendMessage(CMD_SET_BATCHED_SCAN, callingUid, csph, bundle);
-    }
-
-    public List<BatchedScanResult> syncGetBatchedScanResultsList() {
-        synchronized (mBatchedScanResults) {
-            List<BatchedScanResult> batchedScanList =
-                    new ArrayList<BatchedScanResult>(mBatchedScanResults.size());
-            for(BatchedScanResult result: mBatchedScanResults) {
-                batchedScanList.add(new BatchedScanResult(result));
-            }
-            return batchedScanList;
-        }
-    }
-
-    public void requestBatchedScanPoll() {
-        sendMessage(CMD_POLL_BATCHED_SCAN);
-    }
-
-    private void startBatchedScan() {
-        if (mBatchedScanSettings == null) return;
-
-        if (mDhcpActive) {
-            if (DBG) log("not starting Batched Scans due to DHCP");
-            return;
-        }
-
-        // first grab any existing data
-        retrieveBatchedScanData();
-
-        if (PDBG) loge("try  starting Batched Scans due to DHCP");
-
-
-        mAlarmManager.cancel(mBatchedScanIntervalIntent);
-
-        String scansExpected = mWifiNative.setBatchedScanSettings(mBatchedScanSettings);
-        try {
-            mExpectedBatchedScans = Integer.parseInt(scansExpected);
-            setNextBatchedAlarm(mExpectedBatchedScans);
-            if (mExpectedBatchedScans > 0) noteBatchedScanStart();
-        } catch (NumberFormatException e) {
-            stopBatchedScan();
-            loge("Exception parsing WifiNative.setBatchedScanSettings response " + e);
-        }
-    }
-
     // called from BroadcastListener
-    private void startNextBatchedScanAsync() {
-        sendMessage(CMD_START_NEXT_BATCHED_SCAN);
-    }
-
-    private void startNextBatchedScan() {
-        // first grab any existing data
-        retrieveBatchedScanData();
-
-        setNextBatchedAlarm(mExpectedBatchedScans);
-    }
-
-    private void handleBatchedScanPollRequest() {
-        if (DBG) {
-            log("handleBatchedScanPoll Request - mBatchedScanMinPollTime=" +
-                    mBatchedScanMinPollTime + " , mBatchedScanSettings=" +
-                    mBatchedScanSettings);
-        }
-        // if there is no appropriate PollTime that's because we either aren't
-        // batching or we've already set a time for a poll request
-        if (mBatchedScanMinPollTime == 0) return;
-        if (mBatchedScanSettings == null) return;
-
-        long now = System.currentTimeMillis();
-
-        if (now > mBatchedScanMinPollTime) {
-            // do the poll and reset our timers
-            startNextBatchedScan();
-        } else {
-            mAlarmManager.setExact(AlarmManager.RTC_WAKEUP, mBatchedScanMinPollTime,
-                    mBatchedScanIntervalIntent);
-            mBatchedScanMinPollTime = 0;
-        }
-    }
-
-    // return true if new/different
-    private boolean recordBatchedScanSettings(int responsibleUid, int csph, Bundle bundle) {
-        BatchedScanSettings settings = bundle.getParcelable(BATCHED_SETTING);
-        WorkSource responsibleWorkSource = bundle.getParcelable(BATCHED_WORKSOURCE);
-
-        if (DBG) {
-            log("set batched scan to " + settings + " for uid=" + responsibleUid +
-                    ", worksource=" + responsibleWorkSource);
-        }
-        if (settings != null) {
-            if (settings.equals(mBatchedScanSettings)) return false;
-        } else {
-            if (mBatchedScanSettings == null) return false;
-        }
-        mBatchedScanSettings = settings;
-        if (responsibleWorkSource == null) responsibleWorkSource = new WorkSource(responsibleUid);
-        mBatchedScanWorkSource = responsibleWorkSource;
-        mBatchedScanCsph = csph;
-        return true;
-    }
-
-    private void stopBatchedScan() {
-        mAlarmManager.cancel(mBatchedScanIntervalIntent);
-        retrieveBatchedScanData();
-        mWifiNative.setBatchedScanSettings(null);
-        noteBatchedScanStop();
-    }
-
-    private void setNextBatchedAlarm(int scansExpected) {
-
-        if (mBatchedScanSettings == null || scansExpected < 1) return;
-
-        mBatchedScanMinPollTime = System.currentTimeMillis() +
-                mBatchedScanSettings.scanIntervalSec * 1000;
-
-        if (mBatchedScanSettings.maxScansPerBatch < scansExpected) {
-            scansExpected = mBatchedScanSettings.maxScansPerBatch;
-        }
-
-        int secToFull = mBatchedScanSettings.scanIntervalSec;
-        secToFull *= scansExpected;
-
-        int debugPeriod = SystemProperties.getInt("wifi.batchedScan.pollPeriod", 0);
-        if (debugPeriod > 0) secToFull = debugPeriod;
-
-        // set the alarm to do the next poll.  We set it a little short as we'd rather
-        // wake up wearly than miss a scan due to buffer overflow
-        mAlarmManager.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()
-                + ((secToFull - (mBatchedScanSettings.scanIntervalSec / 2)) * 1000),
-                mBatchedScanIntervalIntent);
-    }
 
     /**
      * Start reading new scan data
      * Data comes in as:
      * "scancount=5\n"
      * "nextcount=5\n"
-     *   "apcount=3\n"
-     *   "trunc\n" (optional)
-     *     "bssid=...\n"
-     *     "ssid=...\n"
-     *     "freq=...\n" (in Mhz)
-     *     "level=...\n"
-     *     "dist=...\n" (in cm)
-     *     "distsd=...\n" (standard deviation, in cm)
-     *     "===="
-     *     "bssid=...\n"
-     *     etc
-     *     "===="
-     *     "bssid=...\n"
-     *     etc
-     *     "%%%%"
-     *   "apcount=2\n"
-     *     "bssid=...\n"
-     *     etc
-     *     "%%%%
-     *   etc
-     *   "----"
+     * "apcount=3\n"
+     * "trunc\n" (optional)
+     * "bssid=...\n"
+     * "ssid=...\n"
+     * "freq=...\n" (in Mhz)
+     * "level=...\n"
+     * "dist=...\n" (in cm)
+     * "distsd=...\n" (standard deviation, in cm)
+     * "===="
+     * "bssid=...\n"
+     * etc
+     * "===="
+     * "bssid=...\n"
+     * etc
+     * "%%%%"
+     * "apcount=2\n"
+     * "bssid=...\n"
+     * etc
+     * "%%%%
+     * etc
+     * "----"
      */
     private final static boolean DEBUG_PARSE = false;
-    private void retrieveBatchedScanData() {
-        String rawData = mWifiNative.getBatchedScanResults();
-        if (DEBUG_PARSE) log("rawData = " + rawData);
-        mBatchedScanMinPollTime = 0;
-        if (rawData == null || rawData.equalsIgnoreCase("OK")) {
-            loge("Unexpected BatchedScanResults :" + rawData);
-            return;
-        }
-
-        int scanCount = 0;
-        final String END_OF_BATCHES = "----";
-        final String SCANCOUNT = "scancount=";
-        final String TRUNCATED = "trunc";
-        final String AGE = "age=";
-        final String DIST = "dist=";
-        final String DISTSD = "distSd=";
-
-        String splitData[] = rawData.split("\n");
-        int n = 0;
-        if (splitData[n].startsWith(SCANCOUNT)) {
-            try {
-                scanCount = Integer.parseInt(splitData[n++].substring(SCANCOUNT.length()));
-            } catch (NumberFormatException e) {
-                loge("scancount parseInt Exception from " + splitData[n]);
-            }
-        } else log("scancount not found");
-        if (scanCount == 0) {
-            loge("scanCount==0 - aborting");
-            return;
-        }
-
-        final Intent intent = new Intent(WifiManager.BATCHED_SCAN_RESULTS_AVAILABLE_ACTION);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-
-        synchronized (mBatchedScanResults) {
-            mBatchedScanResults.clear();
-            BatchedScanResult batchedScanResult = new BatchedScanResult();
-
-            String bssid = null;
-            WifiSsid wifiSsid = null;
-            int level = 0;
-            int freq = 0;
-            int dist, distSd;
-            long tsf = 0;
-            dist = distSd = ScanResult.UNSPECIFIED;
-            final long now = SystemClock.elapsedRealtime();
-            final int bssidStrLen = BSSID_STR.length();
-
-            while (true) {
-                while (n < splitData.length) {
-                    if (DEBUG_PARSE) logd("parsing " + splitData[n]);
-                    if (splitData[n].equals(END_OF_BATCHES)) {
-                        if (n+1 != splitData.length) {
-                            loge("didn't consume " + (splitData.length-n));
-                        }
-                        if (mBatchedScanResults.size() > 0) {
-                            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
-                        }
-                        logd("retrieveBatchedScanResults X");
-                        return;
-                    }
-                    if ((splitData[n].equals(END_STR)) || splitData[n].equals(DELIMITER_STR)) {
-                        if (bssid != null) {
-                            batchedScanResult.scanResults.add(new ScanResult(
-                                    wifiSsid, bssid, "", level, freq, tsf, dist, distSd));
-                            wifiSsid = null;
-                            bssid = null;
-                            level = 0;
-                            freq = 0;
-                            tsf = 0;
-                            dist = distSd = ScanResult.UNSPECIFIED;
-                        }
-                        if (splitData[n].equals(END_STR)) {
-                            if (batchedScanResult.scanResults.size() != 0) {
-                                mBatchedScanResults.add(batchedScanResult);
-                                batchedScanResult = new BatchedScanResult();
-                            } else {
-                                logd("Found empty batch");
-                            }
-                        }
-                    } else if (splitData[n].equals(TRUNCATED)) {
-                        batchedScanResult.truncated = true;
-                    } else if (splitData[n].startsWith(BSSID_STR)) {
-                        bssid = new String(splitData[n].getBytes(), bssidStrLen,
-                                splitData[n].length() - bssidStrLen);
-                    } else if (splitData[n].startsWith(FREQ_STR)) {
-                        try {
-                            freq = Integer.parseInt(splitData[n].substring(FREQ_STR.length()));
-                        } catch (NumberFormatException e) {
-                            loge("Invalid freqency: " + splitData[n]);
-                            freq = 0;
-                        }
-                    } else if (splitData[n].startsWith(AGE)) {
-                        try {
-                            tsf = now - Long.parseLong(splitData[n].substring(AGE.length()));
-                            tsf *= 1000; // convert mS -> uS
-                        } catch (NumberFormatException e) {
-                            loge("Invalid timestamp: " + splitData[n]);
-                            tsf = 0;
-                        }
-                    } else if (splitData[n].startsWith(SSID_STR)) {
-                        wifiSsid = WifiSsid.createFromAsciiEncoded(
-                                splitData[n].substring(SSID_STR.length()));
-                    } else if (splitData[n].startsWith(LEVEL_STR)) {
-                        try {
-                            level = Integer.parseInt(splitData[n].substring(LEVEL_STR.length()));
-                            if (level > 0) level -= 256;
-                        } catch (NumberFormatException e) {
-                            loge("Invalid level: " + splitData[n]);
-                            level = 0;
-                        }
-                    } else if (splitData[n].startsWith(DIST)) {
-                        try {
-                            dist = Integer.parseInt(splitData[n].substring(DIST.length()));
-                        } catch (NumberFormatException e) {
-                            loge("Invalid distance: " + splitData[n]);
-                            dist = ScanResult.UNSPECIFIED;
-                        }
-                    } else if (splitData[n].startsWith(DISTSD)) {
-                        try {
-                            distSd = Integer.parseInt(splitData[n].substring(DISTSD.length()));
-                        } catch (NumberFormatException e) {
-                            loge("Invalid distanceSd: " + splitData[n]);
-                            distSd = ScanResult.UNSPECIFIED;
-                        }
-                    } else {
-                        loge("Unable to parse batched scan result line: " + splitData[n]);
-                    }
-                    n++;
-                }
-                rawData = mWifiNative.getBatchedScanResults();
-                if (DEBUG_PARSE) log("reading more data:\n" + rawData);
-                if (rawData == null) {
-                    loge("Unexpected null BatchedScanResults");
-                    return;
-                }
-                splitData = rawData.split("\n");
-                if (splitData.length == 0 || splitData[0].equals("ok")) {
-                    loge("batch scan results just ended!");
-                    if (mBatchedScanResults.size() > 0) {
-                        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
-                    }
-                    return;
-                }
-                n = 0;
-            }
-        }
-    }
 
     private long mDisconnectedTimeStamp = 0;
 
@@ -1633,7 +1658,7 @@
     private long lastScanDuration = 0;
     // Last connect attempt is used to prevent scan requests:
     //  - for a period of 10 seconds after attempting to connect
-    private long lastConnectAttempt = 0;
+    private long lastConnectAttemptTimestamp = 0;
     private String lastScanFreqs = null;
 
     // For debugging, keep track of last message status handling
@@ -1655,9 +1680,9 @@
     //TODO: second logic should be folded into that
     private boolean checkOrDeferScanAllowed(Message msg) {
         long now = System.currentTimeMillis();
-        if (lastConnectAttempt != 0 && (now - lastConnectAttempt) < 10000) {
+        if (lastConnectAttemptTimestamp != 0 && (now - lastConnectAttemptTimestamp) < 10000) {
             Message dmsg = Message.obtain(msg);
-            sendMessageDelayed(dmsg, 11000 - (now - lastConnectAttempt));
+            sendMessageDelayed(dmsg, 11000 - (now - lastConnectAttemptTimestamp));
             return false;
         }
         return true;
@@ -1696,12 +1721,12 @@
         mTxTimeLastReport = mTxTime;
         int rx = mRxTime - mRxTimeLastReport;
         mRxTimeLastReport = mRxTime;
-        int period = (int)(now - lastOntimeReportTimeStamp);
+        int period = (int) (now - lastOntimeReportTimeStamp);
         lastOntimeReportTimeStamp = now;
         sb.append(String.format("[on:%d tx:%d rx:%d period:%d]", on, tx, rx, period));
         // Report stats since Screen State Changed
         on = mOnTime - mOnTimeScreenStateChange;
-        period = (int)(now - lastScreenStateChangeTimeStamp);
+        period = (int) (now - lastScreenStateChangeTimeStamp);
         sb.append(String.format(" from screen [on:%d period:%d]", on, period));
         return sb.toString();
     }
@@ -1719,9 +1744,6 @@
                 mTxTime = stats.tx_time;
                 mRxTime = stats.rx_time;
                 mRunningBeaconCount = stats.beacon_rx;
-                if (dbg) {
-                    loge(stats.toString());
-                }
             }
         }
         if (stats == null || mWifiLinkLayerStatsSupported <= 0) {
@@ -1731,6 +1753,9 @@
         } else {
             mWifiInfo.updatePacketRates(stats);
         }
+        if (useHalBasedAutoJoinOffload()) {
+            sendMessage(CMD_UPDATE_ASSOCIATED_SCAN_PERMISSION);
+        }
         return stats;
     }
 
@@ -1766,10 +1791,10 @@
         if (DBG) {
             String ts = String.format("[%,d ms]", now);
             if (workSource != null) {
-                loge(ts + " noteScanStart" + workSource.toString()
+                if (DBG) logd(ts + " noteScanStart" + workSource.toString()
                         + " uid " + Integer.toString(callingUid));
             } else {
-                loge(ts + " noteScanstart no scan source"
+                if (DBG) logd(ts + " noteScanstart no scan source"
                         + " uid " + Integer.toString(callingUid));
             }
         }
@@ -1778,8 +1803,15 @@
                 && callingUid != SCAN_ALARM_SOURCE)
                 || workSource != null)) {
             mScanWorkSource = workSource != null ? workSource : new WorkSource(callingUid);
+
+            WorkSource batteryWorkSource = mScanWorkSource;
+            if (mScanWorkSource.size() == 1 && mScanWorkSource.get(0) < 0) {
+                // WiFi uses negative UIDs to mean special things. BatteryStats don't care!
+                batteryWorkSource = new WorkSource(Process.WIFI_UID);
+            }
+
             try {
-                mBatteryStats.noteWifiScanStartedFromSource(mScanWorkSource);
+                mBatteryStats.noteWifiScanStartedFromSource(batteryWorkSource);
             } catch (RemoteException e) {
                 log(e.toString());
             }
@@ -1795,10 +1827,10 @@
         if (DBG) {
             String ts = String.format("[%,d ms]", now);
             if (mScanWorkSource != null)
-                loge(ts + " noteScanEnd " + mScanWorkSource.toString()
+                logd(ts + " noteScanEnd " + mScanWorkSource.toString()
                         + " onTime=" + mOnTimeThisScan);
             else
-                loge(ts + " noteScanEnd no scan source"
+                logd(ts + " noteScanEnd no scan source"
                         + " onTime=" + mOnTimeThisScan);
         }
         if (mScanWorkSource != null) {
@@ -1812,47 +1844,6 @@
         }
     }
 
-    private void noteBatchedScanStart() {
-        if (PDBG) loge("noteBatchedScanstart()");
-        // note the end of a previous scan set
-        if (mNotedBatchedScanWorkSource != null &&
-                (mNotedBatchedScanWorkSource.equals(mBatchedScanWorkSource) == false ||
-                 mNotedBatchedScanCsph != mBatchedScanCsph)) {
-            try {
-                mBatteryStats.noteWifiBatchedScanStoppedFromSource(mNotedBatchedScanWorkSource);
-            } catch (RemoteException e) {
-                log(e.toString());
-            } finally {
-                mNotedBatchedScanWorkSource = null;
-                mNotedBatchedScanCsph = 0;
-            }
-        }
-        // note the start of the new
-        try {
-            mBatteryStats.noteWifiBatchedScanStartedFromSource(mBatchedScanWorkSource,
-                    mBatchedScanCsph);
-            mNotedBatchedScanWorkSource = mBatchedScanWorkSource;
-            mNotedBatchedScanCsph = mBatchedScanCsph;
-        } catch (RemoteException e) {
-            log(e.toString());
-        }
-    }
-
-    private void noteBatchedScanStop() {
-        if (PDBG) loge("noteBatchedScanstop()");
-
-        if (mNotedBatchedScanWorkSource != null) {
-            try {
-                mBatteryStats.noteWifiBatchedScanStoppedFromSource(mNotedBatchedScanWorkSource);
-            } catch (RemoteException e) {
-                log(e.toString());
-            } finally {
-                mNotedBatchedScanWorkSource = null;
-                mNotedBatchedScanCsph = 0;
-            }
-        }
-    }
-
     private void handleScanRequest(int type, Message message) {
         ScanSettings settings = null;
         WorkSource workSource = null;
@@ -1871,7 +1862,8 @@
             StringBuilder sb = new StringBuilder();
             boolean first = true;
             for (WifiChannel channel : settings.channelSet) {
-                if (!first) sb.append(','); else first = false;
+                if (!first) sb.append(',');
+                else first = false;
                 sb.append(channel.freqMHz);
             }
             freqs = sb.toString();
@@ -1929,7 +1921,9 @@
     }
 
 
-    /** return true iff scan request is accepted */
+    /**
+     * return true iff scan request is accepted
+     */
     private boolean startScanNative(int type, String freqs) {
         if (mWifiNative.scan(type, freqs)) {
             mIsScanOngoing = true;
@@ -2029,11 +2023,11 @@
 
     /**
      * Get status information for the current connection, if any.
-     * @return a {@link WifiInfo} object containing information about the current connection
      *
+     * @return a {@link WifiInfo} object containing information about the current connection
      */
     public WifiInfo syncRequestConnectionInfo() {
-        return mWifiInfo;
+        return getWiFiInfoForUid(Binder.getCallingUid());
     }
 
     public DhcpResults syncGetDhcpResults() {
@@ -2067,8 +2061,8 @@
     public List<ScanResult> syncGetScanResultsList() {
         synchronized (mScanResultCache) {
             List<ScanResult> scanList = new ArrayList<ScanResult>();
-            for(ScanResult result: mScanResults) {
-                scanList.add(new ScanResult(result));
+            for (ScanDetail result : mScanResults) {
+                scanList.add(new ScanResult(result.getScanResult()));
             }
             return scanList;
         }
@@ -2084,7 +2078,7 @@
      * Get unsynchronized pointer to scan result list
      * Can be called only from AutoJoinController which runs in the WifiStateMachine context
      */
-    public List<ScanResult> getScanResultsListNoCopyUnsync() {
+    public List<ScanDetail> getScanResultsListNoCopyUnsync() {
         return mScanResults;
     }
 
@@ -2135,6 +2129,7 @@
 
     /**
      * Get configured networks synchronously
+     *
      * @param channel
      * @return
      */
@@ -2154,9 +2149,14 @@
         return result;
     }
 
+    public WifiConfiguration syncGetMatchingWifiConfig(ScanResult scanResult, AsyncChannel channel) {
+        Message resultMsg = channel.sendMessageSynchronously(CMD_GET_MATCHING_CONFIG, scanResult);
+        return (WifiConfiguration) resultMsg.obj;
+    }
 
     /**
      * Get connection statistics synchronously
+     *
      * @param channel
      * @return
      */
@@ -2204,7 +2204,7 @@
     /**
      * Enable a network
      *
-     * @param netId network id of the network
+     * @param netId         network id of the network
      * @param disableOthers true, if all other networks have to be disabled
      * @return {@code true} if the operation succeeds, {@code false} otherwise
      */
@@ -2231,6 +2231,7 @@
 
     /**
      * Retrieves a WPS-NFC configuration token for the specified network
+     *
      * @return a hex string representation of the WPS-NFC configuration token
      */
     public String syncGetWpsNfcConfigurationToken(int netId) {
@@ -2241,7 +2242,12 @@
         if (enable) {
             mWifiConfigStore.enableAllNetworks();
         }
-        mWifiNative.enableBackgroundScan(enable);
+        boolean ret = mWifiNative.enableBackgroundScan(enable);
+        if (ret) {
+            mLegacyPnoEnabled = enable;
+        } else {
+            Log.e(TAG, " Fail to set up pno, want " + enable + " now " + mLegacyPnoEnabled);
+        }
     }
 
     /**
@@ -2256,14 +2262,13 @@
 
     /**
      * Clear the blacklist list
-     *
      */
     public void clearBlacklist() {
         sendMessage(CMD_CLEAR_BLACKLIST);
     }
 
     public void enableRssiPolling(boolean enabled) {
-       sendMessage(CMD_ENABLE_RSSI_POLL, enabled ? 1 : 0, 0);
+        sendMessage(CMD_ENABLE_RSSI_POLL, enabled ? 1 : 0, 0);
     }
 
     public void enableAllNetworks() {
@@ -2304,6 +2309,7 @@
      * Set high performance mode of operation.
      * Enabling would set active power mode and disable suspend optimizations;
      * disabling would set auto power mode and enable suspend optimizations
+     *
      * @param enable true if enable, false otherwise
      */
     public void setHighPerfModeEnabled(boolean enable) {
@@ -2312,24 +2318,63 @@
 
     /**
      * Set the country code
+     *
      * @param countryCode following ISO 3166 format
-     * @param persist {@code true} if the setting should be remembered.
+     * @param persist     {@code true} if the setting should be remembered.
      */
-    public void setCountryCode(String countryCode, boolean persist) {
+    public synchronized void setCountryCode(String countryCode, boolean persist) {
         // If it's a good country code, apply after the current
         // wifi connection is terminated; ignore resetting of code
         // for now (it is unclear what the chipset should do when
         // country code is reset)
-        int countryCodeSequence = mCountryCodeSequence.incrementAndGet();
+
         if (TextUtils.isEmpty(countryCode)) {
             log("Ignoring resetting of country code");
         } else {
-            sendMessage(CMD_SET_COUNTRY_CODE, countryCodeSequence, persist ? 1 : 0, countryCode);
+            // if mCountryCodeSequence == 0, it is the first time to set country code, always set
+            // else only when the new country code is different from the current one to set
+            int countryCodeSequence = mCountryCodeSequence.get();
+            if (countryCodeSequence == 0 || countryCode.equals(mSetCountryCode) == false) {
+
+                countryCodeSequence = mCountryCodeSequence.incrementAndGet();
+                mSetCountryCode = countryCode;
+                sendMessage(CMD_SET_COUNTRY_CODE, countryCodeSequence, persist ? 1 : 0,
+                        countryCode);
+            }
+
+            if (persist) {
+                Settings.Global.putString(mContext.getContentResolver(),
+                        Settings.Global.WIFI_COUNTRY_CODE,
+                        countryCode);
+            }
         }
     }
 
     /**
+     * Get Network object of current wifi network
+     * @return Network object of current wifi network
+     */
+    public Network getCurrentNetwork() {
+        if (mNetworkAgent != null) {
+            return new Network(mNetworkAgent.netId);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Get the country code
+     *
+     * @param countryCode following ISO 3166 format
+     */
+    public String getCountryCode() {
+        return mSetCountryCode;
+    }
+
+
+    /**
      * Set the operational frequency band
+     *
      * @param band
      * @param persist {@code true} if the setting should be remembered.
      */
@@ -2372,10 +2417,28 @@
     }
 
     /**
+     * Send a message indicating a package has been uninstalled.
+     */
+    public void removeAppConfigs(String packageName, int uid) {
+        // Build partial AppInfo manually - package may not exist in database any more
+        ApplicationInfo ai = new ApplicationInfo();
+        ai.packageName = packageName;
+        ai.uid = uid;
+        sendMessage(CMD_REMOVE_APP_CONFIGURATIONS, ai);
+    }
+
+    /**
+     * Send a message indicating a user has been removed.
+     */
+    public void removeUserConfigs(int userId) {
+        sendMessage(CMD_REMOVE_USER_CONFIGURATIONS, userId);
+    }
+
+    /**
      * Save configuration on supplicant
      *
      * @return {@code true} if the operation succeeds, {@code false} otherwise
-     *
+     * <p/>
      * TODO: deprecate this
      */
     public boolean syncSaveConfig(AsyncChannel channel) {
@@ -2435,18 +2498,31 @@
         pw.println("mUserWantsSuspendOpt " + mUserWantsSuspendOpt);
         pw.println("mSuspendOptNeedsDisabled " + mSuspendOptNeedsDisabled);
         pw.println("Supplicant status " + mWifiNative.status(true));
-        pw.println("mEnableBackgroundScan " + mEnableBackgroundScan);
-        pw.println("mLastSetCountryCode " + mLastSetCountryCode);
-        pw.println("mPersistedCountryCode " + mPersistedCountryCode);
+        pw.println("mLegacyPnoEnabled " + mLegacyPnoEnabled);
+        pw.println("mSetCountryCode " + mSetCountryCode);
+        pw.println("mDriverSetCountryCode " + mDriverSetCountryCode);
+        pw.println("mConnectedModeGScanOffloadStarted " + mConnectedModeGScanOffloadStarted);
+        pw.println("mGScanPeriodMilli " + mGScanPeriodMilli);
+        if (mWhiteListedSsids != null && mWhiteListedSsids.length > 0) {
+            pw.println("SSID whitelist :" );
+            for (int i=0; i < mWhiteListedSsids.length; i++) {
+                pw.println("       " + mWhiteListedSsids[i]);
+            }
+        }
         mNetworkFactory.dump(fd, pw, args);
         mUntrustedNetworkFactory.dump(fd, pw, args);
         pw.println();
         mWifiConfigStore.dump(fd, pw, args);
+        pw.println();
+        mWifiLogger.captureBugReportData(WifiLogger.REPORT_REASON_USER_ACTION);
+        mWifiLogger.dump(fd, pw, args);
     }
 
-    /*********************************************************
+    /**
+     * ******************************************************
      * Internal private functions
-     ********************************************************/
+     * ******************************************************
+     */
 
     private void logStateAndMessage(Message message, String state) {
         messageHandlingStatus = 0;
@@ -2454,7 +2530,7 @@
             //long now = SystemClock.elapsedRealtimeNanos();
             //String ts = String.format("[%,d us]", now/1000);
 
-            loge( " " + state + " " + getLogRecString(message));
+            logd(" " + state + " " + getLogRecString(message));
         }
     }
 
@@ -2490,7 +2566,58 @@
         if (msg.sendingUid > 0 && msg.sendingUid != Process.WIFI_UID) {
             sb.append(" uid=" + msg.sendingUid);
         }
+        sb.append(" ").append(printTime());
         switch (msg.what) {
+            case CMD_STARTED_GSCAN_DBG:
+            case CMD_STARTED_PNO_DBG:
+                sb.append(" ");
+                sb.append(Integer.toString(msg.arg1));
+                sb.append(" ");
+                sb.append(Integer.toString(msg.arg2));
+                if (msg.obj != null) {
+                    sb.append(" " + (String)msg.obj);
+                }
+                break;
+            case CMD_RESTART_AUTOJOIN_OFFLOAD:
+                sb.append(" ");
+                sb.append(Integer.toString(msg.arg1));
+                sb.append(" ");
+                sb.append(Integer.toString(msg.arg2));
+                sb.append("/").append(Integer.toString(mRestartAutoJoinOffloadCounter));
+                if (msg.obj != null) {
+                    sb.append(" " + (String)msg.obj);
+                }
+                break;
+            case CMD_UPDATE_ASSOCIATED_SCAN_PERMISSION:
+                sb.append(" ");
+                sb.append(Integer.toString(msg.arg1));
+                sb.append(" ");
+                sb.append(Integer.toString(msg.arg2));
+                sb.append(" halAllowed=").append(useHalBasedAutoJoinOffload());
+                sb.append(" scanAllowed=").append(allowFullBandScanAndAssociated());
+                sb.append(" autojoinAllowed=");
+                sb.append(mWifiConfigStore.enableAutoJoinWhenAssociated.get());
+                sb.append(" withTraffic=").append(getAllowScansWithTraffic());
+                sb.append(" tx=").append(mWifiInfo.txSuccessRate);
+                sb.append("/").append(mWifiConfigStore.maxTxPacketForFullScans);
+                sb.append(" rx=").append(mWifiInfo.rxSuccessRate);
+                sb.append("/").append(mWifiConfigStore.maxRxPacketForFullScans);
+                sb.append(" -> ").append(mConnectedModeGScanOffloadStarted);
+                break;
+            case CMD_PNO_NETWORK_FOUND:
+                sb.append(" ");
+                sb.append(Integer.toString(msg.arg1));
+                sb.append(" ");
+                sb.append(Integer.toString(msg.arg2));
+                if (msg.obj != null) {
+                    ScanResult[] results = (ScanResult[])msg.obj;
+                    for (int i = 0; i < results.length; i++) {
+                       sb.append(" ").append(results[i].SSID).append(" ");
+                       sb.append(results[i].frequency);
+                       sb.append(" ").append(results[i].level);
+                    }
+                }
+                break;
             case CMD_START_SCAN:
                 now = System.currentTimeMillis();
                 sb.append(" ");
@@ -2500,7 +2627,7 @@
                 sb.append(" ic=");
                 sb.append(Integer.toString(sScanAlarmIntentCount));
                 if (msg.obj != null) {
-                    Bundle bundle = (Bundle)msg.obj;
+                    Bundle bundle = (Bundle) msg.obj;
                     Long request = bundle.getLong(SCAN_REQUEST_TIME, 0);
                     if (request != 0) {
                         sb.append(" proc(ms):").append(now - request);
@@ -2539,7 +2666,6 @@
                 sb.append(Integer.toString(msg.arg1));
                 sb.append(" ");
                 sb.append(Integer.toString(msg.arg2));
-                sb.append(printTime());
                 StateChangeResult stateChangeResult = (StateChangeResult) msg.obj;
                 if (stateChangeResult != null) {
                     sb.append(stateChangeResult.toString());
@@ -2601,13 +2727,12 @@
                 sb.append(Integer.toString(msg.arg1));
                 sb.append(" ");
                 sb.append(Integer.toString(msg.arg2));
-                String bssid = (String)msg.obj;
-                if (bssid != null && bssid.length()>0) {
+                String bssid = (String) msg.obj;
+                if (bssid != null && bssid.length() > 0) {
                     sb.append(" ");
                     sb.append(bssid);
                 }
                 sb.append(" blacklist=" + Boolean.toString(didBlackListBSSID));
-                sb.append(printTime());
                 break;
             case WifiMonitor.SCAN_RESULTS_EVENT:
                 sb.append(" ");
@@ -2642,6 +2767,8 @@
                     sb.append(" last=").append(key);
                 }
                 break;
+            case WifiMonitor.SCAN_FAILED_EVENT:
+                break;
             case WifiMonitor.NETWORK_CONNECTION_EVENT:
                 sb.append(" ");
                 sb.append(Integer.toString(msg.arg1));
@@ -2653,7 +2780,6 @@
                 if (config != null) {
                     sb.append(" ").append(config.configKey());
                 }
-                sb.append(printTime());
                 key = mWifiConfigStore.getLastSelectedConfiguration();
                 if (key != null) {
                     sb.append(" last=").append(key);
@@ -2666,17 +2792,16 @@
                 sb.append(" ");
                 sb.append(Integer.toString(msg.arg2));
                 if (msg.obj != null) {
-                    sb.append(" BSSID=").append((String)msg.obj);
+                    sb.append(" BSSID=").append((String) msg.obj);
                 }
                 if (mTargetRoamBSSID != null) {
                     sb.append(" Target=").append(mTargetRoamBSSID);
                 }
                 sb.append(" roam=").append(Integer.toString(mAutoRoaming));
-                sb.append(printTime());
                 break;
             case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
                 if (msg.obj != null) {
-                    sb.append(" ").append((String)msg.obj);
+                    sb.append(" ").append((String) msg.obj);
                 }
                 sb.append(" nid=").append(msg.arg1);
                 sb.append(" reason=").append(msg.arg2);
@@ -2690,13 +2815,12 @@
                 if (linkDebouncing) {
                     sb.append(" debounce");
                 }
-                sb.append(printTime());
                 break;
             case WifiMonitor.SSID_TEMP_DISABLED:
             case WifiMonitor.SSID_REENABLED:
                 sb.append(" nid=").append(msg.arg1);
                 if (msg.obj != null) {
-                    sb.append(" ").append((String)msg.obj);
+                    sb.append(" ").append((String) msg.obj);
                 }
                 config = getCurrentWifiConfiguration();
                 if (config != null) {
@@ -2722,7 +2846,6 @@
                         sb.append(" bssid=").append(mWifiInfo.getBSSID());
                     }
                 }
-                sb.append(printTime());
                 break;
             case CMD_RSSI_POLL:
             case CMD_UNWANTED_NETWORK:
@@ -2732,8 +2855,8 @@
                 sb.append(" ");
                 sb.append(Integer.toString(msg.arg2));
                 if (mWifiInfo.getSSID() != null)
-                if (mWifiInfo.getSSID() != null)
-                    sb.append(" ").append(mWifiInfo.getSSID());
+                    if (mWifiInfo.getSSID() != null)
+                        sb.append(" ").append(mWifiInfo.getSSID());
                 if (mWifiInfo.getBSSID() != null)
                     sb.append(" ").append(mWifiInfo.getBSSID());
                 sb.append(" rssi=").append(mWifiInfo.getRssi());
@@ -2752,6 +2875,11 @@
                 if (wifiScoringReport != null) {
                     sb.append(wifiScoringReport);
                 }
+                if (mConnectedModeGScanOffloadStarted) {
+                    sb.append(" offload-started periodMilli " + mGScanPeriodMilli);
+                } else {
+                    sb.append(" offload-stopped");
+                }
                 break;
             case CMD_AUTO_CONNECT:
             case WifiManager.CONNECT_NETWORK:
@@ -2770,7 +2898,6 @@
                     sb.append(" ").append(mTargetRoamBSSID);
                 }
                 sb.append(" roam=").append(Integer.toString(mAutoRoaming));
-                sb.append(printTime());
                 config = getCurrentWifiConfiguration();
                 if (config != null) {
                     sb.append(config.configKey());
@@ -2784,7 +2911,7 @@
                 sb.append(Integer.toString(msg.arg1));
                 sb.append(" ");
                 sb.append(Integer.toString(msg.arg2));
-                ScanResult result = (ScanResult)msg.obj;
+                ScanResult result = (ScanResult) msg.obj;
                 if (result != null) {
                     now = System.currentTimeMillis();
                     sb.append(" bssid=").append(result.BSSID);
@@ -2802,7 +2929,6 @@
                 }
                 sb.append(" roam=").append(Integer.toString(mAutoRoaming));
                 sb.append(" fail count=").append(Integer.toString(mRoamFailCount));
-                sb.append(printTime());
                 break;
             case CMD_ADD_OR_UPDATE_NETWORK:
                 sb.append(" ");
@@ -2810,7 +2936,7 @@
                 sb.append(" ");
                 sb.append(Integer.toString(msg.arg2));
                 if (msg.obj != null) {
-                    config = (WifiConfiguration)msg.obj;
+                    config = (WifiConfiguration) msg.obj;
                     sb.append(" ").append(config.configKey());
                     sb.append(" prio=").append(config.priority);
                     sb.append(" status=").append(config.status);
@@ -2897,7 +3023,7 @@
                 sb.append(" ");
                 sb.append(Integer.toString(msg.arg2));
                 if (msg.obj != null) {
-                    NetworkInfo info = (NetworkInfo)msg.obj;
+                    NetworkInfo info = (NetworkInfo) msg.obj;
                     NetworkInfo.State state = info.getState();
                     NetworkInfo.DetailedState detailedState = info.getDetailedState();
                     if (state != null) {
@@ -2924,8 +3050,11 @@
                     sb.append(" ").append(mWifiInfo.getBSSID());
                 }
                 if (c != null) {
-                    if (c.scanResultCache != null) {
-                        for (ScanResult r : c.scanResultCache.values()) {
+                    ScanDetailCache scanDetailCache =
+                            mWifiConfigStore.getScanDetailCache(c);
+                    if (scanDetailCache != null) {
+                        for (ScanDetail sd : scanDetailCache.values()) {
+                            ScanResult r = sd.getScanResult();
                             if (r.BSSID.equals(mWifiInfo.getBSSID())) {
                                 sb.append(" ipfail=").append(r.numIpConfigFailures);
                                 sb.append(",st=").append(r.autoJoinStatus);
@@ -2938,7 +3067,6 @@
                     sb.append(",").append(mWifiInfo.txBad);
                     sb.append(",").append(mWifiInfo.txRetries);
                 }
-                sb.append(printTime());
                 sb.append(String.format(" bcn=%d", mRunningBeaconCount));
                 break;
             case CMD_UPDATE_LINKPROPERTIES:
@@ -2967,13 +3095,18 @@
                     }
                 }
                 break;
+            case CMD_IP_REACHABILITY_LOST:
+                if (msg.obj != null) {
+                    sb.append(" ").append((String) msg.obj);
+                }
+                break;
             case CMD_SET_COUNTRY_CODE:
                 sb.append(" ");
                 sb.append(Integer.toString(msg.arg1));
                 sb.append(" ");
                 sb.append(Integer.toString(msg.arg2));
                 if (msg.obj != null) {
-                    sb.append(" ").append((String)msg.obj);
+                    sb.append(" ").append((String) msg.obj);
                 }
                 break;
             case CMD_ROAM_WATCHDOG_TIMER:
@@ -3001,10 +3134,293 @@
         return sb.toString();
     }
 
-    private void handleScreenStateChanged(boolean screenOn, boolean startBackgroundScanIfNeeded) {
+    private void stopPnoOffload() {
+
+        // clear the PNO list
+        if (!WifiNative.setPnoList(null, WifiStateMachine.this)) {
+            Log.e(TAG, "Failed to stop pno");
+        }
+
+    }
+
+
+    private boolean configureSsidWhiteList() {
+
+        mWhiteListedSsids = mWifiConfigStore.getWhiteListedSsids(getCurrentWifiConfiguration());
+        if (mWhiteListedSsids == null || mWhiteListedSsids.length == 0) {
+            return true;
+        }
+
+       if (!WifiNative.setSsidWhitelist(mWhiteListedSsids)) {
+            loge("configureSsidWhiteList couldnt program SSID list, size "
+                    + mWhiteListedSsids.length);
+            return false;
+        }
+
+        logd("configureSsidWhiteList success");
+        return true;
+    }
+
+    // In associated more, lazy roam will be looking for 5GHz roam candidate
+    private boolean configureLazyRoam() {
+        boolean status;
+        if (!useHalBasedAutoJoinOffload()) return false;
+
+        WifiNative.WifiLazyRoamParams params = mWifiNative.new WifiLazyRoamParams();
+        params.A_band_boost_threshold = mWifiConfigStore.bandPreferenceBoostThreshold5.get();
+        params.A_band_penalty_threshold = mWifiConfigStore.bandPreferencePenaltyThreshold5.get();
+        params.A_band_boost_factor = mWifiConfigStore.bandPreferenceBoostFactor5;
+        params.A_band_penalty_factor = mWifiConfigStore.bandPreferencePenaltyFactor5;
+        params.A_band_max_boost = 65;
+        params.lazy_roam_hysteresis = 25;
+        params.alert_roam_rssi_trigger = -75;
+
+        if (DBG) {
+            Log.e(TAG, "configureLazyRoam " + params.toString());
+        }
+
+        if (!WifiNative.setLazyRoam(true, params)) {
+
+            Log.e(TAG, "configureLazyRoam couldnt program params");
+
+            return false;
+        }
+        if (DBG) {
+            Log.e(TAG, "configureLazyRoam success");
+        }
+        return true;
+    }
+
+    // In associated more, lazy roam will be looking for 5GHz roam candidate
+    private boolean stopLazyRoam() {
+        boolean status;
+        if (!useHalBasedAutoJoinOffload()) return false;
+        if (DBG) {
+            Log.e(TAG, "stopLazyRoam");
+        }
+        return WifiNative.setLazyRoam(false, null);
+    }
+
+    private boolean startGScanConnectedModeOffload(String reason) {
+        if (DBG) {
+            if (reason == null) {
+                reason = "";
+            }
+            logd("startGScanConnectedModeOffload " + reason);
+        }
+        stopGScan("startGScanConnectedModeOffload " + reason);
+        if (!mScreenOn) return false;
+
+        if (USE_PAUSE_SCANS) {
+            mWifiNative.pauseScan();
+        }
+        mPnoEnabled = configurePno();
+        if (mPnoEnabled == false) {
+            if (USE_PAUSE_SCANS) {
+                mWifiNative.restartScan();
+            }
+            return false;
+        }
+        mLazyRoamEnabled = configureLazyRoam();
+        if (mLazyRoamEnabled == false) {
+            if (USE_PAUSE_SCANS) {
+                mWifiNative.restartScan();
+            }
+            return false;
+        }
+        if (mWifiConfigStore.getLastSelectedConfiguration() == null) {
+            configureSsidWhiteList();
+        }
+        if (!startConnectedGScan(reason)) {
+            if (USE_PAUSE_SCANS) {
+                mWifiNative.restartScan();
+            }
+            return false;
+        }
+        if (USE_PAUSE_SCANS) {
+            mWifiNative.restartScan();
+        }
+        mConnectedModeGScanOffloadStarted = true;
+        if (DBG) {
+            logd("startGScanConnectedModeOffload success");
+        }
+        return true;
+    }
+
+    private boolean startGScanDisconnectedModeOffload(String reason) {
+        if (DBG) {
+            logd("startGScanDisconnectedModeOffload " + reason);
+        }
+        stopGScan("startGScanDisconnectedModeOffload " + reason);
+        if (USE_PAUSE_SCANS) {
+            mWifiNative.pauseScan();
+        }
+        mPnoEnabled = configurePno();
+        if (mPnoEnabled == false) {
+            if (USE_PAUSE_SCANS) {
+                mWifiNative.restartScan();
+            }
+            return false;
+        }
+        if (!startDisconnectedGScan(reason)) {
+            if (USE_PAUSE_SCANS) {
+                mWifiNative.restartScan();
+            }
+            return false;
+        }
+        if (USE_PAUSE_SCANS) {
+            mWifiNative.restartScan();
+        }
+        return true;
+    }
+
+    private boolean configurePno() {
+        if (!useHalBasedAutoJoinOffload()) return false;
+
+        if (mWifiScanner == null) {
+            log("configurePno: mWifiScanner is null ");
+            return true;
+        }
+
+        List<WifiNative.WifiPnoNetwork> llist
+                = mWifiAutoJoinController.getPnoList(getCurrentWifiConfiguration());
+        if (llist == null || llist.size() == 0) {
+            stopPnoOffload();
+            log("configurePno: empty PNO list ");
+            return true;
+        }
+        if (DBG) {
+            log("configurePno: got llist size " + llist.size());
+        }
+
+        // first program the network we want to look for thru the pno API
+        WifiNative.WifiPnoNetwork list[]
+                = (WifiNative.WifiPnoNetwork[]) llist.toArray(new WifiNative.WifiPnoNetwork[0]);
+
+        if (!WifiNative.setPnoList(list, WifiStateMachine.this)) {
+            Log.e(TAG, "Failed to set pno, length = " + list.length);
+            return false;
+        }
+
+        if (true) {
+            StringBuilder sb = new StringBuilder();
+            for (WifiNative.WifiPnoNetwork network : list) {
+                sb.append("[").append(network.SSID).append(" auth=").append(network.auth);
+                sb.append(" flags=");
+                sb.append(network.flags).append(" rssi").append(network.rssi_threshold);
+                sb.append("] ");
+
+            }
+            sendMessage(CMD_STARTED_PNO_DBG, 1, (int)mGScanPeriodMilli, sb.toString());
+        }
+        return true;
+    }
+
+    final static int DISCONNECTED_SHORT_SCANS_DURATION_MILLI = 2 * 60 * 1000;
+    final static int CONNECTED_SHORT_SCANS_DURATION_MILLI = 2 * 60 * 1000;
+
+    private boolean startConnectedGScan(String reason) {
+        // send a scan background request so as to kick firmware
+        // 5GHz roaming and autojoin
+        // We do this only if screen is on
+        WifiScanner.ScanSettings settings;
+
+        if (mPnoEnabled || mLazyRoamEnabled) {
+            settings = new WifiScanner.ScanSettings();
+            settings.band = WifiScanner.WIFI_BAND_BOTH;
+            long now = System.currentTimeMillis();
+
+            if (!mScreenOn  || (mGScanStartTimeMilli!= 0 && now > mGScanStartTimeMilli
+                    && ((now - mGScanStartTimeMilli) > CONNECTED_SHORT_SCANS_DURATION_MILLI))) {
+                settings.periodInMs = mWifiConfigStore.wifiAssociatedLongScanIntervalMilli.get();
+            } else {
+                mGScanStartTimeMilli = now;
+                settings.periodInMs = mWifiConfigStore.wifiAssociatedShortScanIntervalMilli.get();
+                // if we start offload with short interval, then reconfigure it after a given
+                // duration of time so as to reduce the scan frequency
+                int delay = 30 * 1000 + CONNECTED_SHORT_SCANS_DURATION_MILLI;
+                sendMessageDelayed(CMD_RESTART_AUTOJOIN_OFFLOAD, delay,
+                        mRestartAutoJoinOffloadCounter, " startConnectedGScan " + reason,
+                        (long)delay);
+                mRestartAutoJoinOffloadCounter++;
+            }
+            mGScanPeriodMilli = settings.periodInMs;
+            settings.reportEvents = WifiScanner.REPORT_EVENT_AFTER_BUFFER_FULL;
+            if (DBG) {
+                log("startConnectedScan: settings band="+ settings.band
+                        + " period=" + settings.periodInMs);
+            }
+
+            mWifiScanner.startBackgroundScan(settings, mWifiScanListener);
+            if (true) {
+                sendMessage(CMD_STARTED_GSCAN_DBG, 1, (int)mGScanPeriodMilli, reason);
+            }
+        }
+        return true;
+    }
+
+    private boolean startDisconnectedGScan(String reason) {
+        // send a scan background request so as to kick firmware
+        // PNO
+        // This is done in both screen On and screen Off modes
+        WifiScanner.ScanSettings settings;
+
+        if (mWifiScanner == null) {
+            log("startDisconnectedGScan: no wifi scanner");
+            return false;
+        }
+
+        if (mPnoEnabled || mLazyRoamEnabled) {
+            settings = new WifiScanner.ScanSettings();
+            settings.band = WifiScanner.WIFI_BAND_BOTH;
+            long now = System.currentTimeMillis();
+
+
+            if (!mScreenOn  || (mGScanStartTimeMilli != 0 && now > mGScanStartTimeMilli
+                    && ((now - mGScanStartTimeMilli) > DISCONNECTED_SHORT_SCANS_DURATION_MILLI))) {
+                settings.periodInMs = mWifiConfigStore.wifiDisconnectedLongScanIntervalMilli.get();
+            } else {
+                settings.periodInMs = mWifiConfigStore.wifiDisconnectedShortScanIntervalMilli.get();
+                mGScanStartTimeMilli = now;
+                // if we start offload with short interval, then reconfigure it after a given
+                // duration of time so as to reduce the scan frequency
+                int delay = 30 * 1000 + DISCONNECTED_SHORT_SCANS_DURATION_MILLI;
+                sendMessageDelayed(CMD_RESTART_AUTOJOIN_OFFLOAD, delay,
+                        mRestartAutoJoinOffloadCounter, " startDisconnectedGScan " + reason,
+                        (long)delay);
+                mRestartAutoJoinOffloadCounter++;
+            }
+            mGScanPeriodMilli = settings.periodInMs;
+            settings.reportEvents = WifiScanner.REPORT_EVENT_AFTER_BUFFER_FULL;
+            if (DBG) {
+                log("startDisconnectedScan: settings band="+ settings.band
+                        + " period=" + settings.periodInMs);
+            }
+            mWifiScanner.startBackgroundScan(settings, mWifiScanListener);
+            if (true) {
+                sendMessage(CMD_STARTED_GSCAN_DBG, 1, (int)mGScanPeriodMilli, reason);
+            }
+        }
+        return true;
+    }
+
+    private boolean stopGScan(String reason) {
+        mGScanStartTimeMilli = 0;
+        mGScanPeriodMilli = 0;
+        if (mWifiScanner != null) {
+            mWifiScanner.stopBackgroundScan(mWifiScanListener);
+        }
+        mConnectedModeGScanOffloadStarted = false;
+        if (true) {
+            sendMessage(CMD_STARTED_GSCAN_DBG, 0, 0, reason);
+        }
+        return true;
+    }
+
+    private void handleScreenStateChanged(boolean screenOn) {
         mScreenOn = screenOn;
         if (PDBG) {
-            loge(" handleScreenStateChanged Enter: screenOn=" + screenOn
+            logd(" handleScreenStateChanged Enter: screenOn=" + screenOn
                     + " mUserWantsSuspendOpt=" + mUserWantsSuspendOpt
                     + " state " + getCurrentState().getName()
                     + " suppState:" + mSupplicantStateTracker.getSupplicantStateName());
@@ -3025,41 +3441,61 @@
         getWifiLinkLayerStats(false);
         mOnTimeScreenStateChange = mOnTime;
         lastScreenStateChangeTimeStamp = lastLinkLayerStatsUpdate;
-        mEnableBackgroundScan = false;
+
         cancelDelayedScan();
 
         if (screenOn) {
+            enableBackgroundScan(false);
             setScanAlarm(false);
             clearBlacklist();
 
-            fullBandConnectedTimeIntervalMilli = mWifiConfigStore.associatedPartialScanPeriodMilli;
+            fullBandConnectedTimeIntervalMilli
+                    = mWifiConfigStore.wifiAssociatedShortScanIntervalMilli.get();
             // In either Disconnectedstate or ConnectedState,
             // start the scan alarm so as to enable autojoin
             if (getCurrentState() == mConnectedState
-                    && mWifiConfigStore.enableAutoJoinScanWhenAssociated) {
-                // Scan after 500ms
-                startDelayedScan(500, null, null);
+                    && allowFullBandScanAndAssociated()) {
+                if (useHalBasedAutoJoinOffload()) {
+                    startGScanConnectedModeOffload("screenOnConnected");
+                } else {
+                    // Scan after 500ms
+                    startDelayedScan(500, null, null);
+                }
             } else if (getCurrentState() == mDisconnectedState) {
-                // Scan after 200ms
-                startDelayedScan(200, null, null);
+                if (useHalBasedAutoJoinOffload()) {
+                    startGScanDisconnectedModeOffload("screenOnDisconnected");
+                } else {
+                    // Scan after 500ms
+                    startDelayedScan(500, null, null);
+                }
             }
-        } else if (startBackgroundScanIfNeeded) {
-            // Screen Off and Disconnected and chipset doesn't support scan offload
-            //              => start scan alarm
-            // Screen Off and Disconnected and chipset does support scan offload
-            //              => will use scan offload (i.e. background scan)
-            if (!mBackgroundScanSupported) {
-                setScanAlarm(true);
+        } else {
+            if (getCurrentState() == mDisconnectedState) {
+                // Screen Off and Disconnected and chipset doesn't support scan offload
+                //              => start scan alarm
+                // Screen Off and Disconnected and chipset does support scan offload
+                //              => will use scan offload (i.e. background scan)
+                if (useHalBasedAutoJoinOffload()) {
+                    startGScanDisconnectedModeOffload("screenOffDisconnected");
+                } else {
+                    if (!mBackgroundScanSupported) {
+                        setScanAlarm(true);
+                    } else {
+                        if (!mIsScanOngoing) {
+                            enableBackgroundScan(true);
+                        }
+                    }
+                }
             } else {
-                mEnableBackgroundScan = true;
+                enableBackgroundScan(false);
+                if (useHalBasedAutoJoinOffload()) {
+                    // don't try stop Gscan if it is not enabled
+                    stopGScan("ScreenOffStop(enableBackground=" + mLegacyPnoEnabled + ") ");
+                }
             }
         }
-        if (DBG) logd("backgroundScan enabled=" + mEnableBackgroundScan
-                + " startBackgroundScanIfNeeded:" + startBackgroundScanIfNeeded);
-        if (startBackgroundScanIfNeeded) {
-            // to scan for them in background, we need all networks enabled
-            enableBackgroundScan(mEnableBackgroundScan);
-        }
+        if (DBG) logd("backgroundScan enabled=" + mLegacyPnoEnabled);
+
         if (DBG) log("handleScreenStateChanged Exit: " + screenOn);
     }
 
@@ -3097,7 +3533,7 @@
                         return false;
                     }
 
-                    if(mCm.tether(intf) != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+                    if (mCm.tether(intf) != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
                         loge("Error tethering on " + intf);
                         return false;
                     }
@@ -3168,17 +3604,27 @@
     private void setFrequencyBand() {
         int band = Settings.Global.getInt(mContext.getContentResolver(),
                 Settings.Global.WIFI_FREQUENCY_BAND, WifiManager.WIFI_FREQUENCY_BAND_AUTO);
-        setFrequencyBand(band, false);
+
+        if (mWifiNative.setBand(band)) {
+            mFrequencyBand.set(band);
+            if (PDBG) {
+                logd("done set frequency band " + band);
+            }
+        } else {
+            loge("Failed to set frequency band " + band);
+        }
     }
 
+
+
     private void setSuspendOptimizationsNative(int reason, boolean enabled) {
         if (DBG) {
             log("setSuspendOptimizationsNative: " + reason + " " + enabled
                     + " -want " + mUserWantsSuspendOpt.get()
                     + " stack:" + Thread.currentThread().getStackTrace()[2].getMethodName()
-                    +" - "+ Thread.currentThread().getStackTrace()[3].getMethodName()
-                    +" - "+ Thread.currentThread().getStackTrace()[4].getMethodName()
-                    +" - "+ Thread.currentThread().getStackTrace()[5].getMethodName());
+                    + " - " + Thread.currentThread().getStackTrace()[3].getMethodName()
+                    + " - " + Thread.currentThread().getStackTrace()[4].getMethodName()
+                    + " - " + Thread.currentThread().getStackTrace()[5].getMethodName());
         }
         //mWifiNative.setSuspendOptimizations(enabled);
 
@@ -3189,9 +3635,9 @@
                 if (DBG) {
                     log("setSuspendOptimizationsNative do it " + reason + " " + enabled
                             + " stack:" + Thread.currentThread().getStackTrace()[2].getMethodName()
-                            +" - "+ Thread.currentThread().getStackTrace()[3].getMethodName()
-                            +" - "+ Thread.currentThread().getStackTrace()[4].getMethodName()
-                            +" - "+ Thread.currentThread().getStackTrace()[5].getMethodName());
+                            + " - " + Thread.currentThread().getStackTrace()[3].getMethodName()
+                            + " - " + Thread.currentThread().getStackTrace()[4].getMethodName()
+                            + " - " + Thread.currentThread().getStackTrace()[5].getMethodName());
                 }
                 mWifiNative.setSuspendOptimizations(true);
             }
@@ -3235,7 +3681,7 @@
         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
     }
 
-    private void setWifiApState(int wifiApState) {
+    private void setWifiApState(int wifiApState, int reason) {
         final int previousWifiApState = mWifiApState.get();
 
         try {
@@ -3257,6 +3703,11 @@
         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);
+        if (wifiApState == WifiManager.WIFI_AP_STATE_FAILED) {
+            //only set reason number when softAP start failed
+            intent.putExtra(WifiManager.EXTRA_WIFI_AP_FAILURE_REASON, reason);
+        }
+
         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
     }
 
@@ -3275,6 +3726,7 @@
         }
     }*/
 
+    private static final String IE_STR = "ie=";
     private static final String ID_STR = "id=";
     private static final String BSSID_STR = "bssid=";
     private static final String FREQ_STR = "freq=";
@@ -3292,7 +3744,7 @@
 
     /**
      * Format:
-     *
+     * <p/>
      * id=1
      * bssid=68:7f:76:d7:1a:6e
      * freq=2412
@@ -3331,7 +3783,7 @@
             scanResultsBuf.append("\n");
             String[] lines = tmpResults.split("\n");
             sid = -1;
-            for (int i=lines.length - 1; i >= 0; i--) {
+            for (int i = lines.length - 1; i >= 0; i--) {
                 if (lines[i].startsWith(END_STR)) {
                     break;
                 } else if (lines[i].startsWith(ID_STR)) {
@@ -3356,21 +3808,25 @@
             if (emptyScanResultCount > 10) {
                 // If we got too many empty scan results, the current scan cache is stale,
                 // hence clear it.
-                mScanResults = new ArrayList<ScanResult>();
+                mScanResults = new ArrayList<>();
             }
-           return;
+            return;
         }
 
         emptyScanResultCount = 0;
 
+        mWifiConfigStore.trimANQPCache(false);
+
         // note that all these splits and substrings keep references to the original
         // huge string buffer while the amount we really want is generally pretty small
         // so make copies instead (one example b/11087956 wasted 400k of heap here).
-        synchronized(mScanResultCache) {
-            mScanResults = new ArrayList<ScanResult>();
+        synchronized (mScanResultCache) {
+            mScanResults = new ArrayList<>();
             String[] lines = scanResults.split("\n");
             final int bssidStrLen = BSSID_STR.length();
             final int flagLen = FLAGS_STR.length();
+            String infoElements = null;
+            List<String> anqpLines = null;
 
             for (String line : lines) {
                 if (line.startsWith(BSSID_STR)) {
@@ -3388,7 +3844,7 @@
                          * so we need to adjust for that here.
                          */
                         if (level > 0) level -= 256;
-                    } catch(NumberFormatException e) {
+                    } catch (NumberFormatException e) {
                         level = 0;
                     }
                 } else if (line.startsWith(TSF_STR)) {
@@ -3402,40 +3858,45 @@
                 } else if (line.startsWith(SSID_STR)) {
                     wifiSsid = WifiSsid.createFromAsciiEncoded(
                             line.substring(SSID_STR.length()));
-                } else if (line.startsWith(DELIMITER_STR) || line.startsWith(END_STR)) {
-                    Matcher match = null;
-                    if (bssid!= null) {
-                        match = mNotZero.matcher(bssid);
+                } else if (line.startsWith(IE_STR)) {
+                    infoElements = line;
+                } else if (SupplicantBridge.isAnqpAttribute(line)) {
+                    if (anqpLines == null) {
+                        anqpLines = new ArrayList<>();
                     }
-                    if (match != null && !bssid.isEmpty() && match.find()) {
-                        String ssid = (wifiSsid != null) ? wifiSsid.toString() : WifiSsid.NONE;
-                        String key = bssid + ssid;
-                        ScanResult scanResult = mScanResultCache.get(key);
-                        if (scanResult != null) {
-                            // TODO: average the RSSI, instead of overwriting it
-                            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;
-                            scanResult.seen = System.currentTimeMillis();
-                        } else {
-                            scanResult =
-                                new ScanResult(
-                                        wifiSsid, bssid, flags, level, freq, tsf);
-                            scanResult.seen = System.currentTimeMillis();
-                            mScanResultCache.put(key, scanResult);
-                        }
-                        mNumScanResultsReturned ++; // Keep track of how many scan results we got
-                                                    // as part of this scan's processing
-                        mScanResults.add(scanResult);
-                    } else {
-                        if (bssid != null)  {
-                            loge("setScanResults obtaining null BSSID results <"
-                                + bssid + ">, discard it");
+                    anqpLines.add(line);
+                } else if (line.startsWith(DELIMITER_STR) || line.startsWith(END_STR)) {
+                    if (bssid != null) {
+                        try {
+                            NetworkDetail networkDetail =
+                                    new NetworkDetail(bssid, infoElements, anqpLines, freq);
+
+                            String xssid = (wifiSsid != null) ? wifiSsid.toString() : WifiSsid.NONE;
+                            if (!xssid.equals(networkDetail.getTrimmedSSID())) {
+                                logd(String.format(
+                                        "Inconsistent SSID on BSSID '%s': '%s' vs '%s': %s",
+                                        bssid, xssid, networkDetail.getSSID(), infoElements));
+                            }
+
+                            if (networkDetail.hasInterworking()) {
+                                Log.d(Utils.hs2LogTag(getClass()), "HSNwk: '" + networkDetail);
+                            }
+
+                            ScanDetail scanDetail = mScanResultCache.get(networkDetail);
+                            if (scanDetail != null) {
+                                scanDetail.updateResults(networkDetail, level, wifiSsid, xssid,
+                                        flags, freq, tsf);
+                            } else {
+                                scanDetail = new ScanDetail(networkDetail, wifiSsid, bssid,
+                                        flags, level, freq, tsf);
+                                mScanResultCache.put(networkDetail, scanDetail);
+                            }
+
+                            mNumScanResultsReturned++; // Keep track of how many scan results we got
+                            // as part of this scan's processing
+                            mScanResults.add(scanDetail);
+                        } catch (IllegalArgumentException iae) {
+                            Log.d(TAG, "Failed to parse information elements: " + iae);
                         }
                     }
                     bssid = null;
@@ -3444,10 +3905,15 @@
                     tsf = 0;
                     flags = "";
                     wifiSsid = null;
+                    infoElements = null;
+                    anqpLines = null;
                 }
             }
         }
-        boolean attemptAutoJoin = true;
+
+        /* don't attempt autojoin if last connect attempt was just scheduled */
+        boolean attemptAutoJoin =
+                (System.currentTimeMillis() - lastConnectAttemptTimestamp) > CONNECT_TIMEOUT_MSEC;
         SupplicantState state = mWifiInfo.getSupplicantState();
         String selection = mWifiConfigStore.getLastSelectedConfiguration();
         if (getCurrentState() == mRoamingState
@@ -3455,7 +3921,7 @@
                 || getCurrentState() == mScanModeState
                 || getCurrentState() == mDisconnectingState
                 || (getCurrentState() == mConnectedState
-                && !mWifiConfigStore.enableAutoJoinWhenAssociated)
+                && !getEnableAutoJoinWhenAssociated())
                 || linkDebouncing
                 || state == SupplicantState.ASSOCIATING
                 || state == SupplicantState.AUTHENTICATING
@@ -3463,7 +3929,7 @@
                 || state == SupplicantState.GROUP_HANDSHAKE
                 || (/* keep autojoin enabled if user has manually selected a wifi network,
                         so as to make sure we reliably remain connected to this network */
-                        mConnectionRequests == 0 && selection == null)) {
+                mConnectionRequests == 0 && selection == null)) {
             // Dont attempt auto-joining again while we are already attempting to join
             // and/or obtaining Ip address
             attemptAutoJoin = false;
@@ -3472,11 +3938,13 @@
             if (selection == null) {
                 selection = "<none>";
             }
-            loge("wifi setScanResults state" + getCurrentState()
+            logd("wifi setScanResults state" + getCurrentState()
                     + " sup_state=" + state
                     + " debouncing=" + linkDebouncing
                     + " mConnectionRequests=" + mConnectionRequests
-                    + " selection=" + selection);
+                    + " selection=" + selection
+                    + " mNumScanResultsReturned " + mNumScanResultsReturned
+                     + " mScanResults " + mScanResults.size());
         }
         if (attemptAutoJoin) {
             messageHandlingStatus = MESSAGE_HANDLING_STATUS_PROCESSED;
@@ -3486,8 +3954,8 @@
             mWifiConfigStore.setLastSelectedConfiguration(WifiConfiguration.INVALID_NETWORK_ID);
         }
 
-        if (mWifiConfigStore.enableAutoJoinWhenAssociated) {
-            synchronized(mScanResultCache) {
+        if (attemptAutoJoin) {
+            synchronized (mScanResultCache) {
                 // AutoJoincontroller will directly acces the scan result list and update it with
                 // ScanResult status
                 mNumScanResultsKnown = mWifiAutoJoinController.newSupplicantResults(attemptAutoJoin);
@@ -3532,13 +4000,13 @@
         }
 
         if (PDBG) {
-            loge("fetchRssiLinkSpeedAndFrequencyNative rssi="
+            logd("fetchRssiLinkSpeedAndFrequencyNative rssi="
                     + Integer.toString(newRssi) + " linkspeed="
                     + Integer.toString(newLinkSpeed));
         }
 
         if (newRssi > WifiInfo.INVALID_RSSI && newRssi < WifiInfo.MAX_RSSI) {
-        // screen out invalid values
+            // screen out invalid values
             /* some implementations avoid negative values by adding 256
              * so we need to adjust for that here.
              */
@@ -3579,7 +4047,7 @@
     }
 
     /**
-     *  Determine if we need to switch network:
+     * Determine if we need to switch network:
      * - the delta determine the urgency to switch and/or or the expected evilness of the disruption
      * - match the uregncy of the switch versus the packet usage at the interface
      */
@@ -3590,7 +4058,7 @@
         }
         delta = networkDelta;
         if (mWifiInfo != null) {
-            if (!mWifiConfigStore.enableAutoJoinWhenAssociated
+            if (!getEnableAutoJoinWhenAssociated()
                     && mWifiInfo.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID) {
                 // If AutoJoin while associated is not enabled,
                 // we should never switch network when already associated
@@ -3605,13 +4073,13 @@
                 } else if ((mWifiInfo.txSuccessRate > 5) || (mWifiInfo.rxSuccessRate > 30)) {
                     delta -= 6;
                 }
-                loge("WifiStateMachine shouldSwitchNetwork "
+                logd("shouldSwitchNetwork "
                         + " txSuccessRate=" + String.format("%.2f", mWifiInfo.txSuccessRate)
                         + " rxSuccessRate=" + String.format("%.2f", mWifiInfo.rxSuccessRate)
                         + " delta " + networkDelta + " -> " + delta);
             }
         } else {
-            loge("WifiStateMachine shouldSwitchNetwork "
+            logd("shouldSwitchNetwork "
                     + " delta " + networkDelta + " -> " + delta);
         }
         if (delta > 0) {
@@ -3632,6 +4100,7 @@
 
     // For debug, provide information about the last scoring operation
     String wifiScoringReport = null;
+
     private void calculateWifiScore(WifiLinkLayerStats stats) {
         StringBuilder sb = new StringBuilder();
 
@@ -3667,17 +4136,18 @@
         boolean use24Thresholds = false;
         boolean homeNetworkBoost = false;
         WifiConfiguration currentConfiguration = getCurrentWifiConfiguration();
-        if (currentConfiguration != null
-                && currentConfiguration.scanResultCache != null) {
-            currentConfiguration.setVisibility(12000);
+        ScanDetailCache scanDetailCache =
+                mWifiConfigStore.getScanDetailCache(currentConfiguration);
+        if (currentConfiguration != null && scanDetailCache != null) {
+            currentConfiguration.setVisibility(scanDetailCache.getVisibility(12000));
             if (currentConfiguration.visibility != null) {
                 if (currentConfiguration.visibility.rssi24 != WifiConfiguration.INVALID_RSSI
                         && currentConfiguration.visibility.rssi24
-                        >= (currentConfiguration.visibility.rssi5-2)) {
+                        >= (currentConfiguration.visibility.rssi5 - 2)) {
                     use24Thresholds = true;
                 }
             }
-            if (currentConfiguration.scanResultCache.size() <= 6
+            if (scanDetailCache.size() <= 6
                 && currentConfiguration.allowedKeyManagement.cardinality() == 1
                 && currentConfiguration.allowedKeyManagement.
                     get(WifiConfiguration.KeyMgmt.WPA_PSK) == true) {
@@ -3695,19 +4165,19 @@
 
         boolean is24GHz = use24Thresholds || mWifiInfo.is24GHz();
 
-        boolean isBadRSSI = (is24GHz && rssi < mWifiConfigStore.thresholdBadRssi24)
-                || (!is24GHz && rssi < mWifiConfigStore.thresholdBadRssi5);
-        boolean isLowRSSI = (is24GHz && rssi < mWifiConfigStore.thresholdLowRssi24)
-                || (!is24GHz && mWifiInfo.getRssi() < mWifiConfigStore.thresholdLowRssi5);
-        boolean isHighRSSI = (is24GHz && rssi >= mWifiConfigStore.thresholdGoodRssi24)
-                || (!is24GHz && mWifiInfo.getRssi() >= mWifiConfigStore.thresholdGoodRssi5);
+        boolean isBadRSSI = (is24GHz && rssi < mWifiConfigStore.thresholdBadRssi24.get())
+                || (!is24GHz && rssi < mWifiConfigStore.thresholdBadRssi5.get());
+        boolean isLowRSSI = (is24GHz && rssi < mWifiConfigStore.thresholdLowRssi24.get())
+                || (!is24GHz && mWifiInfo.getRssi() < mWifiConfigStore.thresholdLowRssi5.get());
+        boolean isHighRSSI = (is24GHz && rssi >= mWifiConfigStore.thresholdGoodRssi24.get())
+                || (!is24GHz && mWifiInfo.getRssi() >= mWifiConfigStore.thresholdGoodRssi5.get());
 
         if (isBadRSSI) sb.append(" br");
         if (isLowRSSI) sb.append(" lr");
         if (isHighRSSI) sb.append(" hr");
 
         int penalizedDueToUserTriggeredDisconnect = 0;        // For debug information
-        if (currentConfiguration!= null &&
+        if (currentConfiguration != null &&
                 (mWifiInfo.txSuccessRate > 5 || mWifiInfo.rxSuccessRate > 5)) {
             if (isBadRSSI) {
                 currentConfiguration.numTicksAtBadRSSI++;
@@ -3727,9 +4197,9 @@
                 }
                 if (mWifiConfigStore.enableWifiCellularHandoverUserTriggeredAdjustment &&
                         (currentConfiguration.numUserTriggeredWifiDisableBadRSSI > 0
-                        || currentConfiguration.numUserTriggeredWifiDisableLowRSSI > 0
-                        || currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI > 0)) {
-                    score = score -5;
+                                || currentConfiguration.numUserTriggeredWifiDisableLowRSSI > 0
+                                || currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI > 0)) {
+                    score = score - 5;
                     penalizedDueToUserTriggeredDisconnect = 1;
                     sb.append(" p1");
                 }
@@ -3748,8 +4218,8 @@
                 }
                 if (mWifiConfigStore.enableWifiCellularHandoverUserTriggeredAdjustment &&
                         (currentConfiguration.numUserTriggeredWifiDisableLowRSSI > 0
-                        || currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI > 0)) {
-                    score = score -5;
+                                || currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI > 0)) {
+                    score = score - 5;
                     penalizedDueToUserTriggeredDisconnect = 2;
                     sb.append(" p2");
                 }
@@ -3765,7 +4235,7 @@
                 }
                 if (mWifiConfigStore.enableWifiCellularHandoverUserTriggeredAdjustment &&
                         currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI > 0) {
-                    score = score -5;
+                    score = score - 5;
                     penalizedDueToUserTriggeredDisconnect = 3;
                     sb.append(" p3");
                 }
@@ -3781,15 +4251,15 @@
             else if (isHighRSSI) rssiStatus += " highRSSI ";
             else if (isLowRSSI) rssiStatus += " lowRSSI ";
             if (isBadLinkspeed) rssiStatus += " lowSpeed ";
-            loge("calculateWifiScore freq=" + Integer.toString(mWifiInfo.getFrequency())
-                            + " speed=" + Integer.toString(mWifiInfo.getLinkSpeed())
-                            + " score=" + Integer.toString(mWifiInfo.score)
-                            + rssiStatus
-                            + " -> txbadrate=" + String.format( "%.2f", mWifiInfo.txBadRate )
-                            + " txgoodrate=" + String.format("%.2f", mWifiInfo.txSuccessRate)
-                            + " txretriesrate=" + String.format("%.2f", mWifiInfo.txRetriesRate)
-                            + " rxrate=" + String.format("%.2f", mWifiInfo.rxSuccessRate)
-                            + " userTriggerdPenalty" + penalizedDueToUserTriggeredDisconnect);
+            logd("calculateWifiScore freq=" + Integer.toString(mWifiInfo.getFrequency())
+                    + " speed=" + Integer.toString(mWifiInfo.getLinkSpeed())
+                    + " score=" + Integer.toString(mWifiInfo.score)
+                    + rssiStatus
+                    + " -> txbadrate=" + String.format("%.2f", mWifiInfo.txBadRate)
+                    + " txgoodrate=" + String.format("%.2f", mWifiInfo.txSuccessRate)
+                    + " txretriesrate=" + String.format("%.2f", mWifiInfo.txRetriesRate)
+                    + " rxrate=" + String.format("%.2f", mWifiInfo.rxSuccessRate)
+                    + " userTriggerdPenalty" + penalizedDueToUserTriggeredDisconnect);
         }
 
         if ((mWifiInfo.txBadRate >= 1) && (mWifiInfo.txSuccessRate < 3)
@@ -3798,13 +4268,13 @@
             if (mWifiInfo.linkStuckCount < 5)
                 mWifiInfo.linkStuckCount += 1;
             sb.append(String.format(" ls+=%d", mWifiInfo.linkStuckCount));
-            if (PDBG) loge(" bad link -> stuck count ="
+            if (PDBG) logd(" bad link -> stuck count ="
                     + Integer.toString(mWifiInfo.linkStuckCount));
-        } else if (mWifiInfo.txSuccessRate > 2 || mWifiInfo.txBadRate < 0.1) {
+        } else if (mWifiInfo.txBadRate < 0.3) {
             if (mWifiInfo.linkStuckCount > 0)
                 mWifiInfo.linkStuckCount -= 1;
             sb.append(String.format(" ls-=%d", mWifiInfo.linkStuckCount));
-            if (PDBG) loge(" good link -> stuck count ="
+            if (PDBG) logd(" good link -> stuck count ="
                     + Integer.toString(mWifiInfo.linkStuckCount));
         }
 
@@ -3817,9 +4287,9 @@
         sb.append(String.format(",%d", score));
 
         if (isBadLinkspeed) {
-            score -= 4 ;
+            score -= 4;
             if (PDBG) {
-                loge(" isBadLinkspeed   ---> count=" + mBadLinkspeedcount
+                logd(" isBadLinkspeed   ---> count=" + mBadLinkspeedcount
                         + " score=" + Integer.toString(score));
             }
         } else if ((isGoodLinkspeed) && (mWifiInfo.txSuccessRate > 5)) {
@@ -3841,17 +4311,17 @@
             mWifiInfo.lowRssiCount = 0;
         }
 
-        score -= mWifiInfo.badRssiCount * 2 +  mWifiInfo.lowRssiCount ;
+        score -= mWifiInfo.badRssiCount * 2 + mWifiInfo.lowRssiCount;
         sb.append(String.format(",%d", score));
 
-        if (PDBG) loge(" badRSSI count" + Integer.toString(mWifiInfo.badRssiCount)
-                     + " lowRSSI count" + Integer.toString(mWifiInfo.lowRssiCount)
-                        + " --> score " + Integer.toString(score));
+        if (PDBG) logd(" badRSSI count" + Integer.toString(mWifiInfo.badRssiCount)
+                + " lowRSSI count" + Integer.toString(mWifiInfo.lowRssiCount)
+                + " --> score " + Integer.toString(score));
 
 
         if (isHighRSSI) {
             score += 5;
-            if (PDBG) loge(" isHighRSSI       ---> score=" + Integer.toString(score));
+            if (PDBG) logd(" isHighRSSI       ---> score=" + Integer.toString(score));
         }
         sb.append(String.format(",%d]", score));
 
@@ -3866,7 +4336,7 @@
         //report score
         if (score != mWifiInfo.score) {
             if (DBG) {
-                loge("calculateWifiScore() report new score " + Integer.toString(score));
+                logd("calculateWifiScore() report new score " + Integer.toString(score));
             }
             mWifiInfo.score = score;
             if (mNetworkAgent != null) {
@@ -3927,32 +4397,36 @@
 
     private boolean isProvisioned(LinkProperties lp) {
         return lp.isProvisioned() ||
-               (mWifiConfigStore.isUsingStaticIp(mLastNetworkId) && lp.hasIPv4Address());
+                (mWifiConfigStore.isUsingStaticIp(mLastNetworkId) && lp.hasIPv4Address());
     }
 
     /**
-     * Updates mLinkProperties by merging information from various sources.
-     *
+     * Creates a new LinkProperties object by merging information from various sources.
+     * <p/>
      * This is needed because the information in mLinkProperties comes from multiple sources (DHCP,
      * netlink, static configuration, ...). When one of these sources of information has updated
      * link properties, we can't just assign them to mLinkProperties or we'd lose track of the
      * information that came from other sources. Instead, when one of those sources has new
      * information, we update the object that tracks the information from that source and then
-     * call this method to apply the change to mLinkProperties.
-     *
-     * The information in mLinkProperties is currently obtained as follows:
-     * - Interface name: set in the constructor.
-     * - IPv4 and IPv6 addresses: netlink, passed in by mNetlinkTracker.
-     * - IPv4 routes, DNS servers, and domains: DHCP.
-     * - IPv6 routes and DNS servers: netlink, passed in by mNetlinkTracker.
-     * - HTTP proxy: the wifi config store.
+     * call this method to integrate the change into a new LinkProperties object for subsequent
+     * comparison with mLinkProperties.
+     * <p/>
+     * The information used to build LinkProperties is currently obtained as follows:
+     *     - Interface name: set in the constructor.
+     *     - IPv4 and IPv6 addresses: netlink, passed in by mNetlinkTracker.
+     *     - IPv4 routes, DNS servers, and domains: DHCP.
+     *     - IPv6 routes and DNS servers: netlink, passed in by mNetlinkTracker.
+     *     - HTTP proxy: the wifi config store.
      */
-    private void updateLinkProperties(int reason) {
+    private LinkProperties makeLinkProperties() {
         LinkProperties newLp = new LinkProperties();
 
-        // Interface name and proxy are locally configured.
+        // Interface name, proxy, and TCP buffer sizes are locally configured.
         newLp.setInterfaceName(mInterfaceName);
         newLp.setHttpProxy(mWifiConfigStore.getProxyProperties(mLastNetworkId));
+        if (!TextUtils.isEmpty(mTcpBufferSizes)) {
+            newLp.setTcpBufferSizes(mTcpBufferSizes);
+        }
 
         // IPv4/v6 addresses, IPv6 routes and IPv6 DNS servers come from netlink.
         LinkProperties netlinkLinkProperties = mNetlinkTracker.getLinkProperties();
@@ -3961,7 +4435,11 @@
             newLp.addRoute(route);
         }
         for (InetAddress dns : netlinkLinkProperties.getDnsServers()) {
-            newLp.addDnsServer(dns);
+            // Only add likely reachable DNS servers.
+            // TODO: investigate deleting this.
+            if (newLp.isReachable(dns)) {
+                newLp.addDnsServer(dns);
+            }
         }
 
         // IPv4 routes, DNS servers and domains come from mDhcpResults.
@@ -3973,17 +4451,32 @@
                     newLp.addRoute(route);
                 }
                 for (InetAddress dns : mDhcpResults.dnsServers) {
-                    newLp.addDnsServer(dns);
+                    // Only add likely reachable DNS servers.
+                    // TODO: investigate deleting this.
+                    if (newLp.isReachable(dns)) {
+                        newLp.addDnsServer(dns);
+                    }
                 }
                 newLp.setDomains(mDhcpResults.domains);
             }
         }
 
+        return newLp;
+    }
+
+    private void updateLinkProperties(int reason) {
+        LinkProperties newLp = makeLinkProperties();
+
         final boolean linkChanged = !newLp.equals(mLinkProperties);
         final boolean wasProvisioned = isProvisioned(mLinkProperties);
         final boolean isProvisioned = isProvisioned(newLp);
-        final boolean lostIPv4Provisioning =
-            mLinkProperties.hasIPv4Address() && !newLp.hasIPv4Address();
+        // TODO: Teach LinkProperties how to understand static assignment
+        // and simplify all this provisioning change detection logic by
+        // unifying it under LinkProperties.compareProvisioning().
+        final boolean lostProvisioning =
+                (wasProvisioned && !isProvisioned) ||
+                (mLinkProperties.hasIPv4Address() && !newLp.hasIPv4Address()) ||
+                (mLinkProperties.isIPv6Provisioned() && !newLp.isIPv6Provisioned());
         final DetailedState detailedState = getNetworkDetailedState();
 
         if (linkChanged) {
@@ -3992,12 +4485,18 @@
                         + " old: " + mLinkProperties + " new: " + newLp);
             }
             mLinkProperties = newLp;
-            if (TextUtils.isEmpty(mTcpBufferSizes) == false) {
-                mLinkProperties.setTcpBufferSizes(mTcpBufferSizes);
+            if (mIpReachabilityMonitor != null) {
+                mIpReachabilityMonitor.updateLinkProperties(mLinkProperties);
             }
             if (mNetworkAgent != null) mNetworkAgent.sendLinkProperties(mLinkProperties);
         }
 
+        if (lostProvisioning) {
+            log("Lost IP layer provisioning!" +
+                    " was: " + mLinkProperties +
+                    " now: " + newLp);
+        }
+
         if (DBG) {
             StringBuilder sb = new StringBuilder();
             sb.append("updateLinkProperties nid: " + mLastNetworkId);
@@ -4027,7 +4526,7 @@
                     sb.append(" isprov");
                 }
             }
-            loge(sb.toString());
+            logd(sb.toString());
         }
 
         // If we just configured or lost IP configuration, do the needful.
@@ -4046,7 +4545,7 @@
                     // TODO: disconnect here instead. If our configuration is not usable, there's no
                     // point in staying connected, and if mLinkProperties is out of sync with
                     // reality, that will cause problems in the future.
-                    loge("IPv4 config succeeded, but not provisioned");
+                    logd("IPv4 config succeeded, but not provisioned");
                 }
                 break;
 
@@ -4054,7 +4553,7 @@
                 // DHCP failed. If we're not already provisioned, or we had IPv4 and now lost it,
                 // give up and disconnect.
                 // If we're already provisioned (e.g., IPv6-only network), stay connected.
-                if (!isProvisioned || lostIPv4Provisioning) {
+                if (!isProvisioned || lostProvisioning) {
                     sendMessage(CMD_IP_CONFIGURATION_LOST);
                 } else {
                     // DHCP failed, but we're provisioned (e.g., if we're on an IPv6-only network).
@@ -4085,7 +4584,7 @@
 
             case CMD_UPDATE_LINKPROPERTIES:
                 // IP addresses, DNS servers, etc. changed. Act accordingly.
-                if (wasProvisioned && !isProvisioned) {
+                if (lostProvisioning) {
                     // We no longer have a usable network configuration. Disconnect.
                     sendMessage(CMD_IP_CONFIGURATION_LOST);
                 } else if (!wasProvisioned && isProvisioned) {
@@ -4103,66 +4602,70 @@
     /**
      * Clears all our link properties.
      */
-     private void clearLinkProperties() {
-         // Clear the link properties obtained from DHCP and netlink.
-         synchronized (mDhcpResultsLock) {
-             if (mDhcpResults != null) {
-                 mDhcpResults.clear();
-             }
-         }
-         mNetlinkTracker.clearLinkProperties();
+    private void clearLinkProperties() {
+        // Clear the link properties obtained from DHCP and netlink.
+        synchronized (mDhcpResultsLock) {
+            if (mDhcpResults != null) {
+                mDhcpResults.clear();
+            }
+        }
+        mNetlinkTracker.clearLinkProperties();
+        if (mIpReachabilityMonitor != null) {
+            mIpReachabilityMonitor.clearLinkProperties();
+        }
 
-         // Now clear the merged link properties.
-         mLinkProperties.clear();
-         if (mNetworkAgent != null) mNetworkAgent.sendLinkProperties(mLinkProperties);
-     }
+        // Now clear the merged link properties.
+        mLinkProperties.clear();
+        if (mNetworkAgent != null) mNetworkAgent.sendLinkProperties(mLinkProperties);
+    }
 
-     /**
-      * try to update default route MAC address.
-      */
-      private String updateDefaultRouteMacAddress(int timeout) {
-          String address = null;
-          for (RouteInfo route : mLinkProperties.getRoutes()) {
-              if (route.isDefaultRoute() && route.hasGateway()) {
-                  InetAddress gateway = route.getGateway();
-                  if (gateway instanceof Inet4Address) {
-                      if (PDBG) {
-                          loge("updateDefaultRouteMacAddress found Ipv4 default :"
-                                  + gateway.getHostAddress());
-                      }
-                      address = macAddressFromRoute(gateway.getHostAddress());
-                     /* The gateway's MAC address is known */
-                      if ((address == null) && (timeout > 0)) {
-                          boolean reachable = false;
-                          try {
-                              reachable = gateway.isReachable(timeout);
-                          } catch (Exception e) {
-                              loge("updateDefaultRouteMacAddress exception reaching :"
-                                      + gateway.getHostAddress());
+    /**
+     * try to update default route MAC address.
+     */
+    private String updateDefaultRouteMacAddress(int timeout) {
+        String address = null;
+        for (RouteInfo route : mLinkProperties.getRoutes()) {
+            if (route.isDefaultRoute() && route.hasGateway()) {
+                InetAddress gateway = route.getGateway();
+                if (gateway instanceof Inet4Address) {
+                    if (PDBG) {
+                        logd("updateDefaultRouteMacAddress found Ipv4 default :"
+                                + gateway.getHostAddress());
+                    }
+                    address = macAddressFromRoute(gateway.getHostAddress());
+                    /* The gateway's MAC address is known */
+                    if ((address == null) && (timeout > 0)) {
+                        boolean reachable = false;
+                        try {
+                            reachable = gateway.isReachable(timeout);
+                        } catch (Exception e) {
+                            loge("updateDefaultRouteMacAddress exception reaching :"
+                                    + gateway.getHostAddress());
 
-                          } finally {
-                              if (reachable == true) {
+                        } finally {
+                            if (reachable == true) {
 
-                                  address = macAddressFromRoute(gateway.getHostAddress());
-                                  if (PDBG) {
-                                      loge("updateDefaultRouteMacAddress reachable (tried again) :"
-                                              + gateway.getHostAddress() + " found " + address);
-                                  }
-                              }
-                          }
-                      }
-                      if (address != null) {
-                          mWifiConfigStore.setDefaultGwMacAddress(mLastNetworkId, address);
-                      }
-                  }
-              }
-          }
-          return address;
-      }
+                                address = macAddressFromRoute(gateway.getHostAddress());
+                                if (PDBG) {
+                                    logd("updateDefaultRouteMacAddress reachable (tried again) :"
+                                            + gateway.getHostAddress() + " found " + address);
+                                }
+                            }
+                        }
+                    }
+                    if (address != null) {
+                        mWifiConfigStore.setDefaultGwMacAddress(mLastNetworkId, address);
+                    }
+                }
+            }
+        }
+        return address;
+    }
 
-    private void sendScanResultsAvailableBroadcast() {
+    void sendScanResultsAvailableBroadcast(boolean scanSucceeded) {
         Intent intent = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        intent.putExtra(WifiManager.EXTRA_RESULTS_UPDATED, scanSucceeded);
         mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
     }
 
@@ -4182,16 +4685,43 @@
         Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
         intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, new NetworkInfo(mNetworkInfo));
-        intent.putExtra(WifiManager.EXTRA_LINK_PROPERTIES, new LinkProperties (mLinkProperties));
+        intent.putExtra(WifiManager.EXTRA_LINK_PROPERTIES, new LinkProperties(mLinkProperties));
         if (bssid != null)
             intent.putExtra(WifiManager.EXTRA_BSSID, bssid);
         if (mNetworkInfo.getDetailedState() == DetailedState.VERIFYING_POOR_LINK ||
                 mNetworkInfo.getDetailedState() == DetailedState.CONNECTED) {
-            intent.putExtra(WifiManager.EXTRA_WIFI_INFO, new WifiInfo(mWifiInfo));
+            // We no longer report MAC address to third-parties and our code does
+            // not rely on this broadcast, so just send the default MAC address.
+            WifiInfo sentWifiInfo = new WifiInfo(mWifiInfo);
+            sentWifiInfo.setMacAddress(WifiInfo.DEFAULT_MAC_ADDRESS);
+            intent.putExtra(WifiManager.EXTRA_WIFI_INFO, sentWifiInfo);
         }
         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
     }
 
+    private WifiInfo getWiFiInfoForUid(int uid) {
+        if (Binder.getCallingUid() == Process.myUid()) {
+            return mWifiInfo;
+        }
+
+        WifiInfo result = new WifiInfo(mWifiInfo);
+        result.setMacAddress(WifiInfo.DEFAULT_MAC_ADDRESS);
+
+        IBinder binder = ServiceManager.getService("package");
+        IPackageManager packageManager = IPackageManager.Stub.asInterface(binder);
+
+        try {
+            if (packageManager.checkUidPermission(Manifest.permission.LOCAL_MAC_ADDRESS,
+                    uid) == PackageManager.PERMISSION_GRANTED) {
+                result.setMacAddress(mWifiInfo.getMacAddress());
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error checking receiver permission", e);
+        }
+
+        return result;
+    }
+
     private void sendLinkConfigurationChangedBroadcast() {
         Intent intent = new Intent(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
@@ -4208,6 +4738,7 @@
 
     /**
      * Record the detailed state of a network.
+     *
      * @param state the new {@code DetailedState}
      */
     private boolean setNetworkDetailedState(NetworkInfo.DetailedState state) {
@@ -4237,7 +4768,7 @@
             // Always indicate that SSID has changed
             if (!mNetworkInfo.getExtraInfo().equals(mWifiInfo.getSSID())) {
                 if (DBG) {
-                    log("setDetailed state send new extra info"  + mWifiInfo.getSSID());
+                    log("setDetailed state send new extra info" + mWifiInfo.getSSID());
                 }
                 mNetworkInfo.setExtraInfo(mWifiInfo.getSSID());
                 sendNetworkStateChangeBroadcast(null);
@@ -4262,7 +4793,6 @@
         return mNetworkInfo.getDetailedState();
     }
 
-
     private SupplicantState handleSupplicantStateChange(Message message) {
         StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
         SupplicantState state = stateChangeResult.state;
@@ -4279,7 +4809,36 @@
         }
 
         mWifiInfo.setBSSID(stateChangeResult.BSSID);
+
+        if (mWhiteListedSsids != null
+                && mWhiteListedSsids.length > 0
+                && stateChangeResult.wifiSsid != null) {
+            String SSID = stateChangeResult.wifiSsid.toString();
+            String currentSSID = mWifiInfo.getSSID();
+            if (SSID != null
+                    && currentSSID != null
+                    && !SSID.equals(WifiSsid.NONE)) {
+                    // Remove quote before comparing
+                    if (SSID.length() >= 2 && SSID.charAt(0) == '"'
+                            && SSID.charAt(SSID.length() - 1) == '"')
+                    {
+                        SSID = SSID.substring(1, SSID.length() - 1);
+                    }
+                    if (currentSSID.length() >= 2 && currentSSID.charAt(0) == '"'
+                            && currentSSID.charAt(currentSSID.length() - 1) == '"') {
+                        currentSSID = currentSSID.substring(1, currentSSID.length() - 1);
+                    }
+                    if ((!SSID.equals(currentSSID)) && (getCurrentState() == mConnectedState)) {
+                        lastConnectAttemptTimestamp = System.currentTimeMillis();
+                        targetWificonfiguration
+                            = mWifiConfigStore.getWifiConfiguration(mWifiInfo.getNetworkId());
+                        transitionTo(mRoamingState);
+                    }
+             }
+        }
+
         mWifiInfo.setSSID(stateChangeResult.wifiSsid);
+        mWifiInfo.setEphemeral(mWifiConfigStore.isEphemeral(mWifiInfo.getNetworkId()));
 
         mSupplicantStateTracker.sendMessage(Message.obtain(message));
 
@@ -4293,9 +4852,9 @@
     private void handleNetworkDisconnect() {
         if (DBG) log("handleNetworkDisconnect: Stopping DHCP and clearing IP"
                 + " stack:" + Thread.currentThread().getStackTrace()[2].getMethodName()
-                +" - "+ Thread.currentThread().getStackTrace()[3].getMethodName()
-                +" - "+ Thread.currentThread().getStackTrace()[4].getMethodName()
-                +" - "+ Thread.currentThread().getStackTrace()[5].getMethodName());
+                + " - " + Thread.currentThread().getStackTrace()[3].getMethodName()
+                + " - " + Thread.currentThread().getStackTrace()[4].getMethodName()
+                + " - " + Thread.currentThread().getStackTrace()[5].getMethodName());
 
 
         clearCurrentConfigBSSID("handleNetworkDisconnect");
@@ -4318,7 +4877,7 @@
 
         /**
          *  fullBandConnectedTimeIntervalMilli:
-         *  - start scans at mWifiConfigStore.associatedPartialScanPeriodMilli seconds interval
+         *  - start scans at mWifiConfigStore.wifiAssociatedShortScanIntervalMilli seconds interval
          *  - exponentially increase to mWifiConfigStore.associatedFullScanMaxIntervalMilli
          *  Initialize to sane value = 20 seconds
          */
@@ -4340,16 +4899,18 @@
         /* Cancel auto roam requests */
         autoRoamSetBSSID(mLastNetworkId, "any");
 
-        mLastBssid= null;
+        mLastBssid = null;
         registerDisconnected();
         mLastNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
     }
 
-    private void handleSupplicantConnectionLoss() {
+    private void handleSupplicantConnectionLoss(boolean killSupplicant) {
         /* Socket connection can be lost when we do a graceful shutdown
         * or when the driver is hung. Ensure supplicant is stopped here.
         */
-        mWifiMonitor.killSupplicant(mP2pSupported);
+        if (killSupplicant) {
+            mWifiMonitor.killSupplicant(mP2pSupported);
+        }
         mWifiNative.closeSupplicantConnection();
         sendSupplicantConnectionChangedBroadcast(false);
         setWifiState(WIFI_STATE_DISABLED);
@@ -4386,9 +4947,6 @@
         setSuspendOptimizationsNative(SUSPEND_DUE_TO_DHCP, false);
         mWifiNative.setPowerSave(false);
 
-        stopBatchedScan();
-        WifiNative.pauseScan();
-
         // Update link layer stats
         getWifiLinkLayerStats(false);
 
@@ -4402,22 +4960,32 @@
     }
 
 
-    void startDhcp() {
-        if (mDhcpStateMachine == null) {
-            mDhcpStateMachine = DhcpStateMachine.makeDhcpStateMachine(
-                    mContext, WifiStateMachine.this, mInterfaceName);
+    private boolean useLegacyDhcpClient() {
+        return Settings.Global.getInt(
+                mContext.getContentResolver(),
+                Settings.Global.LEGACY_DHCP_CLIENT, 0) == 1;
+    }
 
+    private void maybeInitDhcpStateMachine() {
+        if (mDhcpStateMachine == null) {
+            if (useLegacyDhcpClient()) {
+                mDhcpStateMachine = DhcpStateMachine.makeDhcpStateMachine(
+                        mContext, WifiStateMachine.this, mInterfaceName);
+            } else {
+                mDhcpStateMachine = DhcpClient.makeDhcpStateMachine(
+                        mContext, WifiStateMachine.this, mInterfaceName);
+            }
         }
+    }
+
+    void startDhcp() {
+        maybeInitDhcpStateMachine();
         mDhcpStateMachine.registerForPreDhcpNotification();
         mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_START_DHCP);
     }
 
     void renewDhcp() {
-        if (mDhcpStateMachine == null) {
-            mDhcpStateMachine = DhcpStateMachine.makeDhcpStateMachine(
-                    mContext, WifiStateMachine.this, mInterfaceName);
-
-        }
+        maybeInitDhcpStateMachine();
         mDhcpStateMachine.registerForPreDhcpNotification();
         mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_RENEW_DHCP);
     }
@@ -4442,36 +5010,34 @@
                 mWifiNative.BLUETOOTH_COEXISTENCE_MODE_SENSE);
 
         mDhcpActive = false;
+    }
 
-        startBatchedScan();
-        WifiNative.restartScan();
+    void connectScanningService() {
+
+        if (mWifiScanner == null) {
+            mWifiScanner = (WifiScanner) mContext.getSystemService(Context.WIFI_SCANNING_SERVICE);
+        }
     }
 
     private void handleIPv4Success(DhcpResults dhcpResults, int reason) {
 
         if (PDBG) {
-            loge("wifistatemachine handleIPv4Success <" + dhcpResults.toString() + ">");
-            loge("link address " + dhcpResults.ipAddress);
+            logd("handleIPv4Success <" + dhcpResults.toString() + ">");
+            logd("link address " + dhcpResults.ipAddress);
         }
 
+        Inet4Address addr;
         synchronized (mDhcpResultsLock) {
             mDhcpResults = dhcpResults;
+            addr = (Inet4Address) dhcpResults.ipAddress.getAddress();
         }
 
-        Inet4Address addr = (Inet4Address) dhcpResults.ipAddress.getAddress();
         if (isRoaming()) {
-            if (addr instanceof Inet4Address) {
-                int previousAddress = mWifiInfo.getIpAddress();
-                int newAddress = NetworkUtils.inetAddressToInt(addr);
-                if (previousAddress != newAddress) {
-                    loge("handleIPv4Success, roaming and address changed" +
-                            mWifiInfo + " got: " + addr);
-                } else {
-
-                }
-            } else {
-                loge("handleIPv4Success, roaming and didnt get an IPv4 address" +
-                        addr.toString());
+            int previousAddress = mWifiInfo.getIpAddress();
+            int newAddress = NetworkUtils.inetAddressToInt(addr);
+            if (previousAddress != newAddress) {
+                logd("handleIPv4Success, roaming and address changed" +
+                        mWifiInfo + " got: " + addr);
             }
         }
         mWifiInfo.setInetAddress(addr);
@@ -4492,7 +5058,7 @@
         if (c != null) {
             ScanResult result = getCurrentScanResult();
             if (result == null) {
-                loge("WifiStateMachine: handleSuccessfulIpConfiguration and no scan results" +
+                logd("WifiStateMachine: handleSuccessfulIpConfiguration and no scan results" +
                         c.configKey());
             } else {
                 // Clear the per BSSID failure count
@@ -4502,7 +5068,7 @@
                 // this will typically happen if the user walks away and come back to his arrea
                 // TODO: implement blacklisting based on a timer, i.e. keep BSSID blacklisted
                 // in supplicant for a couple of hours or a day
-                mWifiNative.clearBlacklist();
+                mWifiConfigStore.clearBssidBlacklist();
             }
         }
     }
@@ -4514,7 +5080,7 @@
              }
         }
         if (PDBG) {
-            loge("wifistatemachine handleIPv4Failure");
+            logd("handleIPv4Failure");
         }
         updateLinkProperties(reason);
     }
@@ -4532,13 +5098,150 @@
         mWifiNative.disconnect();
     }
 
+    // TODO: De-duplicated this and handleIpConfigurationLost().
+    private void handleIpReachabilityLost() {
+        // No need to be told about any additional neighbors that might also
+        // become unreachable--quiet them now while we start disconnecting.
+        if (mIpReachabilityMonitor != null) {
+            mIpReachabilityMonitor.clearLinkProperties();
+        }
+
+        mWifiInfo.setInetAddress(null);
+        mWifiInfo.setMeteredHint(false);
+
+        // TODO: Determine whether to call some form of mWifiConfigStore.handleSSIDStateChange().
+
+        // Disconnect via supplicant, and let autojoin retry connecting to the network.
+        mWifiNative.disconnect();
+    }
+
+    private int convertFrequencyToChannelNumber(int frequency) {
+        if (frequency >= 2412 && frequency <= 2484) {
+            return (frequency -2412) / 5 + 1;
+        } else if (frequency >= 5170  &&  frequency <=5825) {
+            //DFS is included
+            return (frequency -5170) / 5 + 34;
+        } else {
+            return 0;
+        }
+    }
+
+    private int chooseApChannel(int apBand) {
+        int apChannel;
+        int[] channel;
+
+        if (apBand == 0)  {
+            if (mWifiApConfigStore.allowed2GChannel == null ||
+                    mWifiApConfigStore.allowed2GChannel.size() == 0) {
+                //most safe channel to use
+                if(DBG) {
+                    Log.d(TAG, "No specified 2G allowed channel list");
+                }
+                apChannel = 6;
+            } else {
+                int index = mRandom.nextInt(mWifiApConfigStore.allowed2GChannel.size());
+                apChannel = mWifiApConfigStore.allowed2GChannel.get(index).intValue();
+            }
+        } else {
+            //5G without DFS
+            channel = mWifiNative.getChannelsForBand(2);
+            if (channel != null && channel.length > 0) {
+                apChannel = channel[mRandom.nextInt(channel.length)];
+                apChannel = convertFrequencyToChannelNumber(apChannel);
+            } else {
+                Log.e(TAG, "SoftAp do not get available channel list");
+                apChannel = 0;
+            }
+        }
+
+        if(DBG) {
+            Log.d(TAG, "SoftAp set on channel " + apChannel);
+        }
+
+        return apChannel;
+    }
+
+    /* SoftAP configuration */
+    private boolean enableSoftAp() {
+        if (WifiNative.getInterfaces() != 0) {
+            if (!mWifiNative.toggleInterface(0)) {
+                if (DBG) Log.e(TAG, "toggleInterface failed");
+                return false;
+            }
+        } else {
+            if (DBG) Log.d(TAG, "No interfaces to toggle");
+        }
+
+        try {
+            mNwService.wifiFirmwareReload(mInterfaceName, "AP");
+            if (DBG) Log.d(TAG, "Firmware reloaded in AP mode");
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to reload AP firmware " + e);
+        }
+
+        if (WifiNative.startHal() == false) {
+            /* starting HAL is optional */
+            Log.e(TAG, "Failed to start HAL");
+        }
+        return true;
+    }
+
     /* Current design is to not set the config on a running hostapd but instead
      * stop and start tethering when user changes config on a running access point
      *
      * TODO: Add control channel setup through hostapd that allows changing config
      * on a running daemon
      */
-    private void startSoftApWithConfig(final WifiConfiguration config) {
+    private void startSoftApWithConfig(final WifiConfiguration configuration) {
+        // set channel
+        final WifiConfiguration config = new WifiConfiguration(configuration);
+
+        if (DBG) {
+            Log.d(TAG, "SoftAp config channel is: " + config.apChannel);
+        }
+
+        //We need HAL support to set country code and get available channel list, if HAL is
+        //not available, like razor, we regress to original implementaion (2GHz, channel 6)
+        if (mWifiNative.isHalStarted()) {
+            //set country code through HAL Here
+            if (mSetCountryCode != null) {
+                if (!mWifiNative.setCountryCodeHal(mSetCountryCode.toUpperCase(Locale.ROOT))) {
+                    if (config.apBand != 0) {
+                        Log.e(TAG, "Fail to set country code. Can not setup Softap on 5GHz");
+                        //countrycode is mandatory for 5GHz
+                        sendMessage(CMD_START_AP_FAILURE, WifiManager.SAP_START_FAILURE_GENERAL);
+                        return;
+                    }
+                }
+            } else {
+                if (config.apBand != 0) {
+                    //countrycode is mandatory for 5GHz
+                    Log.e(TAG, "Can not setup softAp on 5GHz without country code!");
+                    sendMessage(CMD_START_AP_FAILURE, WifiManager.SAP_START_FAILURE_GENERAL);
+                    return;
+                }
+            }
+
+            if (config.apChannel == 0) {
+                config.apChannel = chooseApChannel(config.apBand);
+                if (config.apChannel == 0) {
+                    if(mWifiNative.isGetChannelsForBandSupported()) {
+                        //fail to get available channel
+                        sendMessage(CMD_START_AP_FAILURE, WifiManager.SAP_START_FAILURE_NO_CHANNEL);
+                        return;
+                    } else {
+                        //for some old device, wifiHal may not be supportedget valid channels are not
+                        //supported
+                        config.apBand = 0;
+                        config.apChannel = 6;
+                    }
+                }
+            }
+        } else {
+            //for some old device, wifiHal may not be supported
+            config.apBand = 0;
+            config.apChannel = 6;
+        }
         // Start hostapd on a separate thread
         new Thread(new Runnable() {
             public void run() {
@@ -4551,7 +5254,7 @@
                         mNwService.startAccessPoint(config, mInterfaceName);
                     } catch (Exception e1) {
                         loge("Exception in softap re-start " + e1);
-                        sendMessage(CMD_START_AP_FAILURE);
+                        sendMessage(CMD_START_AP_FAILURE, WifiManager.SAP_START_FAILURE_GENERAL);
                         return;
                     }
                 }
@@ -4750,33 +5453,10 @@
                     }
                     break;
                 case CMD_BOOT_COMPLETED:
-                    String countryCode = mPersistedCountryCode;
-                    if (TextUtils.isEmpty(countryCode) == false) {
-                        Settings.Global.putString(mContext.getContentResolver(),
-                                Settings.Global.WIFI_COUNTRY_CODE,
-                                countryCode);
-                        // It may be that the state transition that should send this info
-                        // to the driver happened between mPersistedCountryCode getting set
-                        // and now, so simply persisting it here would mean we have sent
-                        // nothing to the driver.  Send the cmd so it might be set now.
-                        int sequenceNum = mCountryCodeSequence.incrementAndGet();
-                        sendMessageAtFrontOfQueue(CMD_SET_COUNTRY_CODE,
-                                sequenceNum, 0, countryCode);
-                    }
                     maybeRegisterNetworkFactory();
                     break;
-                case CMD_SET_BATCHED_SCAN:
-                    recordBatchedScanSettings(message.arg1, message.arg2, (Bundle)message.obj);
-                    break;
-                case CMD_POLL_BATCHED_SCAN:
-                    handleBatchedScanPollRequest();
-                    break;
-                case CMD_START_NEXT_BATCHED_SCAN:
-                    startNextBatchedScan();
-                    break;
                 case CMD_SCREEN_STATE_CHANGED:
-                    handleScreenStateChanged(message.arg1 != 0,
-                            /* startBackgroundScanIfNeeded = */ false);
+                    handleScreenStateChanged(message.arg1 != 0);
                     break;
                     /* Discard */
                 case CMD_START_SCAN:
@@ -4804,6 +5484,7 @@
                 case WifiMonitor.NETWORK_CONNECTION_EVENT:
                 case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
                 case WifiMonitor.SCAN_RESULTS_EVENT:
+                case WifiMonitor.SCAN_FAILED_EVENT:
                 case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
                 case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
                 case WifiMonitor.ASSOCIATION_REJECTION_EVENT:
@@ -4839,6 +5520,10 @@
                 case CMD_DISCONNECTING_WATCHDOG_TIMER:
                 case CMD_ROAM_WATCHDOG_TIMER:
                 case CMD_DISABLE_EPHEMERAL_NETWORK:
+                case CMD_RESTART_AUTOJOIN_OFFLOAD:
+                case CMD_STARTED_PNO_DBG:
+                case CMD_STARTED_GSCAN_DBG:
+                case CMD_UPDATE_ASSOCIATED_SCAN_PERMISSION:
                     messageHandlingStatus = MESSAGE_HANDLING_STATUS_DISCARD;
                     break;
                 case DhcpStateMachine.CMD_ON_QUIT:
@@ -4886,11 +5571,13 @@
                             WifiManager.BUSY);
                     break;
                 case CMD_GET_SUPPORTED_FEATURES:
-                    if (WifiNative.startHal()) {
-                        int featureSet = WifiNative.getSupportedFeatureSet();
-                        replyToMessage(message, message.what, featureSet);
-                    } else {
-                        replyToMessage(message, message.what, 0);
+                    int featureSet = WifiNative.getSupportedFeatureSet();
+                    replyToMessage(message, message.what, featureSet);
+                    break;
+                case CMD_FIRMWARE_ALERT:
+                    if (mWifiLogger != null) {
+                        byte[] buffer = (byte[])message.obj;
+                        mWifiLogger.captureAlertData(message.arg1, buffer);
                     }
                     break;
                 case CMD_GET_LINK_LAYER_STATS:
@@ -4909,13 +5596,23 @@
                 case CMD_UPDATE_LINKPROPERTIES:
                     updateLinkProperties(CMD_UPDATE_LINKPROPERTIES);
                     break;
+                case CMD_GET_MATCHING_CONFIG:
+                    replyToMessage(message, message.what);
+                    break;
                 case CMD_IP_CONFIGURATION_SUCCESSFUL:
                 case CMD_IP_CONFIGURATION_LOST:
+                case CMD_IP_REACHABILITY_LOST:
                     messageHandlingStatus = MESSAGE_HANDLING_STATUS_DISCARD;
                     break;
                 case CMD_GET_CONNECTION_STATISTICS:
                     replyToMessage(message, message.what, mWifiConnectionStatistics);
                     break;
+                case CMD_REMOVE_APP_CONFIGURATIONS:
+                    deferMessage(message);
+                    break;
+                case CMD_REMOVE_USER_CONFIGURATIONS:
+                    deferMessage(message);
+                    break;
                 default:
                     loge("Error! unhandled message" + message);
                     break;
@@ -4927,8 +5624,8 @@
     class InitialState extends State {
         @Override
         public void enter() {
+            WifiNative.stopHal();
             mWifiNative.unloadDriver();
-
             if (mWifiP2pChannel == null) {
                 mWifiP2pChannel = new AsyncChannel();
                 mWifiP2pChannel.connect(mContext, getHandler(),
@@ -4937,11 +5634,16 @@
 
             if (mWifiApConfigChannel == null) {
                 mWifiApConfigChannel = new AsyncChannel();
-                WifiApConfigStore wifiApConfigStore = WifiApConfigStore.makeWifiApConfigStore(
+                mWifiApConfigStore = WifiApConfigStore.makeWifiApConfigStore(
                         mContext, getHandler());
-                wifiApConfigStore.loadApConfiguration();
+                mWifiApConfigStore.loadApConfiguration();
                 mWifiApConfigChannel.connectSync(mContext, getHandler(),
-                        wifiApConfigStore.getMessenger());
+                        mWifiApConfigStore.getMessenger());
+            }
+
+            if (mWifiConfigStore.enableHalBasedPno.get()) {
+                // make sure developer Settings are in sync with the config option
+                mHalBasedPnoEnableInDevSettings = true;
             }
         }
         @Override
@@ -4969,10 +5671,10 @@
                             // Set privacy extensions
                             mNwService.setInterfaceIpv6PrivacyExtensions(mInterfaceName, true);
 
-                           // IPv6 is enabled only as long as access point is connected since:
-                           // - IPv6 addresses and routes stick around after disconnection
-                           // - kernel is unaware when connected and fails to start IPv6 negotiation
-                           // - kernel can start autoconfiguration when 802.1x is not complete
+                            // IPv6 is enabled only as long as access point is connected since:
+                            // - IPv6 addresses and routes stick around after disconnection
+                            // - kernel is unaware when connected and fails to start IPv6 negotiation
+                            // - kernel can start autoconfiguration when 802.1x is not complete
                             mNwService.disableIpv6(mInterfaceName);
                         } catch (RemoteException re) {
                             loge("Unable to change interface settings: " + re);
@@ -4985,7 +5687,13 @@
                         * on a running supplicant properly.
                         */
                         mWifiMonitor.killSupplicant(mP2pSupported);
-                        if(mWifiNative.startSupplicant(mP2pSupported)) {
+
+                        if (WifiNative.startHal() == false) {
+                            /* starting HAL is optional */
+                            loge("Failed to start HAL");
+                        }
+
+                        if (mWifiNative.startSupplicant(mP2pSupported)) {
                             setWifiState(WIFI_STATE_ENABLING);
                             if (DBG) log("Supplicant start successful");
                             mWifiMonitor.startMonitoring();
@@ -4998,12 +5706,19 @@
                     }
                     break;
                 case CMD_START_AP:
-                    if (mWifiNative.loadDriver()) {
-                        setWifiApState(WIFI_AP_STATE_ENABLING);
-                        transitionTo(mSoftApStartingState);
-                    } else {
+                    if (mWifiNative.loadDriver() == false) {
                         loge("Failed to load driver for softap");
+                    } else {
+                        if (enableSoftAp() == true) {
+                            setWifiApState(WIFI_AP_STATE_ENABLING, 0);
+                            transitionTo(mSoftApStartingState);
+                        } else {
+                            setWifiApState(WIFI_AP_STATE_FAILED,
+                                    WifiManager.SAP_START_FAILURE_GENERAL);
+                            transitionTo(mInitialState);
+                        }
                     }
+                    break;
                 default:
                     return NOT_HANDLED;
             }
@@ -5060,13 +5775,12 @@
                     mLastSignalLevel = -1;
 
                     mWifiInfo.setMacAddress(mWifiNative.getMacAddress());
+                    /* set frequency band of operation */
+                    setFrequencyBand();
                     mWifiNative.enableSaveConfig();
                     mWifiConfigStore.loadAndEnableAllNetworks();
-                    if (mWifiConfigStore.enableVerboseLogging > 0) {
-                        enableVerboseLogging(mWifiConfigStore.enableVerboseLogging);
-                    }
-                    if (mWifiConfigStore.associatedPartialScanPeriodMilli < 0) {
-                        mWifiConfigStore.associatedPartialScanPeriodMilli = 0;
+                    if (mWifiConfigStore.enableVerboseLogging.get() > 0) {
+                        enableVerboseLogging(mWifiConfigStore.enableVerboseLogging.get());
                     }
                     initializeWpsDetails();
 
@@ -5125,6 +5839,12 @@
             mWifiNative.setScanInterval((int)mSupplicantScanIntervalMs / 1000);
             mWifiNative.setExternalSim(true);
 
+            /* turn on use of DFS channels */
+            WifiNative.setDfsFlag(true);
+
+            /* set country code */
+            setCountryCode();
+
             setRandomMacOui();
             mWifiNative.enableAutoConnect(false);
         }
@@ -5143,7 +5863,7 @@
                     break;
                 case WifiMonitor.SUP_DISCONNECTION_EVENT:  /* Supplicant connection lost */
                     loge("Connection lost, restart supplicant");
-                    handleSupplicantConnectionLoss();
+                    handleSupplicantConnectionLoss(true);
                     handleNetworkDisconnect();
                     mSupplicantStateTracker.sendMessage(CMD_RESET_SUPPLICANT_STATE);
                     if (mP2pSupported) {
@@ -5154,13 +5874,15 @@
                     sendMessageDelayed(CMD_START_SUPPLICANT, SUPPLICANT_RESTART_INTERVAL_MSECS);
                     break;
                 case WifiMonitor.SCAN_RESULTS_EVENT:
+                case WifiMonitor.SCAN_FAILED_EVENT:
                     maybeRegisterNetworkFactory(); // Make sure our NetworkFactory is registered
                     closeRadioScanStats();
                     noteScanEnd();
                     setScanResults();
                     if (mIsFullScanOngoing || mSendScanResultsBroadcast) {
                         /* Just updated results from full scan, let apps know about this */
-                        sendScanResultsAvailableBroadcast();
+                        boolean scanSucceeded = message.what == WifiMonitor.SCAN_RESULTS_EVENT;
+                        sendScanResultsAvailableBroadcast(scanSucceeded);
                     }
                     mSendScanResultsBroadcast = false;
                     mIsScanOngoing = false;
@@ -5179,7 +5901,7 @@
                 case CMD_START_AP:
                     /* Cannot start soft AP while in client mode */
                     loge("Failed to start soft AP with a running supplicant");
-                    setWifiApState(WIFI_AP_STATE_FAILED);
+                    setWifiApState(WIFI_AP_STATE_FAILED, WifiManager.SAP_START_FAILURE_GENERAL);
                     break;
                 case CMD_SET_OPERATIONAL_MODE:
                     mOperationalMode = message.arg1;
@@ -5200,6 +5922,29 @@
                     }
                     replyToMessage(message, message.what, stats);
                     break;
+                case CMD_SET_COUNTRY_CODE:
+                    String country = (String) message.obj;
+
+                    final boolean persist = (message.arg2 == 1);
+                    final int sequence = message.arg1;
+
+                    if (sequence != mCountryCodeSequence.get()) {
+                        if (DBG) log("set country code ignored due to sequnce num");
+                        break;
+                    }
+                    if (DBG) log("set country code " + country);
+                    country = country.toUpperCase(Locale.ROOT);
+
+                    if (mDriverSetCountryCode == null || !mDriverSetCountryCode.equals(country)) {
+                        if (mWifiNative.setCountryCode(country)) {
+                            mDriverSetCountryCode = country;
+                        } else {
+                            loge("Failed to set country code " + country);
+                        }
+                    }
+
+                    mWifiP2pChannel.sendMessage(WifiP2pServiceImpl.SET_COUNTRY_CODE, country);
+                    break;
                 default:
                     return NOT_HANDLED;
             }
@@ -5227,7 +5972,7 @@
             String p2pSuppState = System.getProperty("init.svc.p2p_supplicant");
             if (p2pSuppState == null) p2pSuppState = "unknown";
 
-            loge("SupplicantStoppingState: stopSupplicant "
+            logd("SupplicantStoppingState: stopSupplicant "
                     + " init.svc.wpa_supplicant=" + suppState
                     + " init.svc.p2p_supplicant=" + p2pSuppState);
             mWifiMonitor.stopSupplicant();
@@ -5248,13 +5993,13 @@
                     break;
                 case WifiMonitor.SUP_DISCONNECTION_EVENT:
                     if (DBG) log("Supplicant connection lost");
-                    handleSupplicantConnectionLoss();
+                    handleSupplicantConnectionLoss(false);
                     transitionTo(mInitialState);
                     break;
                 case CMD_STOP_SUPPLICANT_FAILED:
                     if (message.arg1 == mSupplicantStopFailureToken) {
                         loge("Timed out on a supplicant stop, kill and proceed");
-                        handleSupplicantConnectionLoss();
+                        handleSupplicantConnectionLoss(true);
                         transitionTo(mInitialState);
                     }
                     break;
@@ -5340,6 +6085,7 @@
                     deferMessage(message);
                     break;
                 case WifiMonitor.SCAN_RESULTS_EVENT:
+                case WifiMonitor.SCAN_FAILED_EVENT:
                     // Loose scan results obtained in Driver Starting state, they can only confuse
                     // the state machine
                     break;
@@ -5355,8 +6101,10 @@
         public void enter() {
 
             if (PDBG) {
-                loge("DriverStartedState enter");
+                logd("DriverStartedState enter");
             }
+
+            mWifiLogger.startLogging(mVerboseLoggingLevel > 0);
             mIsRunning = true;
             mInDelayedStop = false;
             mDelayedStopCounter++;
@@ -5367,10 +6115,6 @@
              * driver are changed to reduce interference with bluetooth
              */
             mWifiNative.setBluetoothCoexistenceScanMode(mBluetoothConnectionActive);
-            /* set country code */
-            setCountryCode();
-            /* set frequency band of operation */
-            setFrequencyBand();
             /* initialize network state */
             setNetworkDetailedState(DetailedState.DISCONNECTED);
 
@@ -5386,8 +6130,6 @@
 
             mDhcpActive = false;
 
-            startBatchedScan();
-
             if (mOperationalMode != CONNECT_MODE) {
                 mWifiNative.disconnect();
                 mWifiConfigStore.disableAllNetworks();
@@ -5405,14 +6147,14 @@
                 mWifiNative.status();
                 // Transitioning to Disconnected state will trigger a scan and subsequently AutoJoin
                 transitionTo(mDisconnectedState);
+                transitionTo(mDisconnectedState);
             }
 
             // We may have missed screen update at boot
             if (mScreenBroadcastReceived.get() == false) {
                 PowerManager powerManager = (PowerManager)mContext.getSystemService(
                         Context.POWER_SERVICE);
-                handleScreenStateChanged(powerManager.isScreenOn(),
-                        /* startBackgroundScanIfNeeded = */ false);
+                handleScreenStateChanged(powerManager.isScreenOn());
             } else {
                 // Set the right suspend mode settings
                 mWifiNative.setSuspendOptimizations(mSuspendOptNeedsDisabled == 0
@@ -5435,8 +6177,18 @@
             intent.putExtra(WifiManager.EXTRA_SCAN_AVAILABLE, WIFI_STATE_ENABLED);
             mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
 
+            mHalFeatureSet = WifiNative.getSupportedFeatureSet();
+            if ((mHalFeatureSet & WifiManager.WIFI_FEATURE_HAL_EPNO)
+                    == WifiManager.WIFI_FEATURE_HAL_EPNO) {
+                mHalBasedPnoDriverSupported = true;
+            }
+
+            // Enable link layer stats gathering
+            mWifiNative.setWifiLinkLayerStats("wlan0", 1);
+
             if (PDBG) {
-                loge("Driverstarted State enter done");
+                logd("Driverstarted State enter done, epno=" + mHalBasedPnoDriverSupported
+                     + " feature=" + mHalFeatureSet);
             }
         }
 
@@ -5448,56 +6200,20 @@
                 case CMD_START_SCAN:
                     handleScanRequest(WifiNative.SCAN_WITHOUT_CONNECTION_SETUP, message);
                     break;
-                case CMD_SET_BATCHED_SCAN:
-                    if (recordBatchedScanSettings(message.arg1, message.arg2,
-                            (Bundle)message.obj)) {
-                        if (mBatchedScanSettings != null) {
-                            startBatchedScan();
-                        } else {
-                            stopBatchedScan();
-                        }
-                    }
-                    break;
-                case CMD_SET_COUNTRY_CODE:
-                    String country = (String) message.obj;
-                    final boolean persist = (message.arg2 == 1);
-                    final int sequence = message.arg1;
-                    if (sequence != mCountryCodeSequence.get()) {
-                        if (DBG) log("set country code ignored due to sequnce num");
-                        break;
-                    }
-                    if (DBG) log("set country code " + country);
-                    if (persist) {
-                        mPersistedCountryCode = country;
-                        Settings.Global.putString(mContext.getContentResolver(),
-                                Settings.Global.WIFI_COUNTRY_CODE,
-                                country);
-                    }
-                    country = country.toUpperCase(Locale.ROOT);
-                    if (mLastSetCountryCode == null
-                            || country.equals(mLastSetCountryCode) == false) {
-                        if (mWifiNative.setCountryCode(country)) {
-                            mLastSetCountryCode = country;
-                        } else {
-                            loge("Failed to set country code " + country);
-                        }
-                    }
-                    mWifiP2pChannel.sendMessage(WifiP2pServiceImpl.SET_COUNTRY_CODE, country);
-                    break;
                 case CMD_SET_FREQUENCY_BAND:
                     int band =  message.arg1;
                     if (DBG) log("set frequency band " + band);
                     if (mWifiNative.setBand(band)) {
 
-                        if (PDBG)  loge("did set frequency band " + band);
+                        if (PDBG)  logd("did set frequency band " + band);
 
                         mFrequencyBand.set(band);
                         // Flush old data - like scan results
                         mWifiNative.bssFlush();
                         // Fetch the latest scan results when frequency band is set
-                        startScanNative(WifiNative.SCAN_WITHOUT_CONNECTION_SETUP, null);
+//                        startScanNative(WifiNative.SCAN_WITHOUT_CONNECTION_SETUP, null);
 
-                        if (PDBG)  loge("done set frequency band " + band);
+                        if (PDBG)  logd("done set frequency band " + band);
 
                     } else {
                         loge("Failed to set frequency band " + band);
@@ -5601,6 +6317,9 @@
                         mWifiNative.startTdls(remoteAddress, enable);
                     }
                     break;
+                case WifiMonitor.ANQP_DONE_EVENT:
+                    mWifiConfigStore.notifyANQPDone((Long) message.obj, message.arg1 != 0);
+                    break;
                 default:
                     return NOT_HANDLED;
             }
@@ -5608,11 +6327,12 @@
         }
         @Override
         public void exit() {
+
+            mWifiLogger.stopLogging();
+
             mIsRunning = false;
             updateBatteryWorkSource(null);
-            mScanResults = new ArrayList<ScanResult>();
-
-            stopBatchedScan();
+            mScanResults = new ArrayList<>();
 
             final Intent intent = new Intent(WifiManager.WIFI_SCAN_AVAILABLE);
             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
@@ -5620,8 +6340,6 @@
             mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
             noteScanEnd(); // wrap up any pending request.
             mBufferedScanMsg.clear();
-
-            mLastSetCountryCode = null;
         }
     }
 
@@ -5920,7 +6638,7 @@
                 s = "CMD_GET_CONFIGURED_NETWORKS";
                 break;
             case CMD_GET_SUPPORTED_FEATURES:
-                s = "CMD_GET_ADAPTORS";
+                s = "CMD_GET_SUPPORTED_FEATURES";
                 break;
             case CMD_UNWANTED_NETWORK:
                 s = "CMD_UNWANTED_NETWORK";
@@ -5931,6 +6649,9 @@
             case CMD_GET_LINK_LAYER_STATS:
                 s = "CMD_GET_LINK_LAYER_STATS";
                 break;
+            case CMD_GET_MATCHING_CONFIG:
+                s = "CMD_GET_MATCHING_CONFIG";
+                break;
             case CMD_GET_PRIVILEGED_CONFIGURED_NETWORKS:
                 s = "CMD_GET_PRIVILEGED_CONFIGURED_NETWORKS";
                 break;
@@ -5970,15 +6691,6 @@
             case CMD_NO_NETWORKS_PERIODIC_SCAN:
                 s = "CMD_NO_NETWORKS_PERIODIC_SCAN";
                 break;
-            case CMD_SET_BATCHED_SCAN:
-                s = "CMD_SET_BATCHED_SCAN";
-                break;
-            case CMD_START_NEXT_BATCHED_SCAN:
-                s = "CMD_START_NEXT_BATCHED_SCAN";
-                break;
-            case CMD_POLL_BATCHED_SCAN:
-                s = "CMD_POLL_BATCHED_SCAN";
-                break;
             case CMD_UPDATE_LINKPROPERTIES:
                 s = "CMD_UPDATE_LINKPROPERTIES";
                 break;
@@ -6003,6 +6715,9 @@
             case WifiMonitor.SCAN_RESULTS_EVENT:
                 s = "SCAN_RESULTS_EVENT";
                 break;
+            case WifiMonitor.SCAN_FAILED_EVENT:
+                s = "SCAN_FAILED_EVENT";
+                break;
             case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
                 s = "SUPPLICANT_STATE_CHANGE_EVENT";
                 break;
@@ -6033,6 +6748,18 @@
             case WifiMonitor.ASSOCIATION_REJECTION_EVENT:
                 s = "ASSOCIATION_REJECTION_EVENT";
                 break;
+            case WifiMonitor.ANQP_DONE_EVENT:
+                s = "WifiMonitor.ANQP_DONE_EVENT";
+                break;
+            case WifiMonitor.GAS_QUERY_DONE_EVENT:
+                s = "WifiMonitor.GAS_QUERY_DONE_EVENT";
+                break;
+            case WifiMonitor.HS20_DEAUTH_EVENT:
+                s = "WifiMonitor.HS20_DEAUTH_EVENT";
+                break;
+            case WifiMonitor.GAS_QUERY_START_EVENT:
+                s = "WifiMonitor.GAS_QUERY_START_EVENT";
+                break;
             case CMD_SET_OPERATIONAL_MODE:
                 s = "CMD_SET_OPERATIONAL_MODE";
                 break;
@@ -6099,6 +6826,9 @@
             case CMD_IP_CONFIGURATION_SUCCESSFUL:
                 s = "CMD_IP_CONFIGURATION_SUCCESSFUL";
                 break;
+            case CMD_IP_REACHABILITY_LOST:
+                s = "CMD_IP_REACHABILITY_LOST";
+                break;
             case CMD_STATIC_IP_SUCCESS:
                 s = "CMD_STATIC_IP_SUCCESSFUL";
                 break;
@@ -6117,6 +6847,12 @@
             case CMD_ASSOCIATED_BSSID:
                 s = "CMD_ASSOCIATED_BSSID";
                 break;
+            case CMD_REMOVE_APP_CONFIGURATIONS:
+                s = "CMD_REMOVE_APP_CONFIGURATIONS";
+                break;
+            case CMD_REMOVE_USER_CONFIGURATIONS:
+                s = "CMD_REMOVE_USER_CONFIGURATIONS";
+                break;
             case CMD_ROAM_WATCHDOG_TIMER:
                 s = "CMD_ROAM_WATCHDOG_TIMER";
                 break;
@@ -6126,6 +6862,21 @@
             case CMD_DISCONNECTING_WATCHDOG_TIMER:
                 s = "CMD_DISCONNECTING_WATCHDOG_TIMER";
                 break;
+            case CMD_RESTART_AUTOJOIN_OFFLOAD:
+                s = "CMD_RESTART_AUTOJOIN_OFFLOAD";
+                break;
+            case CMD_STARTED_PNO_DBG:
+                s = "CMD_STARTED_PNO_DBG";
+                break;
+            case CMD_STARTED_GSCAN_DBG:
+                s = "CMD_STARTED_GSCAN_DBG";
+                break;
+            case CMD_PNO_NETWORK_FOUND:
+                s = "CMD_PNO_NETWORK_FOUND";
+                break;
+            case CMD_UPDATE_ASSOCIATED_SCAN_PERMISSION:
+                s = "CMD_UPDATE_ASSOCIATED_SCAN_PERMISSION";
+                break;
             default:
                 s = "what:" + Integer.toString(what);
                 break;
@@ -6176,12 +6927,12 @@
                 && rssi != WifiInfo.INVALID_RSSI
                 && config != null) {
             boolean is24GHz = mWifiInfo.is24GHz();
-            boolean isBadRSSI = (is24GHz && rssi < mWifiConfigStore.thresholdBadRssi24)
-                    || (!is24GHz && rssi < mWifiConfigStore.thresholdBadRssi5);
-            boolean isLowRSSI = (is24GHz && rssi < mWifiConfigStore.thresholdLowRssi24)
-                    || (!is24GHz && mWifiInfo.getRssi() < mWifiConfigStore.thresholdLowRssi5);
-            boolean isHighRSSI = (is24GHz && rssi >= mWifiConfigStore.thresholdGoodRssi24)
-                    || (!is24GHz && mWifiInfo.getRssi() >= mWifiConfigStore.thresholdGoodRssi5);
+            boolean isBadRSSI = (is24GHz && rssi < mWifiConfigStore.thresholdBadRssi24.get())
+                    || (!is24GHz && rssi < mWifiConfigStore.thresholdBadRssi5.get());
+            boolean isLowRSSI = (is24GHz && rssi < mWifiConfigStore.thresholdLowRssi24.get())
+                    || (!is24GHz && mWifiInfo.getRssi() < mWifiConfigStore.thresholdLowRssi5.get());
+            boolean isHighRSSI = (is24GHz && rssi >= mWifiConfigStore.thresholdGoodRssi24.get())
+                    || (!is24GHz && mWifiInfo.getRssi() >= mWifiConfigStore.thresholdGoodRssi5.get());
             if (isBadRSSI) {
                 // Take note that we got disabled while RSSI was Bad
                 config.numUserTriggeredWifiDisableLowRSSI++;
@@ -6211,10 +6962,14 @@
         if (BSSID == null) {
             BSSID = mTargetRoamBSSID;
         }
-        if (config.scanResultCache == null) {
+        ScanDetailCache scanDetailCache =
+                mWifiConfigStore.getScanDetailCache(config);
+
+        if (scanDetailCache == null) {
             return null;
         }
-        return config.scanResultCache.get(BSSID);
+
+        return scanDetailCache.get(BSSID);
     }
 
     String getCurrentBSSID() {
@@ -6225,6 +6980,12 @@
     }
 
     class ConnectModeState extends State {
+
+        @Override
+        public void enter() {
+            connectScanningService();
+        }
+
         @Override
         public boolean processMessage(Message message) {
             WifiConfiguration config;
@@ -6238,6 +6999,7 @@
 
             switch (message.what) {
                 case WifiMonitor.ASSOCIATION_REJECTION_EVENT:
+                    mWifiLogger.captureBugReportData(WifiLogger.REPORT_REASON_ASSOC_FAILURE);
                     didBlackListBSSID = false;
                     bssid = (String) message.obj;
                     if (bssid == null || TextUtils.isEmpty(bssid)) {
@@ -6254,6 +7016,7 @@
                     mSupplicantStateTracker.sendMessage(WifiMonitor.ASSOCIATION_REJECTION_EVENT);
                     break;
                 case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
+                    mWifiLogger.captureBugReportData(WifiLogger.REPORT_REASON_AUTH_FAILURE);
                     mSupplicantStateTracker.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT);
                     break;
                 case WifiMonitor.SSID_TEMP_DISABLED:
@@ -6261,7 +7024,7 @@
                     String substr = (String) message.obj;
                     String en = message.what == WifiMonitor.SSID_TEMP_DISABLED ?
                             "temp-disabled" : "re-enabled";
-                    loge("ConnectModeState SSID state=" + en + " nid="
+                    logd("ConnectModeState SSID state=" + en + " nid="
                             + Integer.toString(message.arg1) + " [" + substr + "]");
                     synchronized(mScanResultCache) {
                         mWifiConfigStore.handleSSIDStateChange(message.arg1, message.what ==
@@ -6293,6 +7056,15 @@
                         handleNetworkDisconnect();
                         transitionTo(mDisconnectedState);
                     }
+
+                    // If we have COMPLETED a connection to a BSSID, start doing
+                    // DNAv4/DNAv6 -style probing for on-link neighbors of
+                    // interest (e.g. routers); harmless if none are configured.
+                    if (state == SupplicantState.COMPLETED) {
+                        if (mIpReachabilityMonitor != null) {
+                            mIpReachabilityMonitor.probeAll();
+                        }
+                    }
                     break;
                 case WifiP2pServiceImpl.DISCONNECT_WIFI_REQUEST:
                     if (message.arg1 == 1) {
@@ -6305,6 +7077,17 @@
                     break;
                 case CMD_ADD_OR_UPDATE_NETWORK:
                     config = (WifiConfiguration) message.obj;
+
+                    if (!recordUidIfAuthorized(config, message.sendingUid,
+                            /* onlyAnnotate */ false)) {
+                        logw("Not authorized to update network "
+                             + " config=" + config.SSID
+                             + " cnid=" + config.networkId
+                             + " uid=" + message.sendingUid);
+                        replyToMessage(message, message.what, FAILURE);
+                        break;
+                    }
+
                     int res = mWifiConfigStore.addOrUpdateNetwork(config, message.sendingUid);
                     if (res < 0) {
                         messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
@@ -6317,9 +7100,11 @@
                                 // Set the last selected configuration so as to allow the system to
                                 // stick the last user choice without persisting the choice
                                 mWifiConfigStore.setLastSelectedConfiguration(res);
+                                mWifiConfigStore.updateLastConnectUid(config, message.sendingUid);
+                                mWifiConfigStore.writeKnownNetworkHistory(false);
 
                                 // Remember time of last connection attempt
-                                lastConnectAttempt = System.currentTimeMillis();
+                                lastConnectAttemptTimestamp = System.currentTimeMillis();
 
                                 mWifiConnectionStatistics.numWifiManagerJoinAttempt++;
 
@@ -6331,6 +7116,16 @@
                     replyToMessage(message, CMD_ADD_OR_UPDATE_NETWORK, res);
                     break;
                 case CMD_REMOVE_NETWORK:
+                    netId = message.arg1;
+                    if (!mWifiConfigStore.canModifyNetwork(message.sendingUid, netId,
+                            /* onlyAnnotate */ false)) {
+                        logw("Not authorized to remove network "
+                             + " cnid=" + netId
+                             + " uid=" + message.sendingUid);
+                        replyToMessage(message, message.what, FAILURE);
+                        break;
+                    }
+
                     ok = mWifiConfigStore.removeNetwork(message.arg1);
                     if (!ok) {
                         messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
@@ -6338,28 +7133,38 @@
                     replyToMessage(message, message.what, ok ? SUCCESS : FAILURE);
                     break;
                 case CMD_ENABLE_NETWORK:
-                    boolean others = message.arg2 == 1;
+                    boolean disableOthers = message.arg2 == 1;
+                    netId = message.arg1;
+                    config = mWifiConfigStore.getWifiConfiguration(netId);
+                    if (config == null) {
+                        loge("No network with id = " + netId);
+                        messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
+                        break;
+                    }
+
                     // Tell autojoin the user did try to select to that network
                     // However, do NOT persist the choice by bumping the priority of the network
-                    if (others) {
+                    if (disableOthers) {
                         mWifiAutoJoinController.
-                                updateConfigurationHistory(message.arg1, true, false);
+                                updateConfigurationHistory(netId, true, false);
                         // Set the last selected configuration so as to allow the system to
                         // stick the last user choice without persisting the choice
-                        mWifiConfigStore.setLastSelectedConfiguration(message.arg1);
+                        mWifiConfigStore.setLastSelectedConfiguration(netId);
 
                         // Remember time of last connection attempt
-                        lastConnectAttempt = System.currentTimeMillis();
+                        lastConnectAttemptTimestamp = System.currentTimeMillis();
 
                         mWifiConnectionStatistics.numWifiManagerJoinAttempt++;
                     }
                     // Cancel auto roam requests
-                    autoRoamSetBSSID(message.arg1, "any");
+                    autoRoamSetBSSID(netId, "any");
 
-                    ok = mWifiConfigStore.enableNetwork(message.arg1, message.arg2 == 1);
+                    int uid = message.sendingUid;
+                    ok = mWifiConfigStore.enableNetwork(netId, disableOthers, uid);
                     if (!ok) {
                         messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
                     }
+
                     replyToMessage(message, message.what, ok ? SUCCESS : FAILURE);
                     break;
                 case CMD_ENABLE_ALL_NETWORKS:
@@ -6389,15 +7194,15 @@
                     }
                     break;
                 case CMD_BLACKLIST_NETWORK:
-                    mWifiNative.addToBlacklist((String) message.obj);
+                    mWifiConfigStore.blackListBssid((String) message.obj);
                     break;
                 case CMD_CLEAR_BLACKLIST:
-                    mWifiNative.clearBlacklist();
+                    mWifiConfigStore.clearBssidBlacklist();
                     break;
                 case CMD_SAVE_CONFIG:
                     ok = mWifiConfigStore.saveConfig();
 
-                    if (DBG) loge("wifistatemachine did save config " + ok);
+                    if (DBG) logd("did save config " + ok);
                     replyToMessage(message, CMD_SAVE_CONFIG, ok ? SUCCESS : FAILURE);
 
                     // Inform the backup manager about a data change
@@ -6416,19 +7221,56 @@
                             mWifiConfigStore.getConfiguredNetworks());
                     break;
                 case WifiMonitor.SUP_REQUEST_IDENTITY:
-                    // Supplicant lacks credentials to connect to that network, hence black list
-                    ssid = (String) message.obj;
+                    int networkId = message.arg2;
+                    boolean identitySent = false;
+                    int eapMethod = WifiEnterpriseConfig.Eap.NONE;
 
-                    if (targetWificonfiguration != null && ssid != null
-                            && targetWificonfiguration.SSID != null
-                            && targetWificonfiguration.SSID.equals("\"" + ssid + "\"")) {
-                        mWifiConfigStore.handleSSIDStateChange(targetWificonfiguration.networkId,
-                                false, "AUTH_FAILED no identity", null);
+                    if (targetWificonfiguration != null
+                            && targetWificonfiguration.enterpriseConfig != null) {
+                        eapMethod = targetWificonfiguration.enterpriseConfig.getEapMethod();
                     }
-                    // Disconnect now, as we don't have any way to fullfill the  supplicant request.
-                    mWifiConfigStore.setLastSelectedConfiguration
-                            (WifiConfiguration.INVALID_NETWORK_ID);
-                    mWifiNative.disconnect();
+
+                    // For SIM & AKA/AKA' EAP method Only, get identity from ICC
+                    if (targetWificonfiguration != null
+                            && targetWificonfiguration.networkId == networkId
+                            && targetWificonfiguration.allowedKeyManagement
+                                    .get(WifiConfiguration.KeyMgmt.IEEE8021X)
+                            &&  (eapMethod == WifiEnterpriseConfig.Eap.SIM
+                            || eapMethod == WifiEnterpriseConfig.Eap.AKA
+                            || eapMethod == WifiEnterpriseConfig.Eap.AKA_PRIME)) {
+                        TelephonyManager tm = (TelephonyManager)
+                                mContext.getSystemService(Context.TELEPHONY_SERVICE);
+                        if (tm != null) {
+                            String imsi = tm.getSubscriberId();
+                            String mccMnc = "";
+
+                            if (tm.getSimState() == TelephonyManager.SIM_STATE_READY)
+                                 mccMnc = tm.getSimOperator();
+
+                            String identity = buildIdentity(eapMethod, imsi, mccMnc);
+
+                            if (!identity.isEmpty()) {
+                                mWifiNative.simIdentityResponse(networkId, identity);
+                                identitySent = true;
+                            }
+                        }
+                    }
+                    if (!identitySent) {
+                        // Supplicant lacks credentials to connect to that network, hence black list
+                        ssid = (String) message.obj;
+                        if (targetWificonfiguration != null && ssid != null
+                                && targetWificonfiguration.SSID != null
+                                && targetWificonfiguration.SSID.equals("\"" + ssid + "\"")) {
+                            mWifiConfigStore.handleSSIDStateChange(
+                                    targetWificonfiguration.networkId, false,
+                                    "AUTH_FAILED no identity", null);
+                        }
+                        // Disconnect now, as we don't have any way to fullfill
+                        // the  supplicant request.
+                        mWifiConfigStore.setLastSelectedConfiguration(
+                                WifiConfiguration.INVALID_NETWORK_ID);
+                        mWifiNative.disconnect();
+                    }
                     break;
                 case WifiMonitor.SUP_REQUEST_SIM_AUTH:
                     logd("Received SUP_REQUEST_SIM_AUTH");
@@ -6436,7 +7278,8 @@
                     if (requestData != null) {
                         if (requestData.protocol == WifiEnterpriseConfig.Eap.SIM) {
                             handleGsmAuthRequest(requestData);
-                        } else if (requestData.protocol == WifiEnterpriseConfig.Eap.AKA) {
+                        } else if (requestData.protocol == WifiEnterpriseConfig.Eap.AKA
+                            || requestData.protocol == WifiEnterpriseConfig.Eap.AKA_PRIME) {
                             handle3GAuthRequest(requestData);
                         }
                     } else {
@@ -6447,7 +7290,11 @@
                     replyToMessage(message, message.what,
                             mWifiConfigStore.getPrivilegedConfiguredNetworks());
                     break;
-                    /* Do a redundant disconnect without transition */
+                case CMD_GET_MATCHING_CONFIG:
+                    replyToMessage(message, message.what,
+                            mWifiConfigStore.getMatchingConfig((ScanResult)message.obj));
+                    break;
+                /* Do a redundant disconnect without transition */
                 case CMD_DISCONNECT:
                     mWifiConfigStore.setLastSelectedConfiguration
                             (WifiConfiguration.INVALID_NETWORK_ID);
@@ -6457,14 +7304,14 @@
                     mWifiAutoJoinController.attemptAutoJoin();
                     break;
                 case CMD_REASSOCIATE:
-                    lastConnectAttempt = System.currentTimeMillis();
+                    lastConnectAttemptTimestamp = System.currentTimeMillis();
                     mWifiNative.reassociate();
                     break;
                 case CMD_RELOAD_TLS_AND_RECONNECT:
                     if (mWifiConfigStore.needsUnlockedKeyStore()) {
                         logd("Reconnecting to give a chance to un-connected TLS networks");
                         mWifiNative.disconnect();
-                        lastConnectAttempt = System.currentTimeMillis();
+                        lastConnectAttemptTimestamp = System.currentTimeMillis();
                         mWifiNative.reconnect();
                     }
                     break;
@@ -6493,7 +7340,7 @@
                     config = (WifiConfiguration) message.obj;
                     netId = message.arg1;
                     int roam = message.arg2;
-                    loge("CMD_AUTO_CONNECT sup state "
+                    logd("CMD_AUTO_CONNECT sup state "
                             + mSupplicantStateTracker.getSupplicantStateName()
                             + " my state " + getCurrentState().getName()
                             + " nid=" + Integer.toString(netId)
@@ -6507,19 +7354,45 @@
                     autoRoamSetBSSID(netId, config.BSSID);
 
                     /* Save the network config */
-                    loge("CMD_AUTO_CONNECT will save config -> " + config.SSID
+                    logd("CMD_AUTO_CONNECT will save config -> " + config.SSID
                             + " nid=" + Integer.toString(netId));
-                    result = mWifiConfigStore.saveNetwork(config, -1);
+                    result = mWifiConfigStore.saveNetwork(config, WifiConfiguration.UNKNOWN_UID);
                     netId = result.getNetworkId();
-                    loge("CMD_AUTO_CONNECT did save config -> "
+                    logd("CMD_AUTO_CONNECT did save config -> "
                             + " nid=" + Integer.toString(netId));
 
+                    // Since we updated the config,read it back from config store:
+                    config = mWifiConfigStore.getWifiConfiguration(netId);
+                    if (config == null) {
+                        loge("CMD_AUTO_CONNECT couldn't update the config, got null config");
+                        break;
+                    }
+                    if (netId != config.networkId) {
+                        loge("CMD_AUTO_CONNECT couldn't update the config, want"
+                                + " nid=" + Integer.toString(netId) + " but got" + config.networkId);
+                        break;
+                    }
+
+                    if (deferForUserInput(message, netId, false)) {
+                        break;
+                    } else if (mWifiConfigStore.getWifiConfiguration(netId).userApproved ==
+                                                                   WifiConfiguration.USER_BANNED) {
+                        replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
+                                WifiManager.NOT_AUTHORIZED);
+                        break;
+                    }
+
                     // Make sure the network is enabled, since supplicant will not reenable it
                     mWifiConfigStore.enableNetworkWithoutBroadcast(netId, false);
 
-                    if (mWifiConfigStore.selectNetwork(netId) &&
-                            mWifiNative.reconnect()) {
-                        lastConnectAttempt = System.currentTimeMillis();
+                    // If we're autojoining a network that the user or an app explicitly selected,
+                    // keep track of the UID that selected it.
+                    int lastConnectUid = mWifiConfigStore.isLastSelectedConfiguration(config) ?
+                            config.lastConnectUid : WifiConfiguration.UNKNOWN_UID;
+
+                    if (mWifiConfigStore.selectNetwork(config, /* updatePriorities = */ false,
+                            lastConnectUid) && mWifiNative.reconnect()) {
+                        lastConnectAttemptTimestamp = System.currentTimeMillis();
                         targetWificonfiguration = mWifiConfigStore.getWifiConfiguration(netId);
                         config = mWifiConfigStore.getWifiConfiguration(netId);
                         if (config != null
@@ -6546,6 +7419,16 @@
                             transitionTo(mDisconnectingState);
                         } else {
                             /* Already in disconnected state, nothing to change */
+                            if (!mScreenOn && mLegacyPnoEnabled && mBackgroundScanSupported) {
+                                int delay = 60 * 1000;
+                                if (VDBG) {
+                                    logd("Starting PNO alarm: " + delay);
+                                }
+                                mAlarmManager.set(AlarmManager.RTC_WAKEUP,
+                                       System.currentTimeMillis() + delay,
+                                       mPnoIntent);
+                            }
+                            mRestartAutoJoinOffloadCounter++;
                         }
                     } else {
                         loge("Failed to connect config: " + config + " netId: " + netId);
@@ -6554,6 +7437,12 @@
                         break;
                     }
                     break;
+                case CMD_REMOVE_APP_CONFIGURATIONS:
+                    mWifiConfigStore.removeNetworksForApp((ApplicationInfo) message.obj);
+                    break;
+                case CMD_REMOVE_USER_CONFIGURATIONS:
+                    mWifiConfigStore.removeNetworksForUser(message.arg1);
+                    break;
                 case WifiManager.CONNECT_NETWORK:
                     /**
                      *  The connect message can contain a network id passed as arg1 on message or
@@ -6568,6 +7457,21 @@
 
                     /* Save the network config */
                     if (config != null) {
+                        // When connecting to an access point, WifiStateMachine wants to update the
+                        // relevant config with administrative data. This update should not be
+                        // considered a 'real' update, therefore lockdown by Device Owner must be
+                        // disregarded.
+                        if (!recordUidIfAuthorized(config, message.sendingUid,
+                                /* onlyAnnotate */ true)) {
+                            logw("Not authorized to update network "
+                                 + " config=" + config.SSID
+                                 + " cnid=" + config.networkId
+                                 + " uid=" + message.sendingUid);
+                            replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
+                                           WifiManager.NOT_AUTHORIZED);
+                            break;
+                        }
+
                         String configKey = config.configKey(true /* allowCached */);
                         WifiConfiguration savedConfig =
                                 mWifiConfigStore.getWifiConfiguration(configKey);
@@ -6576,7 +7480,7 @@
                             // (either AUTO_JOIN_DELETED or ephemeral; see WifiConfigStore#
                             // getConfiguredNetworks). Remove those bits and update the config.
                             config = savedConfig;
-                            loge("CONNECT_NETWORK updating existing config with id=" +
+                            logd("CONNECT_NETWORK updating existing config with id=" +
                                     config.networkId + " configKey=" + configKey);
                             config.ephemeral = false;
                             config.autoJoinStatus = WifiConfiguration.AUTO_JOIN_ENABLED;
@@ -6589,12 +7493,15 @@
                     config = mWifiConfigStore.getWifiConfiguration(netId);
 
                     if (config == null) {
-                        loge("CONNECT_NETWORK id=" + Integer.toString(netId) + " "
+                        logd("CONNECT_NETWORK no config for id=" + Integer.toString(netId) + " "
                                 + mSupplicantStateTracker.getSupplicantStateName() + " my state "
                                 + getCurrentState().getName());
+                        replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
+                                WifiManager.ERROR);
+                        break;
                     } else {
                         String wasSkipped = config.autoJoinBailedDueToLowRssi ? " skipped" : "";
-                        loge("CONNECT_NETWORK id=" + Integer.toString(netId)
+                        logd("CONNECT_NETWORK id=" + Integer.toString(netId)
                                 + " config=" + config.SSID
                                 + " cnid=" + config.networkId
                                 + " supstate=" + mSupplicantStateTracker.getSupplicantStateName()
@@ -6616,10 +7523,21 @@
                         clearConfigBSSID(config, "CONNECT_NETWORK");
                     }
 
+                    if (deferForUserInput(message, netId, true)) {
+                        break;
+                    } else if (mWifiConfigStore.getWifiConfiguration(netId).userApproved ==
+                                                                    WifiConfiguration.USER_BANNED) {
+                        replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
+                                WifiManager.NOT_AUTHORIZED);
+                        break;
+                    }
+
                     mAutoRoaming = WifiAutoJoinController.AUTO_JOIN_IDLE;
 
-                    /* Tell autojoin the user did try to connect to that network */
-                    mWifiAutoJoinController.updateConfigurationHistory(netId, true, true);
+                    /* Tell autojoin the user did try to connect to that network if from settings */
+                    boolean persist =
+                        mWifiConfigStore.checkConfigOverridePermission(message.sendingUid);
+                    mWifiAutoJoinController.updateConfigurationHistory(netId, true, persist);
 
                     mWifiConfigStore.setLastSelectedConfiguration(netId);
 
@@ -6636,9 +7554,9 @@
                     // Make sure the network is enabled, since supplicant will not reenable it
                     mWifiConfigStore.enableNetworkWithoutBroadcast(netId, false);
 
-                    if (mWifiConfigStore.selectNetwork(netId) &&
-                            mWifiNative.reconnect()) {
-                        lastConnectAttempt = System.currentTimeMillis();
+                    if (mWifiConfigStore.selectNetwork(config, /* updatePriorities = */ true,
+                            message.sendingUid) && mWifiNative.reconnect()) {
+                        lastConnectAttemptTimestamp = System.currentTimeMillis();
                         targetWificonfiguration = mWifiConfigStore.getWifiConfiguration(netId);
 
                         /* The state tracker handles enabling networks upon completion/failure */
@@ -6683,13 +7601,26 @@
                     }
                     lastSavedConfigurationAttempt = new WifiConfiguration(config);
                     int nid = config.networkId;
-                    loge("SAVE_NETWORK id=" + Integer.toString(nid)
+                    logd("SAVE_NETWORK id=" + Integer.toString(nid)
                                 + " config=" + config.SSID
                                 + " nid=" + config.networkId
                                 + " supstate=" + mSupplicantStateTracker.getSupplicantStateName()
                                 + " my state " + getCurrentState().getName());
 
-                    result = mWifiConfigStore.saveNetwork(config, -1);
+                    // Only record the uid if this is user initiated
+                    boolean checkUid = (message.what == WifiManager.SAVE_NETWORK);
+                    if (checkUid && !recordUidIfAuthorized(config, message.sendingUid,
+                            /* onlyAnnotate */ false)) {
+                        logw("Not authorized to update network "
+                             + " config=" + config.SSID
+                             + " cnid=" + config.networkId
+                             + " uid=" + message.sendingUid);
+                        replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED,
+                                       WifiManager.NOT_AUTHORIZED);
+                        break;
+                    }
+
+                    result = mWifiConfigStore.saveNetwork(config, WifiConfiguration.UNKNOWN_UID);
                     if (result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID) {
                         if (mWifiInfo.getNetworkId() == result.getNetworkId()) {
                             if (result.hasIpChanged()) {
@@ -6707,9 +7638,11 @@
                             }
                         }
                         replyToMessage(message, WifiManager.SAVE_NETWORK_SUCCEEDED);
+                        broadcastWifiCredentialChanged(WifiManager.WIFI_CREDENTIAL_SAVED, config);
+
                         if (VDBG) {
-                           loge("Success save network nid="
-                                        + Integer.toString(result.getNetworkId()));
+                           logd("Success save network nid="
+                                    + Integer.toString(result.getNetworkId()));
                         }
 
                         synchronized(mScanResultCache) {
@@ -6719,8 +7652,18 @@
                              * and interpret the SAVE_NETWORK as a request to connect
                              */
                             boolean user = message.what == WifiManager.SAVE_NETWORK;
+
+                            // Did this connect come from settings
+                            boolean persistConnect =
+                                mWifiConfigStore.checkConfigOverridePermission(message.sendingUid);
+
+                            if (user) {
+                                mWifiConfigStore.updateLastConnectUid(config, message.sendingUid);
+                                mWifiConfigStore.writeKnownNetworkHistory(false);
+                            }
+
                             mWifiAutoJoinController.updateConfigurationHistory(result.getNetworkId()
-                                    , user, true);
+                                    , user, persistConnect);
                             mWifiAutoJoinController.attemptAutoJoin();
                         }
                     } else {
@@ -6739,8 +7682,23 @@
                     } else {
                         lastForgetConfigurationAttempt = new WifiConfiguration(toRemove);
                     }
+                    // check that the caller owns this network
+                    netId = message.arg1;
+
+                    if (!mWifiConfigStore.canModifyNetwork(message.sendingUid, netId,
+                            /* onlyAnnotate */ false)) {
+                        logw("Not authorized to forget network "
+                             + " cnid=" + netId
+                             + " uid=" + message.sendingUid);
+                        replyToMessage(message, WifiManager.FORGET_NETWORK_FAILED,
+                                WifiManager.NOT_AUTHORIZED);
+                        break;
+                    }
+
                     if (mWifiConfigStore.forgetNetwork(message.arg1)) {
                         replyToMessage(message, WifiManager.FORGET_NETWORK_SUCCEEDED);
+                        broadcastWifiCredentialChanged(WifiManager.WIFI_CREDENTIAL_FORGOT,
+                                (WifiConfiguration) message.obj);
                     } else {
                         loge("Failed to forget network");
                         replyToMessage(message, WifiManager.FORGET_NETWORK_FAILED,
@@ -6800,6 +7758,9 @@
                     handleNetworkDisconnect();
                     transitionTo(mDisconnectedState);
                     break;
+                case CMD_PNO_NETWORK_FOUND:
+                    processPnoNetworkFound((ScanResult[])message.obj);
+                    break;
                 default:
                     return NOT_HANDLED;
             }
@@ -6828,20 +7789,34 @@
             if (this != mNetworkAgent) return;
             if (DBG) log("WifiNetworkAgent -> Wifi unwanted score "
                     + Integer.toString(mWifiInfo.score));
-            unwantedNetwork(network_status_unwanted_disconnect);
+            unwantedNetwork(NETWORK_STATUS_UNWANTED_DISCONNECT);
         }
 
+        @Override
         protected void networkStatus(int status) {
+            if (this != mNetworkAgent) return;
             if (status == NetworkAgent.INVALID_NETWORK) {
                 if (DBG) log("WifiNetworkAgent -> Wifi networkStatus invalid, score="
                         + Integer.toString(mWifiInfo.score));
-                unwantedNetwork(network_status_unwanted_disable_autojoin);
+                unwantedNetwork(NETWORK_STATUS_UNWANTED_VALIDATION_FAILED);
             } else if (status == NetworkAgent.VALID_NETWORK) {
                 if (DBG && mWifiInfo != null) log("WifiNetworkAgent -> Wifi networkStatus valid, score= "
                         + Integer.toString(mWifiInfo.score));
                 doNetworkStatus(status);
             }
         }
+
+        @Override
+        protected void saveAcceptUnvalidated(boolean accept) {
+            if (this != mNetworkAgent) return;
+            WifiStateMachine.this.sendMessage(CMD_ACCEPT_UNVALIDATED, accept ? 1 : 0);
+        }
+
+        @Override
+        protected void preventAutomaticReconnect() {
+            if (this != mNetworkAgent) return;
+            unwantedNetwork(NETWORK_STATUS_UNWANTED_DISABLE_AUTOJOIN);
+        }
     }
 
     void unwantedNetwork(int reason) {
@@ -6852,6 +7827,43 @@
         sendMessage(CMD_NETWORK_STATUS, status);
     }
 
+    // rfc4186 & rfc4187:
+    // create Permanent Identity base on IMSI,
+    // identity = usernam@realm
+    // with username = prefix | IMSI
+    // and realm is derived MMC/MNC tuple according 3GGP spec(TS23.003)
+    private String buildIdentity(int eapMethod, String imsi, String mccMnc) {
+        String mcc;
+        String mnc;
+        String prefix;
+
+        if (imsi == null || imsi.isEmpty())
+            return "";
+
+        if (eapMethod == WifiEnterpriseConfig.Eap.SIM)
+            prefix = "1";
+        else if (eapMethod == WifiEnterpriseConfig.Eap.AKA)
+            prefix = "0";
+        else if (eapMethod == WifiEnterpriseConfig.Eap.AKA_PRIME)
+            prefix = "6";
+        else  // not a valide EapMethod
+            return "";
+
+        /* extract mcc & mnc from mccMnc */
+        if (mccMnc != null && !mccMnc.isEmpty()) {
+            mcc = mccMnc.substring(0, 3);
+            mnc = mccMnc.substring(3);
+            if (mnc.length() == 2)
+                mnc = "0" + mnc;
+        } else {
+            // extract mcc & mnc from IMSI, assume mnc size is 3
+            mcc = imsi.substring(0, 3);
+            mnc = imsi.substring(3, 6);
+        }
+
+        return prefix + imsi + "@wlan.mnc" + mnc + ".mcc" + mcc + ".3gppnetwork.org";
+    }
+
     boolean startScanForConfiguration(WifiConfiguration config, boolean restrictChannelList) {
         if (config == null)
             return false;
@@ -6861,14 +7873,15 @@
         // primary purpose of the partial scans is roaming.
         // Full badn scans with exponential backoff for the purpose or extended roaming and
         // network switching are performed unconditionally.
-        if (config.scanResultCache == null
+        ScanDetailCache scanDetailCache =
+                mWifiConfigStore.getScanDetailCache(config);
+        if (scanDetailCache == null
                 || !config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)
-                || config.scanResultCache.size() > 6) {
+                || scanDetailCache.size() > 6) {
             //return true but to not trigger the scan
             return true;
         }
-        HashSet<Integer> channels
-                = mWifiConfigStore.makeChannelList(config,
+        HashSet<Integer> channels = mWifiConfigStore.makeChannelList(config,
                 ONE_HOUR_MILLI, restrictChannelList);
         if (channels != null && channels.size() != 0) {
             StringBuilder freqs = new StringBuilder();
@@ -6880,7 +7893,7 @@
                 first = false;
             }
             //if (DBG) {
-            loge("WifiStateMachine starting scan for " + config.configKey() + " with " + freqs);
+            logd("starting scan for " + config.configKey() + " with " + freqs);
             //}
             // Call wifi native to start the scan
             if (startScanNative(
@@ -6895,7 +7908,7 @@
             }
             return true;
         } else {
-            if (DBG) loge("WifiStateMachine no channels for " + config.configKey());
+            if (DBG) logd("no channels for " + config.configKey());
             return false;
         }
     }
@@ -6911,13 +7924,13 @@
         if (config == null)
             return;
         if (DBG) {
-            loge(dbg + " " + mTargetRoamBSSID + " config " + config.configKey()
+            logd(dbg + " " + mTargetRoamBSSID + " config " + config.configKey()
                     + " config.bssid " + config.BSSID);
         }
         config.autoJoinBSSID = "any";
         config.BSSID = "any";
         if (DBG) {
-           loge(dbg + " " + config.SSID
+           logd(dbg + " " + config.SSID
                     + " nid=" + Integer.toString(config.networkId));
         }
         mWifiConfigStore.saveWifiConfigBSSID(config);
@@ -6936,7 +7949,7 @@
             }
             setNetworkDetailedState(DetailedState.CONNECTING);
 
-            if (TextUtils.isEmpty(mTcpBufferSizes) == false) {
+            if (!TextUtils.isEmpty(mTcpBufferSizes)) {
                 mLinkProperties.setTcpBufferSizes(mTcpBufferSizes);
             }
             mNetworkAgent = new WifiNetworkAgent(getHandler().getLooper(), mContext,
@@ -6947,10 +7960,28 @@
             // from this point on and having the BSSID specified in the network block would
             // cause the roam to faile and the device to disconnect
             clearCurrentConfigBSSID("L2ConnectedState");
+
+            try {
+                mIpReachabilityMonitor = new IpReachabilityMonitor(
+                        mInterfaceName,
+                        new IpReachabilityMonitor.Callback() {
+                            @Override
+                            public void notifyLost(InetAddress ip, String logMsg) {
+                                sendMessage(CMD_IP_REACHABILITY_LOST, logMsg);
+                            }
+                        });
+            } catch (IllegalArgumentException e) {
+                Log.wtf("Failed to create IpReachabilityMonitor", e);
+            }
         }
 
         @Override
         public void exit() {
+            if (mIpReachabilityMonitor != null) {
+                mIpReachabilityMonitor.stop();
+                mIpReachabilityMonitor = null;
+            }
+
             // This is handled by receiving a NETWORK_DISCONNECTION_EVENT in ConnectModeState
             // Bug: 15347363
             // For paranoia's sake, call handleNetworkDisconnect
@@ -6979,18 +8010,19 @@
               case DhcpStateMachine.CMD_POST_DHCP_ACTION:
                   handlePostDhcpSetup();
                   if (message.arg1 == DhcpStateMachine.DHCP_SUCCESS) {
-                      if (DBG) log("WifiStateMachine DHCP successful");
+                      if (DBG) log("DHCP successful");
                       handleIPv4Success((DhcpResults) message.obj, DhcpStateMachine.DHCP_SUCCESS);
-                      // We advance to mVerifyingLinkState because handleIPv4Success will call
+                      // We advance to mConnectedState because handleIPv4Success will call
                       // updateLinkProperties, which then sends CMD_IP_CONFIGURATION_SUCCESSFUL.
                   } else if (message.arg1 == DhcpStateMachine.DHCP_FAILURE) {
+                      mWifiLogger.captureBugReportData(WifiLogger.REPORT_REASON_DHCP_FAILURE);
                       if (DBG) {
                           int count = -1;
                           WifiConfiguration config = getCurrentWifiConfiguration();
                           if (config != null) {
                               count = config.numConnectionFailures;
                           }
-                          log("WifiStateMachine DHCP failure count=" + count);
+                          log("DHCP failure count=" + count);
                       }
                       handleIPv4Failure(DhcpStateMachine.DHCP_FAILURE);
                       // As above, we transition to mDisconnectingState via updateLinkProperties.
@@ -7002,11 +8034,16 @@
                     transitionTo(mConnectedState);
                     break;
                 case CMD_IP_CONFIGURATION_LOST:
-                    // Get Link layer stats so as we get fresh tx packet counters
+                    // Get Link layer stats so that we get fresh tx packet counters.
                     getWifiLinkLayerStats(true);
                     handleIpConfigurationLost();
                     transitionTo(mDisconnectingState);
                     break;
+                case CMD_IP_REACHABILITY_LOST:
+                    if (DBG && message.obj != null) log((String) message.obj);
+                    handleIpReachabilityLost();
+                    transitionTo(mDisconnectingState);
+                    break;
                 case CMD_DISCONNECT:
                     mWifiNative.disconnect();
                     transitionTo(mDisconnectingState);
@@ -7034,30 +8071,34 @@
                     deferMessage(message);
                     break;
                 case CMD_START_SCAN:
-                    //if (DBG) {
-                        loge("WifiStateMachine CMD_START_SCAN source " + message.arg1
+                    if (DBG) {
+                        logd("CMD_START_SCAN source " + message.arg1
                               + " txSuccessRate="+String.format( "%.2f", mWifiInfo.txSuccessRate)
                               + " rxSuccessRate="+String.format( "%.2f", mWifiInfo.rxSuccessRate)
                               + " targetRoamBSSID=" + mTargetRoamBSSID
                               + " RSSI=" + mWifiInfo.getRssi());
-                    //}
+                    }
                     if (message.arg1 == SCAN_ALARM_SOURCE) {
                         // Check if the CMD_START_SCAN message is obsolete (and thus if it should
-                        // not be processed) and restart the scan if needed
-                        boolean shouldScan =
-                                mScreenOn && mWifiConfigStore.enableAutoJoinScanWhenAssociated;
+                        // not be processed) and restart the scan if neede
+                        if (!getEnableAutoJoinWhenAssociated()) {
+                            return HANDLED;
+                        }
+                        boolean shouldScan = mScreenOn;
+
                         if (!checkAndRestartDelayedScan(message.arg2,
                                 shouldScan,
-                                mWifiConfigStore.associatedPartialScanPeriodMilli, null, null)) {
+                                mWifiConfigStore.wifiAssociatedShortScanIntervalMilli.get(),
+                                null, null)) {
                             messageHandlingStatus = MESSAGE_HANDLING_STATUS_OBSOLETE;
-                            loge("WifiStateMachine L2Connected CMD_START_SCAN source "
+                            logd("L2Connected CMD_START_SCAN source "
                                     + message.arg1
                                     + " " + message.arg2 + ", " + mDelayedScanCounter
                                     + " -> obsolete");
                             return HANDLED;
                         }
                         if (mP2pConnected.get()) {
-                            loge("WifiStateMachine L2Connected CMD_START_SCAN source "
+                            logd("L2Connected CMD_START_SCAN source "
                                     + message.arg1
                                     + " " + message.arg2 + ", " + mDelayedScanCounter
                                     + " ignore because P2P is connected");
@@ -7068,17 +8109,17 @@
                         boolean restrictChannelList = false;
                         long now_ms = System.currentTimeMillis();
                         if (DBG) {
-                            loge("WifiStateMachine CMD_START_SCAN with age="
+                            logd("CMD_START_SCAN with age="
                                     + Long.toString(now_ms - lastFullBandConnectedTimeMilli)
                                     + " interval=" + fullBandConnectedTimeIntervalMilli
                                     + " maxinterval=" + maxFullBandConnectedTimeIntervalMilli);
                         }
                         if (mWifiInfo != null) {
-                            if (mWifiConfigStore.enableFullBandScanWhenAssociated &&
+                            if (mWifiConfigStore.enableFullBandScanWhenAssociated.get() &&
                                     (now_ms - lastFullBandConnectedTimeMilli)
                                     > fullBandConnectedTimeIntervalMilli) {
                                 if (DBG) {
-                                    loge("WifiStateMachine CMD_START_SCAN try full band scan age="
+                                    logd("CMD_START_SCAN try full band scan age="
                                          + Long.toString(now_ms - lastFullBandConnectedTimeMilli)
                                          + " interval=" + fullBandConnectedTimeIntervalMilli
                                          + " maxinterval=" + maxFullBandConnectedTimeIntervalMilli);
@@ -7092,7 +8133,7 @@
                                     mWifiConfigStore.maxRxPacketForFullScans) {
                                 // Too much traffic at the interface, hence no full band scan
                                 if (DBG) {
-                                    loge("WifiStateMachine CMD_START_SCAN " +
+                                    logd("CMD_START_SCAN " +
                                             "prevent full band scan due to pkt rate");
                                 }
                                 tryFullBandScan = false;
@@ -7104,9 +8145,9 @@
                                     mWifiConfigStore.maxRxPacketForPartialScans) {
                                 // Don't scan if lots of packets are being sent
                                 restrictChannelList = true;
-                                if (mWifiConfigStore.alwaysEnableScansWhileAssociated == 0) {
+                                if (mWifiConfigStore.alwaysEnableScansWhileAssociated.get() == 0) {
                                     if (DBG) {
-                                     loge("WifiStateMachine CMD_START_SCAN source " + message.arg1
+                                     logd("CMD_START_SCAN source " + message.arg1
                                         + " ...and ignore scans"
                                         + " tx=" + String.format("%.2f", mWifiInfo.txSuccessRate)
                                         + " rx=" + String.format("%.2f", mWifiInfo.rxSuccessRate));
@@ -7119,15 +8160,15 @@
 
                         WifiConfiguration currentConfiguration = getCurrentWifiConfiguration();
                         if (DBG) {
-                            loge("WifiStateMachine CMD_START_SCAN full=" +
+                            logd("CMD_START_SCAN full=" +
                                     tryFullBandScan);
                         }
                         if (currentConfiguration != null) {
                             if (fullBandConnectedTimeIntervalMilli
-                                    < mWifiConfigStore.associatedPartialScanPeriodMilli) {
+                                    < mWifiConfigStore.wifiAssociatedShortScanIntervalMilli.get()) {
                                 // Sanity
                                 fullBandConnectedTimeIntervalMilli
-                                        = mWifiConfigStore.associatedPartialScanPeriodMilli;
+                                        = mWifiConfigStore.wifiAssociatedShortScanIntervalMilli.get();
                             }
                             if (tryFullBandScan) {
                                 lastFullBandConnectedTimeMilli = now_ms;
@@ -7136,10 +8177,10 @@
                                     // Increase the interval
                                     fullBandConnectedTimeIntervalMilli
                                             = fullBandConnectedTimeIntervalMilli
-                                            * mWifiConfigStore.associatedFullScanBackoff / 8;
+                                            * mWifiConfigStore.associatedFullScanBackoff.get() / 8;
 
                                     if (DBG) {
-                                        loge("WifiStateMachine CMD_START_SCAN bump interval ="
+                                        logd("CMD_START_SCAN bump interval ="
                                         + fullBandConnectedTimeIntervalMilli);
                                     }
                                 }
@@ -7149,7 +8190,7 @@
                                 if (!startScanForConfiguration(
                                         currentConfiguration, restrictChannelList)) {
                                     if (DBG) {
-                                        loge("WifiStateMachine starting scan, " +
+                                        logd("starting scan, " +
                                                 " did not find channels -> full");
                                     }
                                     lastFullBandConnectedTimeMilli = now_ms;
@@ -7158,10 +8199,10 @@
                                         // Increase the interval
                                         fullBandConnectedTimeIntervalMilli
                                                 = fullBandConnectedTimeIntervalMilli
-                                                * mWifiConfigStore.associatedFullScanBackoff / 8;
+                                                * mWifiConfigStore.associatedFullScanBackoff.get() / 8;
 
                                         if (DBG) {
-                                            loge("WifiStateMachine CMD_START_SCAN bump interval ="
+                                            logd("CMD_START_SCAN bump interval ="
                                                     + fullBandConnectedTimeIntervalMilli);
                                         }
                                     }
@@ -7171,7 +8212,7 @@
                             }
 
                         } else {
-                            loge("CMD_START_SCAN : connected mode and no configuration");
+                            logd("CMD_START_SCAN : connected mode and no configuration");
                             messageHandlingStatus = MESSAGE_HANDLING_STATUS_HANDLING_ERROR;
                         }
                     } else {
@@ -7191,7 +8232,7 @@
                     break;
                 case CMD_RSSI_POLL:
                     if (message.arg1 == mRssiPollToken) {
-                        if (mWifiConfigStore.enableChipWakeUpWhenAssociated) {
+                        if (mWifiConfigStore.enableChipWakeUpWhenAssociated.get()) {
                             if (VVDBG) log(" get link layer stats " + mWifiLinkLayerStatsSupported);
                             WifiLinkLayerStats stats = getWifiLinkLayerStats(VDBG);
                             if (stats != null) {
@@ -7215,7 +8256,8 @@
                     }
                     break;
                 case CMD_ENABLE_RSSI_POLL:
-                    if (mWifiConfigStore.enableRssiPollWhenAssociated) {
+                    cleanWifiScore();
+                    if (mWifiConfigStore.enableRssiPollWhenAssociated.get()) {
                         mEnableRssiPolling = (message.arg1 == 1);
                     } else {
                         mEnableRssiPolling = false;
@@ -7226,8 +8268,6 @@
                         fetchRssiLinkSpeedAndFrequencyNative();
                         sendMessageDelayed(obtainMessage(CMD_RSSI_POLL,
                                 mRssiPollToken, 0), POLL_RSSI_INTERVAL_MSECS);
-                    } else {
-                        cleanWifiScore();
                     }
                     break;
                 case WifiManager.RSSI_PKTCNT_FETCH:
@@ -7241,11 +8281,11 @@
                     if (!linkDebouncing && mWifiConfigStore.enableLinkDebouncing) {
 
                         // Ignore if we are not debouncing
-                        loge("CMD_DELAYED_NETWORK_DISCONNECT and not debouncing - ignore "
+                        logd("CMD_DELAYED_NETWORK_DISCONNECT and not debouncing - ignore "
                                 + message.arg1);
                         return HANDLED;
                     } else {
-                        loge("CMD_DELAYED_NETWORK_DISCONNECT and debouncing - disconnect "
+                        logd("CMD_DELAYED_NETWORK_DISCONNECT and debouncing - disconnect "
                                 + message.arg1);
 
                         linkDebouncing = false;
@@ -7259,11 +8299,16 @@
                     break;
                 case CMD_ASSOCIATED_BSSID:
                     if ((String) message.obj == null) {
-                        loge("Associated command w/o BSSID");
+                        logw("Associated command w/o BSSID");
                         break;
                     }
                     mLastBssid = (String) message.obj;
-                    mWifiInfo.setBSSID((String) message.obj);
+                    if (mLastBssid != null
+                            && (mWifiInfo.getBSSID() == null
+                            || !mLastBssid.equals(mWifiInfo.getBSSID()))) {
+                        mWifiInfo.setBSSID((String) message.obj);
+                        sendNetworkStateChangeBroadcast(mLastBssid);
+                    }
                     break;
                 default:
                     return NOT_HANDLED;
@@ -7319,7 +8364,7 @@
                     startDhcp();
                 }
                 obtainingIpWatchdogCount++;
-                loge("Start Dhcp Watchdog " + obtainingIpWatchdogCount);
+                logd("Start Dhcp Watchdog " + obtainingIpWatchdogCount);
                 // Get Link layer stats so as we get fresh tx packet counters
                 getWifiLinkLayerStats(true);
                 sendMessageDelayed(obtainMessage(CMD_OBTAINING_IP_ADDRESS_WATCHDOG_TIMER,
@@ -7330,7 +8375,7 @@
                 StaticIpConfiguration config = mWifiConfigStore.getStaticIpConfiguration(
                         mLastNetworkId);
                 if (config.ipAddress == null) {
-                    loge("Static IP lacks address");
+                    logd("Static IP lacks address");
                     sendMessage(CMD_STATIC_IP_FAILURE);
                 } else {
                     InterfaceConfiguration ifcg = new InterfaceConfiguration();
@@ -7383,7 +8428,7 @@
                   break;
               case CMD_OBTAINING_IP_ADDRESS_WATCHDOG_TIMER:
                   if (message.arg1 == obtainingIpWatchdogCount) {
-                      loge("ObtainingIpAddress: Watchdog Triggered, count="
+                      logd("ObtainingIpAddress: Watchdog Triggered, count="
                               + obtainingIpWatchdogCount);
                       handleIpConfigurationLost();
                       transitionTo(mDisconnectingState);
@@ -7398,6 +8443,8 @@
       }
     }
 
+    // Note: currently, this state is never used, because WifiWatchdogStateMachine unconditionally
+    // sets mPoorNetworkDetectionEnabled to false.
     class VerifyingLinkState extends State {
         @Override
         public void enter() {
@@ -7418,8 +8465,6 @@
                     log(getName() + " POOR_LINK_DETECTED: no transition");
                     break;
                 case WifiWatchdogStateMachine.GOOD_LINK_DETECTED:
-                    log(getName() + " GOOD_LINK_DETECTED: transition to captive portal check");
-
                     log(getName() + " GOOD_LINK_DETECTED: transition to CONNECTED");
                     sendConnectedState();
                     transitionTo(mConnectedState);
@@ -7433,16 +8478,23 @@
     }
 
     private void sendConnectedState() {
-        // Send out a broadcast with the CAPTIVE_PORTAL_CHECK to preserve
-        // existing behaviour. The captive portal check really happens after we
-        // transition into DetailedState.CONNECTED.
-        setNetworkDetailedState(DetailedState.CAPTIVE_PORTAL_CHECK);
-        mWifiConfigStore.updateStatus(mLastNetworkId,
-        DetailedState.CAPTIVE_PORTAL_CHECK);
-        sendNetworkStateChangeBroadcast(mLastBssid);
-
-        if (mWifiConfigStore.getLastSelectedConfiguration() != null) {
-            if (mNetworkAgent != null) mNetworkAgent.explicitlySelected();
+        // If this network was explicitly selected by the user, evaluate whether to call
+        // explicitlySelected() so the system can treat it appropriately.
+        WifiConfiguration config = getCurrentWifiConfiguration();
+        if (mWifiConfigStore.isLastSelectedConfiguration(config)) {
+            boolean prompt = mWifiConfigStore.checkConfigOverridePermission(config.lastConnectUid);
+            if (DBG) {
+                log("Network selected by UID " + config.lastConnectUid + " prompt=" + prompt);
+            }
+            if (prompt) {
+                // Selected by the user via Settings or QuickSettings. If this network has Internet
+                // access, switch to it. Otherwise, switch to it only if the user confirms that they
+                // really want to switch, or has already confirmed and selected "Don't ask again".
+                if (DBG) {
+                    log("explictlySelected acceptUnvalidated=" + config.noInternetAccessExpected);
+                }
+                mNetworkAgent.explicitlySelected(config.noInternetAccessExpected);
+            }
         }
 
         setNetworkDetailedState(DetailedState.CONNECTED);
@@ -7462,7 +8514,7 @@
 
             // Make sure we disconnect if roaming fails
             roamWatchdogCount++;
-            loge("Start Roam Watchdog " + roamWatchdogCount);
+            logd("Start Roam Watchdog " + roamWatchdogCount);
             sendMessageDelayed(obtainMessage(CMD_ROAM_WATCHDOG_TIMER,
                     roamWatchdogCount, 0), ROAM_GUARD_TIMER_MSEC);
             mAssociated = false;
@@ -7475,22 +8527,23 @@
                 case CMD_IP_CONFIGURATION_LOST:
                     config = getCurrentWifiConfiguration();
                     if (config != null) {
+                        mWifiLogger.captureBugReportData(WifiLogger.REPORT_REASON_AUTOROAM_FAILURE);
                         mWifiConfigStore.noteRoamingFailure(config,
                                 WifiConfiguration.ROAMING_FAILURE_IP_CONFIG);
                     }
                     return NOT_HANDLED;
-               case WifiWatchdogStateMachine.POOR_LINK_DETECTED:
+                case WifiWatchdogStateMachine.POOR_LINK_DETECTED:
                     if (DBG) log("Roaming and Watchdog reports poor link -> ignore");
                     return HANDLED;
-               case CMD_UNWANTED_NETWORK:
+                case CMD_UNWANTED_NETWORK:
                     if (DBG) log("Roaming and CS doesnt want the network -> ignore");
                     return HANDLED;
-               case CMD_SET_OPERATIONAL_MODE:
+                case CMD_SET_OPERATIONAL_MODE:
                     if (message.arg1 != CONNECT_MODE) {
                         deferMessage(message);
                     }
                     break;
-               case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
+                case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
                     /**
                      * If we get a SUPPLICANT_STATE_CHANGE_EVENT indicating a DISCONNECT
                      * before NETWORK_DISCONNECTION_EVENT
@@ -7537,6 +8590,7 @@
                        mWifiInfo.setBSSID(mLastBssid);
                        mWifiInfo.setNetworkId(mLastNetworkId);
                        mWifiConfigStore.handleBSSIDBlackList(mLastNetworkId, mLastBssid, true);
+                       sendNetworkStateChangeBroadcast(mLastBssid);
                        transitionTo(mObtainingIpState);
                    } else {
                        messageHandlingStatus = MESSAGE_HANDLING_STATUS_DISCARD;
@@ -7559,13 +8613,15 @@
                    break;
                 case WifiMonitor.SSID_TEMP_DISABLED:
                     // Auth error while roaming
-                    loge("SSID_TEMP_DISABLED nid=" + Integer.toString(mLastNetworkId)
+                    logd("SSID_TEMP_DISABLED nid=" + Integer.toString(mLastNetworkId)
                             + " id=" + Integer.toString(message.arg1)
                             + " isRoaming=" + isRoaming()
                             + " roam=" + Integer.toString(mAutoRoaming));
                     if (message.arg1 == mLastNetworkId) {
                         config = getCurrentWifiConfiguration();
                         if (config != null) {
+                            mWifiLogger.captureBugReportData(
+                                    WifiLogger.REPORT_REASON_AUTOROAM_FAILURE);
                             mWifiConfigStore.noteRoamingFailure(config,
                                     WifiConfiguration.ROAMING_FAILURE_AUTH_FAILURE);
                         }
@@ -7584,7 +8640,7 @@
 
         @Override
         public void exit() {
-            loge("WifiStateMachine: Leaving Roaming state");
+            logd("WifiStateMachine: Leaving Roaming state");
         }
     }
 
@@ -7594,18 +8650,26 @@
             String address;
             updateDefaultRouteMacAddress(1000);
             if (DBG) {
-                log("ConnectedState Enter "
-                        + " mScreenOn=" + mScreenOn
-                        + " scanperiod="
-                        + Integer.toString(mWifiConfigStore.associatedPartialScanPeriodMilli) );
+                log("Enter ConnectedState "
+                       + " mScreenOn=" + mScreenOn
+                       + " scanperiod="
+                       + Integer.toString(mWifiConfigStore.wifiAssociatedShortScanIntervalMilli.get())
+                       + " useGscan=" + mHalBasedPnoDriverSupported + "/"
+                        + mWifiConfigStore.enableHalBasedPno.get()
+                        + " mHalBasedPnoEnableInDevSettings " + mHalBasedPnoEnableInDevSettings);
             }
             if (mScreenOn
-                    && mWifiConfigStore.enableAutoJoinScanWhenAssociated) {
-                // restart scan alarm
-                startDelayedScan(mWifiConfigStore.associatedPartialScanPeriodMilli, null, null);
+                    && getEnableAutoJoinWhenAssociated()) {
+                if (useHalBasedAutoJoinOffload()) {
+                    startGScanConnectedModeOffload("connectedEnter");
+                } else {
+                    // restart scan alarm
+                    startDelayedScan(mWifiConfigStore.wifiAssociatedShortScanIntervalMilli.get(),
+                            null, null);
+                }
             }
             registerConnected();
-            lastConnectAttempt = 0;
+            lastConnectAttemptTimestamp = 0;
             targetWificonfiguration = null;
             // Paranoia
             linkDebouncing = false;
@@ -7615,7 +8679,7 @@
 
             if (testNetworkDisconnect) {
                 testNetworkDisconnectCounter++;
-                loge("ConnectedState Enter start disconnect test " +
+                logd("ConnectedState Enter start disconnect test " +
                         testNetworkDisconnectCounter);
                 sendMessageDelayed(obtainMessage(CMD_TEST_NETWORK_DISCONNECT,
                         testNetworkDisconnectCounter, 0), 15000);
@@ -7625,6 +8689,8 @@
             mWifiConfigStore.enableAllNetworks();
 
             mLastDriverRoamAttempt = 0;
+
+            //startLazyRoam();
         }
         @Override
         public boolean processMessage(Message message) {
@@ -7632,20 +8698,63 @@
             logStateAndMessage(message, getClass().getSimpleName());
 
             switch (message.what) {
+                case CMD_RESTART_AUTOJOIN_OFFLOAD:
+                    if ( (int)message.arg2 < mRestartAutoJoinOffloadCounter ) {
+                        messageHandlingStatus = MESSAGE_HANDLING_STATUS_OBSOLETE;
+                        return HANDLED;
+                    }
+                    /* If we are still in Disconnected state after having discovered a valid
+                     * network this means autojoin didnt managed to associate to the network,
+                     * then restart PNO so as we will try associating to it again.
+                     */
+                    if (useHalBasedAutoJoinOffload()) {
+                        if (mGScanStartTimeMilli == 0) {
+                            // If offload is not started, then start it...
+                            startGScanConnectedModeOffload("connectedRestart");
+                        } else {
+                            // If offload is already started, then check if we need to increase
+                            // the scan period and restart the Gscan
+                            long now = System.currentTimeMillis();
+                            if (mGScanStartTimeMilli != 0 && now > mGScanStartTimeMilli
+                                    && ((now - mGScanStartTimeMilli)
+                                    > DISCONNECTED_SHORT_SCANS_DURATION_MILLI)
+                                && (mGScanPeriodMilli
+                                    < mWifiConfigStore.wifiDisconnectedLongScanIntervalMilli.get()))
+                            {
+                                startConnectedGScan("Connected restart gscan");
+                            }
+                        }
+                    }
+                    break;
+                case CMD_UPDATE_ASSOCIATED_SCAN_PERMISSION:
+                    updateAssociatedScanPermission();
+                    break;
                 case WifiWatchdogStateMachine.POOR_LINK_DETECTED:
                     if (DBG) log("Watchdog reports poor link");
                     transitionTo(mVerifyingLinkState);
                     break;
                 case CMD_UNWANTED_NETWORK:
-                    if (message.arg1 == network_status_unwanted_disconnect) {
+                    if (message.arg1 == NETWORK_STATUS_UNWANTED_DISCONNECT) {
                         mWifiConfigStore.handleBadNetworkDisconnectReport(mLastNetworkId, mWifiInfo);
                         mWifiNative.disconnect();
                         transitionTo(mDisconnectingState);
-                    } else if (message.arg1 == network_status_unwanted_disable_autojoin) {
+                    } else if (message.arg1 == NETWORK_STATUS_UNWANTED_DISABLE_AUTOJOIN ||
+                            message.arg1 == NETWORK_STATUS_UNWANTED_VALIDATION_FAILED) {
                         config = getCurrentWifiConfiguration();
                         if (config != null) {
                             // Disable autojoin
+                            if (message.arg1 == NETWORK_STATUS_UNWANTED_DISABLE_AUTOJOIN) {
+                                config.validatedInternetAccess = false;
+                                // Clear last-selected status, as being last-selected also avoids
+                                // disabling auto-join.
+                                if (mWifiConfigStore.isLastSelectedConfiguration(config)) {
+                                    mWifiConfigStore.setLastSelectedConfiguration(
+                                        WifiConfiguration.INVALID_NETWORK_ID);
+                                }
+                            }
                             config.numNoInternetAccessReports += 1;
+                            config.dirty = true;
+                            mWifiConfigStore.writeKnownNetworkHistory(false);
                         }
                     }
                     return HANDLED;
@@ -7653,12 +8762,24 @@
                     if (message.arg1 == NetworkAgent.VALID_NETWORK) {
                         config = getCurrentWifiConfiguration();
                         if (config != null) {
+                            if (!config.validatedInternetAccess
+                                    || config.numNoInternetAccessReports != 0) {
+                                config.dirty = true;
+                            }
                             // re-enable autojoin
                             config.numNoInternetAccessReports = 0;
                             config.validatedInternetAccess = true;
+                            mWifiConfigStore.writeKnownNetworkHistory(false);
                         }
                     }
                     return HANDLED;
+                case CMD_ACCEPT_UNVALIDATED:
+                    boolean accept = (message.arg1 != 0);
+                    config = getCurrentWifiConfiguration();
+                    if (config != null) {
+                        config.noInternetAccessExpected = accept;
+                    }
+                    return HANDLED;
                 case CMD_TEST_NETWORK_DISCONNECT:
                     // Force a disconnect
                     if (message.arg1 == testNetworkDisconnectCounter) {
@@ -7681,6 +8802,10 @@
                         lastRoam = System.currentTimeMillis() - mLastDriverRoamAttempt;
                         mLastDriverRoamAttempt = 0;
                     }
+                    if (unexpectedDisconnectedReason(message.arg2)) {
+                        mWifiLogger.captureBugReportData(
+                                WifiLogger.REPORT_REASON_UNEXPECTED_DISCONNECT);
+                    }
                     config = getCurrentWifiConfiguration();
                     if (mScreenOn
                             && !linkDebouncing
@@ -7730,7 +8855,7 @@
                     }
                     break;
                 case CMD_AUTO_ROAM:
-                    // Clear the driver roam indication since we are attempting a framerwork roam
+                    // Clear the driver roam indication since we are attempting a framework roam
                     mLastDriverRoamAttempt = 0;
 
                     /* Connect command coming from auto-join */
@@ -7749,7 +8874,7 @@
                         break;
                     }
 
-                    loge("CMD_AUTO_ROAM sup state "
+                    logd("CMD_AUTO_ROAM sup state "
                             + mSupplicantStateTracker.getSupplicantStateName()
                             + " my state " + getCurrentState().getName()
                             + " nid=" + Integer.toString(netId)
@@ -7760,26 +8885,35 @@
 
                     /* Save the BSSID so as to lock it @ firmware */
                     if (!autoRoamSetBSSID(config, bssid) && !linkDebouncing) {
-                        loge("AUTO_ROAM nothing to do");
+                        logd("AUTO_ROAM nothing to do");
                         // Same BSSID, nothing to do
                         messageHandlingStatus = MESSAGE_HANDLING_STATUS_DISCARD;
                         break;
                     };
 
-                    // Make sure the network is enabled, since supplicant will not reenable it
+                    // Make sure the network is enabled, since supplicant will not re-enable it
                     mWifiConfigStore.enableNetworkWithoutBroadcast(netId, false);
 
+                    if (deferForUserInput(message, netId, false)) {
+                        break;
+                    } else if (mWifiConfigStore.getWifiConfiguration(netId).userApproved ==
+                            WifiConfiguration.USER_BANNED) {
+                        replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
+                                WifiManager.NOT_AUTHORIZED);
+                        break;
+                    }
+
                     boolean ret = false;
                     if (mLastNetworkId != netId) {
-                       if (mWifiConfigStore.selectNetwork(netId) &&
-                           mWifiNative.reconnect()) {
+                       if (mWifiConfigStore.selectNetwork(config, /* updatePriorities = */ false,
+                               WifiConfiguration.UNKNOWN_UID) && mWifiNative.reconnect()) {
                            ret = true;
                        }
                     } else {
                          ret = mWifiNative.reassociate();
                     }
                     if (ret) {
-                        lastConnectAttempt = System.currentTimeMillis();
+                        lastConnectAttemptTimestamp = System.currentTimeMillis();
                         targetWificonfiguration = mWifiConfigStore.getWifiConfiguration(netId);
 
                         // replyToMessage(message, WifiManager.CONNECT_NETWORK_SUCCEEDED);
@@ -7802,9 +8936,13 @@
 
         @Override
         public void exit() {
-            loge("WifiStateMachine: Leaving Connected state");
+            logd("WifiStateMachine: Leaving Connected state");
             setScanAlarm(false);
             mLastDriverRoamAttempt = 0;
+
+            stopLazyRoam();
+
+            mWhiteListedSsids = null;
         }
     }
 
@@ -7814,19 +8952,20 @@
         public void enter() {
 
             if (PDBG) {
-                loge(" Enter DisconnectingState State scan interval " + mFrameworkScanIntervalMs
-                        + " mEnableBackgroundScan= " + mEnableBackgroundScan
+                logd(" Enter DisconnectingState State scan interval "
+                        + mWifiConfigStore.wifiDisconnectedShortScanIntervalMilli.get()
+                        + " mLegacyPnoEnabled= " + mLegacyPnoEnabled
                         + " screenOn=" + mScreenOn);
             }
 
-            // Make sure we disconnect: we enter this state prior connecting to a new
-            // network, waiting for either a DISCONECT event or a SUPPLICANT_STATE_CHANGE
+            // Make sure we disconnect: we enter this state prior to connecting to a new
+            // network, waiting for either a DISCONNECT event or a SUPPLICANT_STATE_CHANGE
             // event which in this case will be indicating that supplicant started to associate.
             // In some cases supplicant doesn't ignore the connect requests (it might not
             // find the target SSID in its cache),
             // Therefore we end up stuck that state, hence the need for the watchdog.
             disconnectingWatchdogCount++;
-            loge("Start Disconnecting Watchdog " + disconnectingWatchdogCount);
+            logd("Start Disconnecting Watchdog " + disconnectingWatchdogCount);
             sendMessageDelayed(obtainMessage(CMD_DISCONNECTING_WATCHDOG_TIMER,
                     disconnectingWatchdogCount, 0), DISCONNECTING_GUARD_TIMER_MSEC);
         }
@@ -7877,41 +9016,43 @@
                 return;
             }
 
-            mFrameworkScanIntervalMs = Settings.Global.getLong(mContext.getContentResolver(),
-                    Settings.Global.WIFI_FRAMEWORK_SCAN_INTERVAL_MS,
-                    mDefaultFrameworkScanIntervalMs);
-
             if (PDBG) {
-                loge(" Enter disconnected State scan interval " + mFrameworkScanIntervalMs
-                        + " mEnableBackgroundScan= " + mEnableBackgroundScan
+                logd(" Enter DisconnectedState scan interval "
+                        + mWifiConfigStore.wifiDisconnectedShortScanIntervalMilli.get()
+                        + " mLegacyPnoEnabled= " + mLegacyPnoEnabled
                         + " screenOn=" + mScreenOn
-                        + " mFrameworkScanIntervalMs=" + mFrameworkScanIntervalMs);
+                        + " useGscan=" + mHalBasedPnoDriverSupported + "/"
+                        + mWifiConfigStore.enableHalBasedPno.get());
             }
 
             /** clear the roaming state, if we were roaming, we failed */
             mAutoRoaming = WifiAutoJoinController.AUTO_JOIN_IDLE;
 
-            if (mScreenOn) {
-                /**
-                 * screen lit and => delayed timer
-                 */
-                startDelayedScan(mDisconnectedScanPeriodMs, null, null);
+            if (useHalBasedAutoJoinOffload()) {
+                startGScanDisconnectedModeOffload("disconnectedEnter");
             } else {
-                /**
-                 * screen dark and PNO supported => scan alarm disabled
-                 */
-                if (mEnableBackgroundScan) {
-                    /* If a regular scan result is pending, do not initiate background
-                     * scan until the scan results are returned. This is needed because
-                     * initiating a background scan will cancel the regular scan and
-                     * scan results will not be returned until background scanning is
-                     * cleared
+                if (mScreenOn) {
+                    /**
+                     * screen lit and => delayed timer
                      */
-                    if (!mIsScanOngoing) {
-                        enableBackgroundScan(true);
-                    }
+                    startDelayedScan(500, null, null);
                 } else {
-                    setScanAlarm(true);
+                    /**
+                     * screen dark and PNO supported => scan alarm disabled
+                     */
+                    if (mBackgroundScanSupported) {
+                        /* If a regular scan result is pending, do not initiate background
+                         * scan until the scan results are returned. This is needed because
+                        * initiating a background scan will cancel the regular scan and
+                        * scan results will not be returned until background scanning is
+                        * cleared
+                        */
+                        if (!mIsScanOngoing) {
+                            enableBackgroundScan(true);
+                        }
+                    } else {
+                        setScanAlarm(true);
+                    }
                 }
             }
 
@@ -7920,13 +9061,14 @@
              * The scans are useful to notify the user of the presence of an open network.
              * Note that these are not wake up scans.
              */
-            if (!mP2pConnected.get() && mWifiConfigStore.getConfiguredNetworks().size() == 0) {
+            if (mNoNetworksPeriodicScan != 0 && !mP2pConnected.get()
+                    && mWifiConfigStore.getConfiguredNetworks().size() == 0) {
                 sendMessageDelayed(obtainMessage(CMD_NO_NETWORKS_PERIODIC_SCAN,
-                        ++mPeriodicScanToken, 0), mSupplicantScanIntervalMs);
+                        ++mPeriodicScanToken, 0), mNoNetworksPeriodicScan);
             }
 
             mDisconnectedTimeStamp = System.currentTimeMillis();
-
+            mDisconnectedPnoAlarmCount = 0;
         }
         @Override
         public boolean processMessage(Message message) {
@@ -7937,20 +9079,22 @@
             switch (message.what) {
                 case CMD_NO_NETWORKS_PERIODIC_SCAN:
                     if (mP2pConnected.get()) break;
-                    if (message.arg1 == mPeriodicScanToken &&
+                    if (mNoNetworksPeriodicScan != 0 && message.arg1 == mPeriodicScanToken &&
                             mWifiConfigStore.getConfiguredNetworks().size() == 0) {
                         startScan(UNKNOWN_SCAN_SOURCE, -1, null, null);
                         sendMessageDelayed(obtainMessage(CMD_NO_NETWORKS_PERIODIC_SCAN,
-                                    ++mPeriodicScanToken, 0), mSupplicantScanIntervalMs);
+                                    ++mPeriodicScanToken, 0), mNoNetworksPeriodicScan);
                     }
                     break;
                 case WifiManager.FORGET_NETWORK:
                 case CMD_REMOVE_NETWORK:
+                case CMD_REMOVE_APP_CONFIGURATIONS:
+                case CMD_REMOVE_USER_CONFIGURATIONS:
                     // Set up a delayed message here. After the forget/remove is handled
                     // the handled delayed message will determine if there is a need to
                     // scan and continue
                     sendMessageDelayed(obtainMessage(CMD_NO_NETWORKS_PERIODIC_SCAN,
-                                ++mPeriodicScanToken, 0), mSupplicantScanIntervalMs);
+                                ++mPeriodicScanToken, 0), mNoNetworksPeriodicScan);
                     ret = NOT_HANDLED;
                     break;
                 case CMD_SET_OPERATIONAL_MODE:
@@ -7973,7 +9117,7 @@
                 case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
                     StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
                     if (DBG) {
-                        loge("SUPPLICANT_STATE_CHANGE_EVENT state=" + stateChangeResult.state +
+                        logd("SUPPLICANT_STATE_CHANGE_EVENT state=" + stateChangeResult.state +
                                 " -> state= " + WifiInfo.getDetailedStateOf(stateChangeResult.state)
                                 + " debouncing=" + linkDebouncing);
                     }
@@ -7987,38 +9131,113 @@
                         messageHandlingStatus = MESSAGE_HANDLING_STATUS_REFUSED;
                         return HANDLED;
                     }
-                    /* Disable background scan temporarily during a regular scan */
-                    if (mEnableBackgroundScan) {
-                        enableBackgroundScan(false);
-                    }
                     if (message.arg1 == SCAN_ALARM_SOURCE) {
                         // Check if the CMD_START_SCAN message is obsolete (and thus if it should
                         // not be processed) and restart the scan
-                        int period =  mDisconnectedScanPeriodMs;
+                        int period =  mWifiConfigStore.wifiDisconnectedShortScanIntervalMilli.get();
                         if (mP2pConnected.get()) {
                            period = (int)Settings.Global.getLong(mContext.getContentResolver(),
                                     Settings.Global.WIFI_SCAN_INTERVAL_WHEN_P2P_CONNECTED_MS,
-                                    mDisconnectedScanPeriodMs);
+                                    period);
                         }
                         if (!checkAndRestartDelayedScan(message.arg2,
                                 true, period, null, null)) {
                             messageHandlingStatus = MESSAGE_HANDLING_STATUS_OBSOLETE;
-                            loge("WifiStateMachine Disconnected CMD_START_SCAN source "
+                            logd("Disconnected CMD_START_SCAN source "
                                     + message.arg1
                                     + " " + message.arg2 + ", " + mDelayedScanCounter
                                     + " -> obsolete");
                             return HANDLED;
                         }
+                        /* Disable background scan temporarily during a regular scan */
+                        enableBackgroundScan(false);
                         handleScanRequest(WifiNative.SCAN_WITHOUT_CONNECTION_SETUP, message);
                         ret = HANDLED;
                     } else {
+
+                        /*
+                         * The SCAN request is not handled in this state and
+                         * would eventually might/will get handled in the
+                         * parent's state. The PNO, if already enabled had to
+                         * get disabled before the SCAN trigger. Hence, stop
+                         * the PNO if already enabled in this state, though the
+                         * SCAN request is not handled(PNO disable before the
+                         * SCAN trigger in any other state is not the right
+                         * place to issue).
+                         */
+
+                        enableBackgroundScan(false);
                         ret = NOT_HANDLED;
                     }
                     break;
+                case CMD_RESTART_AUTOJOIN_OFFLOAD:
+                    if ( (int)message.arg2 < mRestartAutoJoinOffloadCounter ) {
+                        messageHandlingStatus = MESSAGE_HANDLING_STATUS_OBSOLETE;
+                        return HANDLED;
+                    }
+                    /* If we are still in Disconnected state after having discovered a valid
+                     * network this means autojoin didnt managed to associate to the network,
+                     * then restart PNO so as we will try associating to it again.
+                     */
+                    if (useHalBasedAutoJoinOffload()) {
+                        if (mGScanStartTimeMilli == 0) {
+                            // If offload is not started, then start it...
+                            startGScanDisconnectedModeOffload("disconnectedRestart");
+                        } else {
+                            // If offload is already started, then check if we need to increase
+                            // the scan period and restart the Gscan
+                            long now = System.currentTimeMillis();
+                            if (mGScanStartTimeMilli != 0 && now > mGScanStartTimeMilli
+                                    && ((now - mGScanStartTimeMilli)
+                                    > DISCONNECTED_SHORT_SCANS_DURATION_MILLI)
+                                    && (mGScanPeriodMilli
+                                    < mWifiConfigStore.wifiDisconnectedLongScanIntervalMilli.get()))
+                            {
+                                startDisconnectedGScan("disconnected restart gscan");
+                            }
+                        }
+                    } else {
+                        // If we are still disconnected for a short while after having found a
+                        // network thru PNO, then something went wrong, and for some reason we
+                        // couldn't join this network.
+                        // It might be due to a SW bug in supplicant or the wifi stack, or an
+                        // interoperability issue, or we try to join a bad bss and failed
+                        // In that case we want to restart pno so as to make sure that we will
+                        // attempt again to join that network.
+                        if (!mScreenOn && !mIsScanOngoing && mBackgroundScanSupported) {
+                            enableBackgroundScan(false);
+                            enableBackgroundScan(true);
+                        }
+                        return HANDLED;
+                    }
+                    break;
                 case WifiMonitor.SCAN_RESULTS_EVENT:
+                case WifiMonitor.SCAN_FAILED_EVENT:
                     /* Re-enable background scan when a pending scan result is received */
-                    if (mEnableBackgroundScan && mIsScanOngoing) {
+                    if (!mScreenOn && mIsScanOngoing
+                            && mBackgroundScanSupported
+                            && !useHalBasedAutoJoinOffload()) {
                         enableBackgroundScan(true);
+                    } else if (!mScreenOn
+                            && !mIsScanOngoing
+                            && mBackgroundScanSupported
+                            && !useHalBasedAutoJoinOffload()) {
+                        // We receive scan results from legacy PNO, hence restart the PNO alarm
+                        int delay;
+                        if (mDisconnectedPnoAlarmCount < 1) {
+                            delay = 30 * 1000;
+                        } else if (mDisconnectedPnoAlarmCount < 3) {
+                            delay = 60 * 1000;
+                        } else {
+                            delay = 360 * 1000;
+                        }
+                        mDisconnectedPnoAlarmCount++;
+                        if (VDBG) {
+                            logd("Starting PNO alarm " + delay);
+                        }
+                        mAlarmManager.set(AlarmManager.RTC_WAKEUP,
+                                System.currentTimeMillis() + delay,
+                                mPnoIntent);
                     }
                     /* Handled in parent state */
                     ret = NOT_HANDLED;
@@ -8036,14 +9255,21 @@
                     } 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);
+                                    ++mPeriodicScanToken, 0), mNoNetworksPeriodicScan);
                     } else {
                         // If P2P is not connected and there are saved networks, then restart
                         // scanning at the normal period. This is necessary because scanning might
                         // have been disabled altogether if WIFI_SCAN_INTERVAL_WHEN_P2P_CONNECTED_MS
                         // was set to zero.
-                        startDelayedScan(mDisconnectedScanPeriodMs, null, null);
+                        if (useHalBasedAutoJoinOffload()) {
+                            startGScanDisconnectedModeOffload("p2pRestart");
+                        } else {
+                            startDelayedScan(
+                                    mWifiConfigStore.wifiDisconnectedShortScanIntervalMilli.get(),
+                                    null, null);
+                        }
                     }
+                    break;
                 case CMD_RECONNECT:
                 case CMD_REASSOCIATE:
                     if (mTemporarilyDisconnectWifi) {
@@ -8056,8 +9282,7 @@
                     }
                     break;
                 case CMD_SCREEN_STATE_CHANGED:
-                    handleScreenStateChanged(message.arg1 != 0,
-                            /* startBackgroundScanIfNeeded = */ true);
+                    handleScreenStateChanged(message.arg1 != 0);
                     break;
                 default:
                     ret = NOT_HANDLED;
@@ -8067,11 +9292,11 @@
 
         @Override
         public void exit() {
+            mDisconnectedPnoAlarmCount = 0;
             /* No need for a background scan upon exit from a disconnected state */
-            if (mEnableBackgroundScan) {
-                enableBackgroundScan(false);
-            }
+            enableBackgroundScan(false);
             setScanAlarm(false);
+            mAlarmManager.cancel(mPnoIntent);
         }
     }
 
@@ -8143,6 +9368,7 @@
                 case CMD_ENABLE_NETWORK:
                 case CMD_RECONNECT:
                 case CMD_REASSOCIATE:
+                case CMD_ENABLE_ALL_NETWORKS:
                     deferMessage(message);
                     break;
                 case CMD_AUTO_CONNECT:
@@ -8226,15 +9452,15 @@
                         startSoftApWithConfig(config);
                     } else {
                         loge("Softap config is null!");
-                        sendMessage(CMD_START_AP_FAILURE);
+                        sendMessage(CMD_START_AP_FAILURE, WifiManager.SAP_START_FAILURE_GENERAL);
                     }
                     break;
                 case CMD_START_AP_SUCCESS:
-                    setWifiApState(WIFI_AP_STATE_ENABLED);
+                    setWifiApState(WIFI_AP_STATE_ENABLED, 0);
                     transitionTo(mSoftApStartedState);
                     break;
                 case CMD_START_AP_FAILURE:
-                    setWifiApState(WIFI_AP_STATE_FAILED);
+                    setWifiApState(WIFI_AP_STATE_FAILED, message.arg1);
                     transitionTo(mInitialState);
                     break;
                 default:
@@ -8258,7 +9484,7 @@
                     } catch(Exception e) {
                         loge("Exception in stopAccessPoint()");
                     }
-                    setWifiApState(WIFI_AP_STATE_DISABLED);
+                    setWifiApState(WIFI_AP_STATE_DISABLED, 0);
                     transitionTo(mInitialState);
                     break;
                 case CMD_START_AP:
@@ -8344,7 +9570,7 @@
                     return HANDLED;
                 case CMD_STOP_AP:
                     if (DBG) log("Untethering before stopping AP");
-                    setWifiApState(WIFI_AP_STATE_DISABLING);
+                    setWifiApState(WIFI_AP_STATE_DISABLING, 0);
                     stopTethering();
                     transitionTo(mUntetheringState);
                     // More work to do after untethering
@@ -8404,27 +9630,26 @@
         }
     }
 
-    //State machine initiated requests can have replyTo set to null indicating
-    //there are no recepients, we ignore those reply actions
+    /**
+     * State machine initiated requests can have replyTo set to null indicating
+     * there are no recepients, we ignore those reply actions.
+     */
     private void replyToMessage(Message msg, int what) {
         if (msg.replyTo == null) return;
-        Message dstMsg = obtainMessageWithArg2(msg);
-        dstMsg.what = what;
+        Message dstMsg = obtainMessageWithWhatAndArg2(msg, what);
         mReplyChannel.replyToMessage(msg, dstMsg);
     }
 
     private void replyToMessage(Message msg, int what, int arg1) {
         if (msg.replyTo == null) return;
-        Message dstMsg = obtainMessageWithArg2(msg);
-        dstMsg.what = what;
+        Message dstMsg = obtainMessageWithWhatAndArg2(msg, what);
         dstMsg.arg1 = arg1;
         mReplyChannel.replyToMessage(msg, dstMsg);
     }
 
     private void replyToMessage(Message msg, int what, Object obj) {
         if (msg.replyTo == null) return;
-        Message dstMsg = obtainMessageWithArg2(msg);
-        dstMsg.what = what;
+        Message dstMsg = obtainMessageWithWhatAndArg2(msg, what);
         dstMsg.obj = obj;
         mReplyChannel.replyToMessage(msg, dstMsg);
     }
@@ -8432,15 +9657,31 @@
     /**
      * arg2 on the source message has a unique id that needs to be retained in replies
      * to match the request
-
-     * see WifiManager for details
+     * <p>see WifiManager for details
      */
-    private Message obtainMessageWithArg2(Message srcMsg) {
+    private Message obtainMessageWithWhatAndArg2(Message srcMsg, int what) {
         Message msg = Message.obtain();
+        msg.what = what;
         msg.arg2 = srcMsg.arg2;
         return msg;
     }
 
+    /**
+     * @param wifiCredentialEventType WIFI_CREDENTIAL_SAVED or WIFI_CREDENTIAL_FORGOT
+     * @param msg Must have a WifiConfiguration obj to succeed
+     */
+    private void broadcastWifiCredentialChanged(int wifiCredentialEventType,
+            WifiConfiguration config) {
+        if (config != null && config.preSharedKey != null) {
+            Intent intent = new Intent(WifiManager.WIFI_CREDENTIAL_CHANGED_ACTION);
+            intent.putExtra(WifiManager.EXTRA_WIFI_CREDENTIAL_SSID, config.SSID);
+            intent.putExtra(WifiManager.EXTRA_WIFI_CREDENTIAL_EVENT_TYPE,
+                    wifiCredentialEventType);
+            mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT,
+                    android.Manifest.permission.RECEIVE_WIFI_CREDENTIAL_CHANGE);
+        }
+    }
+
     private static int parseHex(char ch) {
         if ('0' <= ch && ch <= '9') {
             return ch - '0';
@@ -8490,7 +9731,6 @@
         return sb.toString();
     }
 
-
     private static byte[] concat(byte[] array1, byte[] array2, byte[] array3) {
 
         int len = array1.length + array2.length + array3.length;
@@ -8539,6 +9779,30 @@
         return result;
     }
 
+    private static byte[] concatHex(byte[] array1, byte[] array2) {
+
+        int len = array1.length + array2.length;
+
+        byte[] result = new byte[len];
+
+        int index = 0;
+        if (array1.length != 0) {
+            for (byte b : array1) {
+                result[index] = b;
+                index++;
+            }
+        }
+
+        if (array2.length != 0) {
+            for (byte b : array2) {
+                result[index] = b;
+                index++;
+            }
+        }
+
+        return result;
+    }
+
     void handleGsmAuthRequest(SimAuthRequestData requestData) {
         if (targetWificonfiguration == null
                 || targetWificonfiguration.networkId == requestData.networkId) {
@@ -8553,8 +9817,10 @@
 
         if (tm != null) {
             StringBuilder sb = new StringBuilder();
-            for (String challenge : requestData.challenges) {
+            for (String challenge : requestData.data) {
 
+                if (challenge == null || challenge.isEmpty())
+                    continue;
                 logd("RAND = " + challenge);
 
                 byte[] rand = null;
@@ -8568,11 +9834,18 @@
                 String base64Challenge = android.util.Base64.encodeToString(
                         rand, android.util.Base64.NO_WRAP);
                 /*
-                 * appType = 1 => SIM, 2 => USIM according to
+                 * First, try with appType = 2 => USIM according to
                  * com.android.internal.telephony.PhoneConstants#APPTYPE_xxx
                  */
                 int appType = 2;
                 String tmResponse = tm.getIccSimChallengeResponse(appType, base64Challenge);
+                if (tmResponse == null) {
+                    /* Then, in case of failure, issue may be due to sim type, retry as a simple sim
+                     * appType = 1 => SIM
+                     */
+                    appType = 1;
+                    tmResponse = tm.getIccSimChallengeResponse(appType, base64Challenge);
+                }
                 logv("Raw Response - " + tmResponse);
 
                 if (tmResponse != null && tmResponse.length() > 4) {
@@ -8593,13 +9866,104 @@
 
             String response = sb.toString();
             logv("Supplicant Response -" + response);
-            mWifiNative.simAuthResponse(requestData.networkId, response);
+            mWifiNative.simAuthResponse(requestData.networkId, "GSM-AUTH", response);
         } else {
             loge("could not get telephony manager");
         }
     }
 
     void handle3GAuthRequest(SimAuthRequestData requestData) {
+        StringBuilder sb = new StringBuilder();
+        byte[] rand = null;
+        byte[] authn = null;
+        String res_type = "UMTS-AUTH";
 
+        if (targetWificonfiguration == null
+                || targetWificonfiguration.networkId == requestData.networkId) {
+            logd("id matches targetWifiConfiguration");
+        } else {
+            logd("id does not match targetWifiConfiguration");
+            return;
+        }
+        if (requestData.data.length == 2) {
+            try {
+                rand = parseHex(requestData.data[0]);
+                authn = parseHex(requestData.data[1]);
+            } catch (NumberFormatException e) {
+                loge("malformed challenge");
+            }
+        } else {
+               loge("malformed challenge");
+        }
+
+        String tmResponse = "";
+        if (rand != null && authn != null) {
+            String base64Challenge = android.util.Base64.encodeToString(
+                    concatHex(rand,authn), android.util.Base64.NO_WRAP);
+
+            TelephonyManager tm = (TelephonyManager)
+                    mContext.getSystemService(Context.TELEPHONY_SERVICE);
+            if (tm != null) {
+                int appType = 2; // 2 => USIM
+                tmResponse = tm.getIccSimChallengeResponse(appType, base64Challenge);
+                logv("Raw Response - " + tmResponse);
+            } else {
+                loge("could not get telephony manager");
+            }
+        }
+
+        if (tmResponse != null && tmResponse.length() > 4) {
+            byte[] result = android.util.Base64.decode(tmResponse,
+                    android.util.Base64.DEFAULT);
+            loge("Hex Response - " + makeHex(result));
+            byte tag = result[0];
+            if (tag == (byte) 0xdb) {
+                logv("successful 3G authentication ");
+                int res_len = result[1];
+                String res = makeHex(result, 2, res_len);
+                int ck_len = result[res_len + 2];
+                String ck = makeHex(result, res_len + 3, ck_len);
+                int ik_len = result[res_len + ck_len + 3];
+                String ik = makeHex(result, res_len + ck_len + 4, ik_len);
+                sb.append(":" + ik + ":" + ck + ":" + res);
+                logv("ik:" + ik + "ck:" + ck + " res:" + res);
+            } else if (tag == (byte) 0xdc) {
+                loge("synchronisation failure");
+                int auts_len = result[1];
+                String auts = makeHex(result, 2, auts_len);
+                res_type = "UMTS-AUTS";
+                sb.append(":" + auts);
+                logv("auts:" + auts);
+            } else {
+                loge("bad response - unknown tag = " + tag);
+                return;
+            }
+        } else {
+            loge("bad response - " + tmResponse);
+            return;
+        }
+
+        String response = sb.toString();
+        logv("Supplicant Response -" + response);
+        mWifiNative.simAuthResponse(requestData.networkId, res_type, response);
+    }
+
+    /**
+     * @param reason reason code from supplicant on network disconnected event
+     * @return true if this is a suspicious disconnect
+     */
+    static boolean unexpectedDisconnectedReason(int reason) {
+        return reason == 2              // PREV_AUTH_NOT_VALID
+                || reason == 6          // CLASS2_FRAME_FROM_NONAUTH_STA
+                || reason == 7          // FRAME_FROM_NONASSOC_STA
+                || reason == 8          // STA_HAS_LEFT
+                || reason == 9          // STA_REQ_ASSOC_WITHOUT_AUTH
+                || reason == 14         // MICHAEL_MIC_FAILURE
+                || reason == 15         // 4WAY_HANDSHAKE_TIMEOUT
+                || reason == 16         // GROUP_KEY_UPDATE_TIMEOUT
+                || reason == 18         // GROUP_CIPHER_NOT_VALID
+                || reason == 19         // PAIRWISE_CIPHER_NOT_VALID
+                || reason == 23         // IEEE_802_1X_AUTH_FAILED
+                || reason == 34;        // DISASSOC_LOW_ACK
     }
 }
diff --git a/service/java/com/android/server/wifi/anqp/ANQPElement.java b/service/java/com/android/server/wifi/anqp/ANQPElement.java
new file mode 100644
index 0000000..aec9537
--- /dev/null
+++ b/service/java/com/android/server/wifi/anqp/ANQPElement.java
@@ -0,0 +1,16 @@
+package com.android.server.wifi.anqp;
+
+/**
+ * Base class for an IEEE802.11u ANQP element.
+ */
+public abstract class ANQPElement {
+    private final Constants.ANQPElementType mID;
+
+    protected ANQPElement(Constants.ANQPElementType id) {
+        mID = id;
+    }
+
+    public Constants.ANQPElementType getID() {
+        return mID;
+    }
+}
diff --git a/service/java/com/android/server/wifi/anqp/ANQPFactory.java b/service/java/com/android/server/wifi/anqp/ANQPFactory.java
new file mode 100644
index 0000000..deca9c6
--- /dev/null
+++ b/service/java/com/android/server/wifi/anqp/ANQPFactory.java
@@ -0,0 +1,256 @@
+package com.android.server.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Set;
+
+/**
+ * Factory to build a collection of 802.11u ANQP elements from a byte buffer.
+ */
+public class ANQPFactory {
+
+    private static final Constants.ANQPElementType[] BaseANQPSet = new Constants.ANQPElementType[]{
+            Constants.ANQPElementType.ANQPVenueName,
+            Constants.ANQPElementType.ANQPNwkAuthType,
+            Constants.ANQPElementType.ANQPRoamingConsortium,
+            Constants.ANQPElementType.ANQPIPAddrAvailability,
+            Constants.ANQPElementType.ANQPNAIRealm,
+            Constants.ANQPElementType.ANQP3GPPNetwork,
+            Constants.ANQPElementType.ANQPDomName
+    };
+
+    private static final Constants.ANQPElementType[] HS20ANQPSet = new Constants.ANQPElementType[]{
+            Constants.ANQPElementType.HSFriendlyName,
+            Constants.ANQPElementType.HSWANMetrics,
+            Constants.ANQPElementType.HSConnCapability
+    };
+
+    public static Constants.ANQPElementType[] getBaseANQPSet() {
+        return BaseANQPSet;
+    }
+
+    public static Constants.ANQPElementType[] getHS20ANQPSet() {
+        return HS20ANQPSet;
+    }
+
+    public static ByteBuffer buildQueryRequest(Set<Constants.ANQPElementType> elements,
+                                               ByteBuffer target) {
+        List<Constants.ANQPElementType> list = new ArrayList<Constants.ANQPElementType>(elements);
+        Collections.sort(list);
+
+        ListIterator<Constants.ANQPElementType> elementIterator = list.listIterator();
+
+        target.order(ByteOrder.LITTLE_ENDIAN);
+        target.putShort((short) Constants.ANQP_QUERY_LIST);
+        int lenPos = target.position();
+        target.putShort((short) 0);
+
+        while (elementIterator.hasNext()) {
+            Integer id = Constants.getANQPElementID(elementIterator.next());
+            if (id != null) {
+                target.putShort(id.shortValue());
+            } else {
+                elementIterator.previous();
+                break;
+            }
+        }
+        target.putShort(lenPos, (short) (target.position() - lenPos - Constants.BYTES_IN_SHORT));
+
+        // Start a new vendor specific element for HS2.0 elements:
+        if (elementIterator.hasNext()) {
+            target.putShort((short) Constants.ANQP_VENDOR_SPEC);
+            int vsLenPos = target.position();
+            target.putShort((short) 0);
+
+            target.putInt(Constants.HS20_PREFIX);
+            target.put((byte) Constants.HS_QUERY_LIST);
+            target.put((byte) 0);
+
+            while (elementIterator.hasNext()) {
+                Constants.ANQPElementType elementType = elementIterator.next();
+                Integer id = Constants.getHS20ElementID(elementType);
+                if (id == null) {
+                    throw new RuntimeException("Unmapped ANQPElementType: " + elementType);
+                } else {
+                    target.put(id.byteValue());
+                }
+            }
+            target.putShort(vsLenPos,
+                    (short) (target.position() - vsLenPos - Constants.BYTES_IN_SHORT));
+        }
+
+        target.flip();
+        return target;
+    }
+
+    public static ByteBuffer buildHomeRealmRequest(List<String> realmNames, ByteBuffer target) {
+        target.order(ByteOrder.LITTLE_ENDIAN);
+        target.putShort((short) Constants.ANQP_VENDOR_SPEC);
+        int lenPos = target.position();
+        target.putShort((short) 0);
+
+        target.putInt(Constants.HS20_PREFIX);
+        target.put((byte) Constants.HS_NAI_HOME_REALM_QUERY);
+        target.put((byte) 0);
+
+        target.put((byte) realmNames.size());
+        for (String realmName : realmNames) {
+            target.put((byte) Constants.UTF8_INDICATOR);
+            byte[] octets = realmName.getBytes(StandardCharsets.UTF_8);
+            target.put((byte) octets.length);
+            target.put(octets);
+        }
+        target.putShort(lenPos, (short) (target.position() - lenPos - Constants.BYTES_IN_SHORT));
+
+        target.flip();
+        return target;
+    }
+
+    public static ByteBuffer buildIconRequest(String fileName, ByteBuffer target) {
+        target.order(ByteOrder.LITTLE_ENDIAN);
+        target.putShort((short) Constants.ANQP_VENDOR_SPEC);
+        int lenPos = target.position();
+        target.putShort((short) 0);
+
+        target.putInt(Constants.HS20_PREFIX);
+        target.put((byte) Constants.HS_ICON_REQUEST);
+        target.put((byte) 0);
+
+        target.put(fileName.getBytes(StandardCharsets.UTF_8));
+        target.putShort(lenPos, (short) (target.position() - lenPos - Constants.BYTES_IN_SHORT));
+
+        target.flip();
+        return target;
+    }
+
+    public static List<ANQPElement> parsePayload(ByteBuffer payload) throws ProtocolException {
+        payload.order(ByteOrder.LITTLE_ENDIAN);
+        List<ANQPElement> elements = new ArrayList<ANQPElement>();
+        while (payload.hasRemaining()) {
+            elements.add(buildElement(payload));
+        }
+        return elements;
+    }
+
+    private static ANQPElement buildElement(ByteBuffer payload) throws ProtocolException {
+        if (payload.remaining() < 4)
+            throw new ProtocolException("Runt payload: " + payload.remaining());
+
+        int infoIDNumber = payload.getShort() & Constants.SHORT_MASK;
+        Constants.ANQPElementType infoID = Constants.mapANQPElement(infoIDNumber);
+        if (infoID == null) {
+            throw new ProtocolException("Bad info ID: " + infoIDNumber);
+        }
+        int length = payload.getShort() & Constants.SHORT_MASK;
+
+        if (payload.remaining() < length) {
+            throw new ProtocolException("Truncated payload: " +
+                    payload.remaining() + " vs " + length);
+        }
+        return buildElement(payload, infoID, length);
+    }
+
+    public static ANQPElement buildElement(ByteBuffer payload, Constants.ANQPElementType infoID,
+                                            int length) throws ProtocolException {
+        try {
+            ByteBuffer elementPayload = payload.duplicate().order(ByteOrder.LITTLE_ENDIAN);
+            payload.position(payload.position() + length);
+            elementPayload.limit(elementPayload.position() + length);
+
+            switch (infoID) {
+                case ANQPCapabilityList:
+                    return new CapabilityListElement(infoID, elementPayload);
+                case ANQPVenueName:
+                    return new VenueNameElement(infoID, elementPayload);
+                case ANQPEmergencyNumber:
+                    return new EmergencyNumberElement(infoID, elementPayload);
+                case ANQPNwkAuthType:
+                    return new NetworkAuthenticationTypeElement(infoID, elementPayload);
+                case ANQPRoamingConsortium:
+                    return new RoamingConsortiumElement(infoID, elementPayload);
+                case ANQPIPAddrAvailability:
+                    return new IPAddressTypeAvailabilityElement(infoID, elementPayload);
+                case ANQPNAIRealm:
+                    return new NAIRealmElement(infoID, elementPayload);
+                case ANQP3GPPNetwork:
+                    return new ThreeGPPNetworkElement(infoID, elementPayload);
+                case ANQPGeoLoc:
+                    return new GEOLocationElement(infoID, elementPayload);
+                case ANQPCivicLoc:
+                    return new CivicLocationElement(infoID, elementPayload);
+                case ANQPLocURI:
+                    return new GenericStringElement(infoID, elementPayload);
+                case ANQPDomName:
+                    return new DomainNameElement(infoID, elementPayload);
+                case ANQPEmergencyAlert:
+                    return new GenericStringElement(infoID, elementPayload);
+                case ANQPTDLSCap:
+                    return new GenericBlobElement(infoID, elementPayload);
+                case ANQPEmergencyNAI:
+                    return new GenericStringElement(infoID, elementPayload);
+                case ANQPNeighborReport:
+                    return new GenericBlobElement(infoID, elementPayload);
+                case ANQPVendorSpec:
+                    if (elementPayload.remaining() > 5) {
+                        int oi = elementPayload.getInt();
+                        if (oi != Constants.HS20_PREFIX) {
+                            return null;
+                        }
+                        int subType = elementPayload.get() & Constants.BYTE_MASK;
+                        Constants.ANQPElementType hs20ID = Constants.mapHS20Element(subType);
+                        if (hs20ID == null) {
+                            throw new ProtocolException("Bad HS20 info ID: " + subType);
+                        }
+                        elementPayload.get();   // Skip the reserved octet
+                        return buildHS20Element(hs20ID, elementPayload);
+                    } else {
+                        return new GenericBlobElement(infoID, elementPayload);
+                    }
+                default:
+                    throw new ProtocolException("Unknown element ID: " + infoID);
+            }
+        } catch (ProtocolException e) {
+            throw e;
+        } catch (Exception e) {
+            // TODO: remove this catch-all for exceptions, once the element parsing code
+            // has been thoroughly unit tested. b/30562650
+            throw new ProtocolException("Unknown parsing error", e);
+        }
+    }
+
+    public static ANQPElement buildHS20Element(Constants.ANQPElementType infoID,
+                                                ByteBuffer payload) throws ProtocolException {
+        try {
+            switch (infoID) {
+                case HSCapabilityList:
+                    return new HSCapabilityListElement(infoID, payload);
+                case HSFriendlyName:
+                    return new HSFriendlyNameElement(infoID, payload);
+                case HSWANMetrics:
+                    return new HSWanMetricsElement(infoID, payload);
+                case HSConnCapability:
+                    return new HSConnectionCapabilityElement(infoID, payload);
+                case HSOperatingclass:
+                    return new GenericBlobElement(infoID, payload);
+                case HSOSUProviders:
+                    return new HSOsuProvidersElement(infoID, payload);
+                case HSIconFile:
+                    return new HSIconFileElement(infoID, payload);
+                default:
+                    return null;
+            }
+        } catch (ProtocolException e) {
+            throw e;
+        } catch (Exception e) {
+            // TODO: remove this catch-all for exceptions, once the element parsing code
+            // has been thoroughly unit tested. b/30562650
+            throw new ProtocolException("Unknown parsing error", e);
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/anqp/CapabilityListElement.java b/service/java/com/android/server/wifi/anqp/CapabilityListElement.java
new file mode 100644
index 0000000..301d417
--- /dev/null
+++ b/service/java/com/android/server/wifi/anqp/CapabilityListElement.java
@@ -0,0 +1,40 @@
+package com.android.server.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+/**
+ * The ANQP Capability List element, 802.11-2012 section 8.4.4.3
+ */
+public class CapabilityListElement extends ANQPElement {
+    private final Constants.ANQPElementType[] mCapabilities;
+
+    public CapabilityListElement(Constants.ANQPElementType infoID, ByteBuffer payload)
+            throws ProtocolException {
+        super(infoID);
+        if ((payload.remaining() & 1) == 1)
+            throw new ProtocolException("Odd length");
+        mCapabilities = new Constants.ANQPElementType[payload.remaining() / Constants.BYTES_IN_SHORT];
+
+        int index = 0;
+        while (payload.hasRemaining()) {
+            int capID = payload.getShort() & Constants.SHORT_MASK;
+            Constants.ANQPElementType capability = Constants.mapANQPElement(capID);
+            if (capability == null)
+                throw new ProtocolException("Unknown capability: " + capID);
+            mCapabilities[index++] = capability;
+        }
+    }
+
+    public Constants.ANQPElementType[] getCapabilities() {
+        return mCapabilities;
+    }
+
+    @Override
+    public String toString() {
+        return "CapabilityList{" +
+                "mCapabilities=" + Arrays.toString(mCapabilities) +
+                '}';
+    }
+}
diff --git a/service/java/com/android/server/wifi/anqp/CellularNetwork.java b/service/java/com/android/server/wifi/anqp/CellularNetwork.java
new file mode 100644
index 0000000..03d607e
--- /dev/null
+++ b/service/java/com/android/server/wifi/anqp/CellularNetwork.java
@@ -0,0 +1,70 @@
+package com.android.server.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import static com.android.server.wifi.anqp.Constants.BYTE_MASK;
+
+public class CellularNetwork implements Iterable<String> {
+    private static final int PLMNListType = 0;
+
+    private final List<String> mMccMnc;
+
+    private CellularNetwork(int plmnCount, ByteBuffer payload) throws ProtocolException {
+        mMccMnc = new ArrayList<>(plmnCount);
+
+        while (plmnCount > 0) {
+            if (payload.remaining() < 3) {
+                throw new ProtocolException("Truncated PLMN info");
+            }
+            byte[] plmn = new byte[3];
+            payload.get(plmn);
+
+            int mcc = ((plmn[0] << 8) & 0xf00) |
+                    (plmn[0] & 0x0f0) |
+                    (plmn[1] & 0x00f);
+
+            int mnc = ((plmn[2] << 4) & 0xf0) |
+                    ((plmn[2] >> 4) & 0x0f);
+
+            int n2 = (plmn[1] >> 4) & 0x0f;
+            String mccMnc = n2 != 0xf ?
+                    String.format("%03x%03x", mcc, (mnc << 4) | n2) :
+                    String.format("%03x%02x", mcc, mnc);
+
+            mMccMnc.add(mccMnc);
+            plmnCount--;
+        }
+    }
+
+    public static CellularNetwork buildCellularNetwork(ByteBuffer payload)
+            throws ProtocolException {
+        int iei = payload.get() & BYTE_MASK;
+        int plmnLen = payload.get() & 0x7f;
+
+        if (iei != PLMNListType) {
+            payload.position(payload.position() + plmnLen);
+            return null;
+        }
+
+        int plmnCount = payload.get() & BYTE_MASK;
+        return new CellularNetwork(plmnCount, payload);
+    }
+
+    @Override
+    public Iterator<String> iterator() {
+        return mMccMnc.iterator();
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder("PLMN:");
+        for (String mccMnc : mMccMnc) {
+            sb.append(' ').append(mccMnc);
+        }
+        return sb.toString();
+    }
+}
diff --git a/service/java/com/android/server/wifi/anqp/CivicLocationElement.java b/service/java/com/android/server/wifi/anqp/CivicLocationElement.java
new file mode 100644
index 0000000..7269336
--- /dev/null
+++ b/service/java/com/android/server/wifi/anqp/CivicLocationElement.java
@@ -0,0 +1,199 @@
+package com.android.server.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * The Civic Location ANQP Element, IEEE802.11-2012 section 8.4.4.13
+ */
+public class CivicLocationElement extends ANQPElement {
+    public enum LocationType {DHCPServer, NwkElement, Client}
+
+    private static final int GEOCONF_CIVIC4 = 99;
+    private static final int RFC4776 = 0;       // Table 8-77, 1=vendor specific
+
+    private final LocationType mLocationType;
+    private final Locale mLocale;
+    private final Map<CAType, String> mValues;
+
+    public CivicLocationElement(Constants.ANQPElementType infoID, ByteBuffer payload)
+            throws ProtocolException {
+        super(infoID);
+
+        if (payload.remaining() < 6) {
+            throw new ProtocolException("Runt civic location:" + payload.remaining());
+        }
+
+        int locType = payload.get() & Constants.BYTE_MASK;
+        if (locType != RFC4776) {
+            throw new ProtocolException("Bad Civic location type: " + locType);
+        }
+
+        int locSubType = payload.get() & Constants.BYTE_MASK;
+        if (locSubType != GEOCONF_CIVIC4) {
+            throw new ProtocolException("Unexpected Civic location sub-type: " + locSubType +
+                    " (cannot handle sub elements)");
+        }
+
+        int length = payload.get() & Constants.BYTE_MASK;
+        if (length > payload.remaining()) {
+            throw new ProtocolException("Invalid CA type length: " + length);
+        }
+
+        int what = payload.get() & Constants.BYTE_MASK;
+        mLocationType = what < LocationType.values().length ? LocationType.values()[what] : null;
+
+        mLocale = Locale.forLanguageTag(Constants.getString(payload, 2, StandardCharsets.US_ASCII));
+
+        mValues = new HashMap<CAType, String>();
+        while (payload.hasRemaining()) {
+            int caTypeNumber = payload.get() & Constants.BYTE_MASK;
+            CAType caType = s_caTypes.get(caTypeNumber);
+
+            int caValLen = payload.get() & Constants.BYTE_MASK;
+            if (caValLen > payload.remaining()) {
+                throw new ProtocolException("Bad CA value length: " + caValLen);
+            }
+            byte[] caValOctets = new byte[caValLen];
+            payload.get(caValOctets);
+
+            if (caType != null) {
+                mValues.put(caType, new String(caValOctets, StandardCharsets.UTF_8));
+            }
+        }
+    }
+
+    public LocationType getLocationType() {
+        return mLocationType;
+    }
+
+    public Locale getLocale() {
+        return mLocale;
+    }
+
+    public Map<CAType, String> getValues() {
+        return Collections.unmodifiableMap(mValues);
+    }
+
+    @Override
+    public String toString() {
+        return "CivicLocation{" +
+                "mLocationType=" + mLocationType +
+                ", mLocale=" + mLocale +
+                ", mValues=" + mValues +
+                '}';
+    }
+
+    private static final Map<Integer, CAType> s_caTypes = new HashMap<Integer, CAType>();
+
+    public static final int LANGUAGE = 0;
+    public static final int STATE_PROVINCE = 1;
+    public static final int COUNTY_DISTRICT = 2;
+    public static final int CITY = 3;
+    public static final int DIVISION_BOROUGH = 4;
+    public static final int BLOCK = 5;
+    public static final int STREET_GROUP = 6;
+    public static final int STREET_DIRECTION = 16;
+    public static final int LEADING_STREET_SUFFIX = 17;
+    public static final int STREET_SUFFIX = 18;
+    public static final int HOUSE_NUMBER = 19;
+    public static final int HOUSE_NUMBER_SUFFIX = 20;
+    public static final int LANDMARK = 21;
+    public static final int ADDITIONAL_LOCATION = 22;
+    public static final int NAME = 23;
+    public static final int POSTAL_ZIP = 24;
+    public static final int BUILDING = 25;
+    public static final int UNIT = 26;
+    public static final int FLOOR = 27;
+    public static final int ROOM = 28;
+    public static final int TYPE = 29;
+    public static final int POSTAL_COMMUNITY = 30;
+    public static final int PO_BOX = 31;
+    public static final int ADDITIONAL_CODE = 32;
+    public static final int SEAT_DESK = 33;
+    public static final int PRIMARY_ROAD = 34;
+    public static final int ROAD_SECTION = 35;
+    public static final int BRANCH_ROAD = 36;
+    public static final int SUB_BRANCH_ROAD = 37;
+    public static final int STREET_NAME_PRE_MOD = 38;
+    public static final int STREET_NAME_POST_MOD = 39;
+    public static final int SCRIPT = 128;
+    public static final int RESERVED = 255;
+
+    public enum CAType {
+        Language,
+        StateProvince,
+        CountyDistrict,
+        City,
+        DivisionBorough,
+        Block,
+        StreetGroup,
+        StreetDirection,
+        LeadingStreetSuffix,
+        StreetSuffix,
+        HouseNumber,
+        HouseNumberSuffix,
+        Landmark,
+        AdditionalLocation,
+        Name,
+        PostalZIP,
+        Building,
+        Unit,
+        Floor,
+        Room,
+        Type,
+        PostalCommunity,
+        POBox,
+        AdditionalCode,
+        SeatDesk,
+        PrimaryRoad,
+        RoadSection,
+        BranchRoad,
+        SubBranchRoad,
+        StreetNamePreMod,
+        StreetNamePostMod,
+        Script,
+        Reserved
+    }
+
+    static {
+        s_caTypes.put(LANGUAGE, CAType.Language);
+        s_caTypes.put(STATE_PROVINCE, CAType.StateProvince);
+        s_caTypes.put(COUNTY_DISTRICT, CAType.CountyDistrict);
+        s_caTypes.put(CITY, CAType.City);
+        s_caTypes.put(DIVISION_BOROUGH, CAType.DivisionBorough);
+        s_caTypes.put(BLOCK, CAType.Block);
+        s_caTypes.put(STREET_GROUP, CAType.StreetGroup);
+        s_caTypes.put(STREET_DIRECTION, CAType.StreetDirection);
+        s_caTypes.put(LEADING_STREET_SUFFIX, CAType.LeadingStreetSuffix);
+        s_caTypes.put(STREET_SUFFIX, CAType.StreetSuffix);
+        s_caTypes.put(HOUSE_NUMBER, CAType.HouseNumber);
+        s_caTypes.put(HOUSE_NUMBER_SUFFIX, CAType.HouseNumberSuffix);
+        s_caTypes.put(LANDMARK, CAType.Landmark);
+        s_caTypes.put(ADDITIONAL_LOCATION, CAType.AdditionalLocation);
+        s_caTypes.put(NAME, CAType.Name);
+        s_caTypes.put(POSTAL_ZIP, CAType.PostalZIP);
+        s_caTypes.put(BUILDING, CAType.Building);
+        s_caTypes.put(UNIT, CAType.Unit);
+        s_caTypes.put(FLOOR, CAType.Floor);
+        s_caTypes.put(ROOM, CAType.Room);
+        s_caTypes.put(TYPE, CAType.Type);
+        s_caTypes.put(POSTAL_COMMUNITY, CAType.PostalCommunity);
+        s_caTypes.put(PO_BOX, CAType.POBox);
+        s_caTypes.put(ADDITIONAL_CODE, CAType.AdditionalCode);
+        s_caTypes.put(SEAT_DESK, CAType.SeatDesk);
+        s_caTypes.put(PRIMARY_ROAD, CAType.PrimaryRoad);
+        s_caTypes.put(ROAD_SECTION, CAType.RoadSection);
+        s_caTypes.put(BRANCH_ROAD, CAType.BranchRoad);
+        s_caTypes.put(SUB_BRANCH_ROAD, CAType.SubBranchRoad);
+        s_caTypes.put(STREET_NAME_PRE_MOD, CAType.StreetNamePreMod);
+        s_caTypes.put(STREET_NAME_POST_MOD, CAType.StreetNamePostMod);
+        s_caTypes.put(SCRIPT, CAType.Script);
+        s_caTypes.put(RESERVED, CAType.Reserved);
+    }
+}
diff --git a/service/java/com/android/server/wifi/anqp/Constants.java b/service/java/com/android/server/wifi/anqp/Constants.java
new file mode 100644
index 0000000..e32c209
--- /dev/null
+++ b/service/java/com/android/server/wifi/anqp/Constants.java
@@ -0,0 +1,196 @@
+package com.android.server.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * ANQP related constants (802.11-2012)
+ */
+public class Constants {
+
+    public static final int NIBBLE_MASK = 0x0f;
+    public static final int BYTE_MASK = 0xff;
+    public static final int SHORT_MASK = 0xffff;
+    public static final long INT_MASK = 0xffffffffL;
+    public static final int BYTES_IN_SHORT = 2;
+    public static final int BYTES_IN_INT = 4;
+    public static final int BYTES_IN_EUI48 = 6;
+
+    public static final int HS20_PREFIX = 0x119a6f50;   // Note that this is represented as a LE int
+    public static final int HS20_FRAME_PREFIX = 0x109a6f50;
+    public static final int UTF8_INDICATOR = 1;
+
+    public static final int ANQP_QUERY_LIST = 256;
+    public static final int ANQP_CAPABILITY_LIST = 257;
+    public static final int ANQP_VENUE_NAME = 258;
+    public static final int ANQP_EMERGENCY_NUMBER = 259;
+    public static final int ANQP_NWK_AUTH_TYPE = 260;
+    public static final int ANQP_ROAMING_CONSORTIUM = 261;
+    public static final int ANQP_IP_ADDR_AVAILABILITY = 262;
+    public static final int ANQP_NAI_REALM = 263;
+    public static final int ANQP_3GPP_NETWORK = 264;
+    public static final int ANQP_GEO_LOC = 265;
+    public static final int ANQP_CIVIC_LOC = 266;
+    public static final int ANQP_LOC_URI = 267;
+    public static final int ANQP_DOM_NAME = 268;
+    public static final int ANQP_EMERGENCY_ALERT = 269;
+    public static final int ANQP_TDLS_CAP = 270;
+    public static final int ANQP_EMERGENCY_NAI = 271;
+    public static final int ANQP_NEIGHBOR_REPORT = 272;
+    public static final int ANQP_VENDOR_SPEC = 56797;
+
+    public static final int HS_QUERY_LIST = 1;
+    public static final int HS_CAPABILITY_LIST = 2;
+    public static final int HS_FRIENDLY_NAME = 3;
+    public static final int HS_WAN_METRICS = 4;
+    public static final int HS_CONN_CAPABILITY = 5;
+    public static final int HS_NAI_HOME_REALM_QUERY = 6;
+    public static final int HS_OPERATING_CLASS = 7;
+    public static final int HS_OSU_PROVIDERS = 8;
+    public static final int HS_ICON_REQUEST = 10;
+    public static final int HS_ICON_FILE = 11;
+
+    public enum ANQPElementType {
+        ANQPQueryList,
+        ANQPCapabilityList,
+        ANQPVenueName,
+        ANQPEmergencyNumber,
+        ANQPNwkAuthType,
+        ANQPRoamingConsortium,
+        ANQPIPAddrAvailability,
+        ANQPNAIRealm,
+        ANQP3GPPNetwork,
+        ANQPGeoLoc,
+        ANQPCivicLoc,
+        ANQPLocURI,
+        ANQPDomName,
+        ANQPEmergencyAlert,
+        ANQPTDLSCap,
+        ANQPEmergencyNAI,
+        ANQPNeighborReport,
+        ANQPVendorSpec,
+        HSQueryList,
+        HSCapabilityList,
+        HSFriendlyName,
+        HSWANMetrics,
+        HSConnCapability,
+        HSNAIHomeRealmQuery,
+        HSOperatingclass,
+        HSOSUProviders,
+        HSIconRequest,
+        HSIconFile
+    }
+
+    private static final Map<Integer, ANQPElementType> sAnqpMap = new HashMap<Integer, ANQPElementType>();
+    private static final Map<Integer, ANQPElementType> sHs20Map = new HashMap<Integer, ANQPElementType>();
+    private static final Map<ANQPElementType, Integer> sRevAnqpmap = new HashMap<ANQPElementType, Integer>();
+    private static final Map<ANQPElementType, Integer> sRevHs20map = new HashMap<ANQPElementType, Integer>();
+
+    static {
+        sAnqpMap.put(ANQP_QUERY_LIST, ANQPElementType.ANQPQueryList);
+        sAnqpMap.put(ANQP_CAPABILITY_LIST, ANQPElementType.ANQPCapabilityList);
+        sAnqpMap.put(ANQP_VENUE_NAME, ANQPElementType.ANQPVenueName);
+        sAnqpMap.put(ANQP_EMERGENCY_NUMBER, ANQPElementType.ANQPEmergencyNumber);
+        sAnqpMap.put(ANQP_NWK_AUTH_TYPE, ANQPElementType.ANQPNwkAuthType);
+        sAnqpMap.put(ANQP_ROAMING_CONSORTIUM, ANQPElementType.ANQPRoamingConsortium);
+        sAnqpMap.put(ANQP_IP_ADDR_AVAILABILITY, ANQPElementType.ANQPIPAddrAvailability);
+        sAnqpMap.put(ANQP_NAI_REALM, ANQPElementType.ANQPNAIRealm);
+        sAnqpMap.put(ANQP_3GPP_NETWORK, ANQPElementType.ANQP3GPPNetwork);
+        sAnqpMap.put(ANQP_GEO_LOC, ANQPElementType.ANQPGeoLoc);
+        sAnqpMap.put(ANQP_CIVIC_LOC, ANQPElementType.ANQPCivicLoc);
+        sAnqpMap.put(ANQP_LOC_URI, ANQPElementType.ANQPLocURI);
+        sAnqpMap.put(ANQP_DOM_NAME, ANQPElementType.ANQPDomName);
+        sAnqpMap.put(ANQP_EMERGENCY_ALERT, ANQPElementType.ANQPEmergencyAlert);
+        sAnqpMap.put(ANQP_TDLS_CAP, ANQPElementType.ANQPTDLSCap);
+        sAnqpMap.put(ANQP_EMERGENCY_NAI, ANQPElementType.ANQPEmergencyNAI);
+        sAnqpMap.put(ANQP_NEIGHBOR_REPORT, ANQPElementType.ANQPNeighborReport);
+        sAnqpMap.put(ANQP_VENDOR_SPEC, ANQPElementType.ANQPVendorSpec);
+
+        sHs20Map.put(HS_QUERY_LIST, ANQPElementType.HSQueryList);
+        sHs20Map.put(HS_CAPABILITY_LIST, ANQPElementType.HSCapabilityList);
+        sHs20Map.put(HS_FRIENDLY_NAME, ANQPElementType.HSFriendlyName);
+        sHs20Map.put(HS_WAN_METRICS, ANQPElementType.HSWANMetrics);
+        sHs20Map.put(HS_CONN_CAPABILITY, ANQPElementType.HSConnCapability);
+        sHs20Map.put(HS_NAI_HOME_REALM_QUERY, ANQPElementType.HSNAIHomeRealmQuery);
+        sHs20Map.put(HS_OPERATING_CLASS, ANQPElementType.HSOperatingclass);
+        sHs20Map.put(HS_OSU_PROVIDERS, ANQPElementType.HSOSUProviders);
+        sHs20Map.put(HS_ICON_REQUEST, ANQPElementType.HSIconRequest);
+        sHs20Map.put(HS_ICON_FILE, ANQPElementType.HSIconFile);
+
+        for (Map.Entry<Integer, ANQPElementType> entry : sAnqpMap.entrySet()) {
+            sRevAnqpmap.put(entry.getValue(), entry.getKey());
+        }
+        for (Map.Entry<Integer, ANQPElementType> entry : sHs20Map.entrySet()) {
+            sRevHs20map.put(entry.getValue(), entry.getKey());
+        }
+    }
+
+    public static ANQPElementType mapANQPElement(int id) {
+        return sAnqpMap.get(id);
+    }
+
+    public static ANQPElementType mapHS20Element(int id) {
+        return sHs20Map.get(id);
+    }
+
+    public static Integer getANQPElementID(ANQPElementType elementType) {
+        return sRevAnqpmap.get(elementType);
+    }
+
+    public static Integer getHS20ElementID(ANQPElementType elementType) {
+        return sRevHs20map.get(elementType);
+    }
+
+    public static long getInteger(ByteBuffer payload, ByteOrder bo, int size) {
+        byte[] octets = new byte[size];
+        payload.get(octets);
+        long value = 0;
+        if (bo == ByteOrder.LITTLE_ENDIAN) {
+            for (int n = octets.length - 1; n >= 0; n--) {
+                value = (value << Byte.SIZE) | (octets[n] & BYTE_MASK);
+            }
+        }
+        else {
+            for (byte octet : octets) {
+                value = (value << Byte.SIZE) | (octet & BYTE_MASK);
+            }
+        }
+        return value;
+    }
+
+    public static String getPrefixedString(ByteBuffer payload, int lengthLength, Charset charset)
+            throws ProtocolException {
+        return getPrefixedString(payload, lengthLength, charset, false);
+    }
+
+    public static String getPrefixedString(ByteBuffer payload, int lengthLength, Charset charset,
+                                           boolean useNull) throws ProtocolException {
+        if (payload.remaining() < lengthLength) {
+            throw new ProtocolException("Runt string: " + payload.remaining());
+        }
+        return getString(payload, (int) getInteger(payload, ByteOrder.LITTLE_ENDIAN,
+                lengthLength), charset, useNull);
+    }
+
+    public static String getString(ByteBuffer payload, int length, Charset charset)
+            throws ProtocolException {
+        return getString(payload, length, charset, false);
+    }
+
+    public static String getString(ByteBuffer payload, int length, Charset charset, boolean useNull)
+            throws ProtocolException {
+        if (length > payload.remaining()) {
+            throw new ProtocolException("Bad string length: " + length);
+        }
+        if (useNull && length == 0) {
+            return null;
+        }
+        byte[] octets = new byte[length];
+        payload.get(octets);
+        return new String(octets, charset);
+    }
+}
diff --git a/service/java/com/android/server/wifi/anqp/DomainNameElement.java b/service/java/com/android/server/wifi/anqp/DomainNameElement.java
new file mode 100644
index 0000000..69edc90
--- /dev/null
+++ b/service/java/com/android/server/wifi/anqp/DomainNameElement.java
@@ -0,0 +1,37 @@
+package com.android.server.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * The Domain Name ANQP Element, IEEE802.11-2012 section 8.4.4.15
+ */
+public class DomainNameElement extends ANQPElement {
+    private final List<String> mDomains;
+
+    public DomainNameElement(Constants.ANQPElementType infoID, ByteBuffer payload)
+            throws ProtocolException {
+        super(infoID);
+        mDomains = new ArrayList<>();
+
+        while (payload.hasRemaining()) {
+            // Use latin-1 to decode for now - safe for ASCII and retains encoding
+            mDomains.add(Constants.getPrefixedString(payload, 1, StandardCharsets.ISO_8859_1));
+        }
+    }
+
+    public List<String> getDomains() {
+        return Collections.unmodifiableList(mDomains);
+    }
+
+    @Override
+    public String toString() {
+        return "DomainName{" +
+                "mDomains=" + mDomains +
+                '}';
+    }
+}
diff --git a/service/java/com/android/server/wifi/anqp/EmergencyNumberElement.java b/service/java/com/android/server/wifi/anqp/EmergencyNumberElement.java
new file mode 100644
index 0000000..6954480
--- /dev/null
+++ b/service/java/com/android/server/wifi/anqp/EmergencyNumberElement.java
@@ -0,0 +1,36 @@
+package com.android.server.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The Emergency Number ANQP Element, IEEE802.11-2012 section 8.4.4.5
+ */
+public class EmergencyNumberElement extends ANQPElement {
+    private final List<String> mNumbers;
+
+    public EmergencyNumberElement(Constants.ANQPElementType infoID, ByteBuffer payload)
+            throws ProtocolException {
+        super(infoID);
+
+        mNumbers = new ArrayList<String>();
+
+        while (payload.hasRemaining()) {
+            mNumbers.add(Constants.getPrefixedString(payload, 1, StandardCharsets.UTF_8));
+        }
+    }
+
+    public List<String> getNumbers() {
+        return mNumbers;
+    }
+
+    @Override
+    public String toString() {
+        return "EmergencyNumber{" +
+                "mNumbers=" + mNumbers +
+                '}';
+    }
+}
diff --git a/service/java/com/android/server/wifi/anqp/GEOLocationElement.java b/service/java/com/android/server/wifi/anqp/GEOLocationElement.java
new file mode 100644
index 0000000..d434c73
--- /dev/null
+++ b/service/java/com/android/server/wifi/anqp/GEOLocationElement.java
@@ -0,0 +1,311 @@
+package com.android.server.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+
+/**
+ * Holds an AP Geospatial Location ANQP Element, as specified in IEEE802.11-2012 section
+ * 8.4.4.12.
+ * <p/>
+ * <p>
+ * Section 8.4.2.24.10 of the IEEE802.11-2012 specification refers to RFC-3825 for the format of the
+ * Geospatial location information. RFC-3825 has subsequently been obsoleted by RFC-6225 which
+ * defines the same basic binary format for the DHCPv4 payload except that a few unused bits of the
+ * Datum field have been reserved for other uses.
+ * </p>
+ * <p/>
+ * <p>
+ * RFC-3825 defines a resolution field for each of latitude, longitude and altitude as "the number
+ * of significant bits" of precision in the respective values and implies through examples and
+ * otherwise that the non-significant bits should be simply disregarded and the range of values are
+ * calculated as the numeric interval obtained by varying the range of "insignificant bits" between
+ * its extremes. As a simple example, consider the value 33 as a simple 8-bit number with three
+ * significant bits: 33 is 00100001 binary and the leading 001 are the significant bits. With the
+ * above definition, the range of numbers are [32,63] with 33 asymmetrically located at the low end
+ * of the interval. In a more realistic setting an instrument, such as a GPS, would most likely
+ * deliver measurements with a gaussian distribution around the exact value, meaning it is more
+ * reasonable to assume the value as a "center" value with a symmetric uncertainty interval.
+ * RFC-6225 redefines the "resolution" from RFC-3825 with an "uncertainty" value with these
+ * properties, which is also the definition suggested here.
+ * </p>
+ * <p/>
+ * <p>
+ * The res fields provides the resolution as the exponent to a power of two,
+ * e.g. 8 means 2^8 = +/- 256, 0 means 2^0 = +/- 1 and -7 means 2^-7 +/- 0.00781250.
+ * Unknown resolution is indicated by not setting the respective resolution field in the RealValue.
+ * </p>
+ */
+public class GEOLocationElement extends ANQPElement {
+    public enum AltitudeType {Unknown, Meters, Floors}
+
+    public enum Datum {Unknown, WGS84, NAD83Land, NAD83Water}
+
+    private static final int ELEMENT_ID = 123;       // ???
+    private static final int GEO_LOCATION_LENGTH = 16;
+
+    private static final int LL_FRACTION_SIZE = 25;
+    private static final int LL_WIDTH = 34;
+    private static final int ALT_FRACTION_SIZE = 8;
+    private static final int ALT_WIDTH = 30;
+    private static final int RES_WIDTH = 6;
+    private static final int ALT_TYPE_WIDTH = 4;
+    private static final int DATUM_WIDTH = 8;
+
+    private final RealValue mLatitude;
+    private final RealValue mLongitude;
+    private final RealValue mAltitude;
+    private final AltitudeType mAltitudeType;
+    private final Datum mDatum;
+
+    public static class RealValue {
+        private final double mValue;
+        private final boolean mResolutionSet;
+        private final int mResolution;
+
+        public RealValue(double value) {
+            mValue = value;
+            mResolution = Integer.MIN_VALUE;
+            mResolutionSet = false;
+        }
+
+        public RealValue(double value, int resolution) {
+            mValue = value;
+            mResolution = resolution;
+            mResolutionSet = true;
+        }
+
+        public double getValue() {
+            return mValue;
+        }
+
+        public boolean isResolutionSet() {
+            return mResolutionSet;
+        }
+
+        public int getResolution() {
+            return mResolution;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append(String.format("%f", mValue));
+            if (mResolutionSet) {
+                sb.append("+/-2^").append(mResolution);
+            }
+            return sb.toString();
+        }
+    }
+
+    public GEOLocationElement(Constants.ANQPElementType infoID, ByteBuffer payload)
+            throws ProtocolException {
+        super(infoID);
+
+        payload.get();
+        int locLength = payload.get() & Constants.BYTE_MASK;
+
+        if (locLength != GEO_LOCATION_LENGTH) {
+            throw new ProtocolException("GeoLocation length field value " + locLength +
+                    " incorrect, expected 16");
+        }
+        if (payload.remaining() != GEO_LOCATION_LENGTH) {
+            throw new ProtocolException("Bad buffer length " + payload.remaining() +
+                    ", expected 16");
+        }
+
+        ReverseBitStream reverseBitStream = new ReverseBitStream(payload);
+
+        int rawLatRes = (int) reverseBitStream.sliceOff(RES_WIDTH);
+        double latitude =
+                fixToFloat(reverseBitStream.sliceOff(LL_WIDTH), LL_FRACTION_SIZE, LL_WIDTH);
+
+        mLatitude = rawLatRes != 0 ?
+                new RealValue(latitude, bitsToAbsResolution(rawLatRes, LL_WIDTH,
+                        LL_FRACTION_SIZE)) :
+                new RealValue(latitude);
+
+        int rawLonRes = (int) reverseBitStream.sliceOff(RES_WIDTH);
+        double longitude =
+                fixToFloat(reverseBitStream.sliceOff(LL_WIDTH), LL_FRACTION_SIZE, LL_WIDTH);
+
+        mLongitude = rawLonRes != 0 ?
+                new RealValue(longitude, bitsToAbsResolution(rawLonRes, LL_WIDTH,
+                        LL_FRACTION_SIZE)) :
+                new RealValue(longitude);
+
+        int altType = (int) reverseBitStream.sliceOff(ALT_TYPE_WIDTH);
+        mAltitudeType = altType < AltitudeType.values().length ?
+                AltitudeType.values()[altType] :
+                AltitudeType.Unknown;
+
+        int rawAltRes = (int) reverseBitStream.sliceOff(RES_WIDTH);
+        double altitude = fixToFloat(reverseBitStream.sliceOff(ALT_WIDTH), ALT_FRACTION_SIZE,
+                ALT_WIDTH);
+
+        mAltitude = rawAltRes != 0 ?
+                new RealValue(altitude, bitsToAbsResolution(rawAltRes, ALT_WIDTH,
+                        ALT_FRACTION_SIZE)) :
+                new RealValue(altitude);
+
+        int datumValue = (int) reverseBitStream.sliceOff(DATUM_WIDTH);
+        mDatum = datumValue < Datum.values().length ? Datum.values()[datumValue] : Datum.Unknown;
+    }
+
+    public RealValue getLatitude() {
+        return mLatitude;
+    }
+
+    public RealValue getLongitude() {
+        return mLongitude;
+    }
+
+    public RealValue getAltitude() {
+        return mAltitude;
+    }
+
+    public AltitudeType getAltitudeType() {
+        return mAltitudeType;
+    }
+
+    public Datum getDatum() {
+        return mDatum;
+    }
+
+    @Override
+    public String toString() {
+        return "GEOLocation{" +
+                "mLatitude=" + mLatitude +
+                ", mLongitude=" + mLongitude +
+                ", mAltitude=" + mAltitude +
+                ", mAltitudeType=" + mAltitudeType +
+                ", mDatum=" + mDatum +
+                '}';
+    }
+
+    private static class ReverseBitStream {
+
+        private final byte[] mOctets;
+        private int mBitoffset;
+
+        private ReverseBitStream(ByteBuffer octets) {
+            mOctets = new byte[octets.remaining()];
+            octets.get(mOctets);
+        }
+
+        private long sliceOff(int bits) {
+            final int bn = mBitoffset + bits;
+            int remaining = bits;
+            long value = 0;
+
+            while (mBitoffset < bn) {
+                int sbit = mBitoffset & 0x7;        // Bit #0 is MSB, inclusive
+                int octet = mBitoffset >>> 3;
+
+                // Copy the minimum of what's to the right of sbit
+                // and how much more goes to the target
+                int width = Math.min(Byte.SIZE - sbit, remaining);
+
+                value = (value << width) | getBits(mOctets[octet], sbit, width);
+
+                mBitoffset += width;
+                remaining -= width;
+            }
+
+            return value;
+        }
+
+        private static int getBits(byte b, int b0, int width) {
+            int mask = (1 << width) - 1;
+            return (b >> (Byte.SIZE - b0 - width)) & mask;
+        }
+    }
+
+    private static class BitStream {
+
+        private final byte[] data;
+        private int bitOffset;              // bit 0 is MSB of data[0]
+
+        private BitStream(int octets) {
+            data = new byte[octets];
+        }
+
+        private void append(long value, int width) {
+            System.out.printf("Appending %x:%d\n", value, width);
+            for (int sbit = width - 1; sbit >= 0; ) {
+                int b0 = bitOffset >>> 3;
+                int dbit = bitOffset & 0x7;
+
+                int shr = sbit - 7 + dbit;
+                int dmask = 0xff >>> dbit;
+
+                if (shr >= 0) {
+                    data[b0] = (byte) ((data[b0] & ~dmask) | ((value >>> shr) & dmask));
+                    bitOffset += Byte.SIZE - dbit;
+                    sbit -= Byte.SIZE - dbit;
+                } else {
+                    data[b0] = (byte) ((data[b0] & ~dmask) | ((value << -shr) & dmask));
+                    bitOffset += sbit + 1;
+                    sbit = -1;
+                }
+            }
+        }
+
+        private byte[] getOctets() {
+            return data;
+        }
+    }
+
+    static double fixToFloat(long value, int fractionSize, int width) {
+        long sign = 1L << (width - 1);
+        if ((value & sign) != 0) {
+            value = -value;
+            return -(double) (value & (sign - 1)) / (double) (1L << fractionSize);
+        } else {
+            return (double) (value & (sign - 1)) / (double) (1L << fractionSize);
+        }
+    }
+
+    private static long floatToFix(double value, int fractionSize, int width) {
+        return Math.round(value * (1L << fractionSize)) & ((1L << width) - 1);
+    }
+
+    private static final double LOG2_FACTOR = 1.0 / Math.log(2.0);
+
+    /**
+     * Convert an absolute variance value into absolute resolution representation,
+     * where the variance = 2^resolution.
+     *
+     * @param variance The absolute variance
+     * @return the absolute resolution.
+     */
+    private static int getResolution(double variance) {
+        return (int) Math.ceil(Math.log(variance) * LOG2_FACTOR);
+    }
+
+    /**
+     * Convert an absolute resolution, into the "number of significant bits" for the given fixed
+     * point notation as defined in RFC-3825 and refined in RFC-6225.
+     *
+     * @param resolution   absolute resolution given as 2^resolution.
+     * @param fieldWidth   Full width of the fixed point number used to represent the value.
+     * @param fractionBits Number of fraction bits in the fixed point number used to represent the
+     *                     value.
+     * @return The number of "significant bits".
+     */
+    private static int absResolutionToBits(int resolution, int fieldWidth, int fractionBits) {
+        return fieldWidth - fractionBits - 1 - resolution;
+    }
+
+    /**
+     * Convert the protocol definition of "number of significant bits" into an absolute resolution.
+     *
+     * @param bits         The number of "significant bits" from the binary protocol.
+     * @param fieldWidth   Full width of the fixed point number used to represent the value.
+     * @param fractionBits Number of fraction bits in the fixed point number used to represent the
+     *                     value.
+     * @return The absolute resolution given as 2^resolution.
+     */
+    private static int bitsToAbsResolution(long bits, int fieldWidth, int fractionBits) {
+        return fieldWidth - fractionBits - 1 - (int) bits;
+    }
+}
diff --git a/service/java/com/android/server/wifi/anqp/GenericBlobElement.java b/service/java/com/android/server/wifi/anqp/GenericBlobElement.java
new file mode 100644
index 0000000..eff130d
--- /dev/null
+++ b/service/java/com/android/server/wifi/anqp/GenericBlobElement.java
@@ -0,0 +1,27 @@
+package com.android.server.wifi.anqp;
+
+import com.android.server.wifi.hotspot2.Utils;
+
+import java.nio.ByteBuffer;
+
+/**
+ * ANQP Element to hold a raw, unparsed, octet blob
+ */
+public class GenericBlobElement extends ANQPElement {
+    private final byte[] mData;
+
+    public GenericBlobElement(Constants.ANQPElementType infoID, ByteBuffer payload) {
+        super(infoID);
+        mData = new byte[payload.remaining()];
+        payload.get(mData);
+    }
+
+    public byte[] getData() {
+        return mData;
+    }
+
+    @Override
+    public String toString() {
+        return "Element ID " + getID() + ": " + Utils.toHexString(mData);
+    }
+}
diff --git a/service/java/com/android/server/wifi/anqp/GenericStringElement.java b/service/java/com/android/server/wifi/anqp/GenericStringElement.java
new file mode 100644
index 0000000..6cf7b1a
--- /dev/null
+++ b/service/java/com/android/server/wifi/anqp/GenericStringElement.java
@@ -0,0 +1,26 @@
+package com.android.server.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * ANQP Element to hold a generic (UTF-8 decoded) character string
+ */
+public class GenericStringElement extends ANQPElement {
+    private final String mText;
+
+    public GenericStringElement(Constants.ANQPElementType infoID, ByteBuffer payload) throws ProtocolException {
+        super(infoID);
+        mText = Constants.getString(payload, payload.remaining(), StandardCharsets.UTF_8);
+    }
+
+    public String getM_text() {
+        return mText;
+    }
+
+    @Override
+    public String toString() {
+        return "Element ID " + getID() + ": '" + mText + "'";
+    }
+}
diff --git a/service/java/com/android/server/wifi/anqp/HSCapabilityListElement.java b/service/java/com/android/server/wifi/anqp/HSCapabilityListElement.java
new file mode 100644
index 0000000..8269324
--- /dev/null
+++ b/service/java/com/android/server/wifi/anqp/HSCapabilityListElement.java
@@ -0,0 +1,42 @@
+package com.android.server.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+/**
+ * The HS Capability list vendor specific ANQP Element,
+ * Wi-Fi Alliance Hotspot 2.0 (Release 2) Technical Specification - Version 5.00,
+ * section 4.2
+ */
+public class HSCapabilityListElement extends ANQPElement {
+    private final Constants.ANQPElementType[] mCapabilities;
+
+    public HSCapabilityListElement(Constants.ANQPElementType infoID, ByteBuffer payload)
+            throws ProtocolException {
+        super(infoID);
+
+        mCapabilities = new Constants.ANQPElementType[payload.remaining()];
+
+        int index = 0;
+        while (payload.hasRemaining()) {
+            int capID = payload.get() & Constants.BYTE_MASK;
+            Constants.ANQPElementType capability = Constants.mapHS20Element(capID);
+            if (capability == null) {
+                throw new ProtocolException("Unknown capability: " + capID);
+            }
+            mCapabilities[index++] = capability;
+        }
+    }
+
+    public Constants.ANQPElementType[] getCapabilities() {
+        return mCapabilities;
+    }
+
+    @Override
+    public String toString() {
+        return "HSCapabilityList{" +
+                "mCapabilities=" + Arrays.toString(mCapabilities) +
+                '}';
+    }
+}
diff --git a/service/java/com/android/server/wifi/anqp/HSConnectionCapabilityElement.java b/service/java/com/android/server/wifi/anqp/HSConnectionCapabilityElement.java
new file mode 100644
index 0000000..53f1051
--- /dev/null
+++ b/service/java/com/android/server/wifi/anqp/HSConnectionCapabilityElement.java
@@ -0,0 +1,79 @@
+package com.android.server.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * The Connection Capability vendor specific ANQP Element,
+ * Wi-Fi Alliance Hotspot 2.0 (Release 2) Technical Specification - Version 5.00,
+ * section 4.5
+ */
+public class HSConnectionCapabilityElement extends ANQPElement {
+
+    public enum ProtoStatus {Closed, Open, Unknown}
+
+    private final List<ProtocolTuple> mStatusList;
+
+    public static class ProtocolTuple {
+        private final int mProtocol;
+        private final int mPort;
+        private final ProtoStatus mStatus;
+
+        private ProtocolTuple(ByteBuffer payload) throws ProtocolException {
+            if (payload.remaining() < 4) {
+                throw new ProtocolException("Runt protocol tuple: " + payload.remaining());
+            }
+            mProtocol = payload.get() & Constants.BYTE_MASK;
+            mPort = payload.getShort() & Constants.SHORT_MASK;
+            int statusNumber = payload.get() & Constants.BYTE_MASK;
+            mStatus = statusNumber < ProtoStatus.values().length ?
+                    ProtoStatus.values()[statusNumber] :
+                    null;
+        }
+
+        public int getProtocol() {
+            return mProtocol;
+        }
+
+        public int getPort() {
+            return mPort;
+        }
+
+        public ProtoStatus getStatus() {
+            return mStatus;
+        }
+
+        @Override
+        public String toString() {
+            return "ProtocolTuple{" +
+                    "mProtocol=" + mProtocol +
+                    ", mPort=" + mPort +
+                    ", mStatus=" + mStatus +
+                    '}';
+        }
+    }
+
+    public HSConnectionCapabilityElement(Constants.ANQPElementType infoID, ByteBuffer payload)
+            throws ProtocolException {
+        super(infoID);
+
+        mStatusList = new ArrayList<>();
+        while (payload.hasRemaining()) {
+            mStatusList.add(new ProtocolTuple(payload));
+        }
+    }
+
+    public List<ProtocolTuple> getStatusList() {
+        return Collections.unmodifiableList(mStatusList);
+    }
+
+    @Override
+    public String toString() {
+        return "HSConnectionCapability{" +
+                "mStatusList=" + mStatusList +
+                '}';
+    }
+}
diff --git a/service/java/com/android/server/wifi/anqp/HSFriendlyNameElement.java b/service/java/com/android/server/wifi/anqp/HSFriendlyNameElement.java
new file mode 100644
index 0000000..a15fc29
--- /dev/null
+++ b/service/java/com/android/server/wifi/anqp/HSFriendlyNameElement.java
@@ -0,0 +1,38 @@
+package com.android.server.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * The Operator Friendly Name vendor specific ANQP Element,
+ * Wi-Fi Alliance Hotspot 2.0 (Release 2) Technical Specification - Version 5.00,
+ * section 4.3
+ */
+public class HSFriendlyNameElement extends ANQPElement {
+    private final List<I18Name> mNames;
+
+    public HSFriendlyNameElement(Constants.ANQPElementType infoID, ByteBuffer payload)
+            throws ProtocolException {
+        super(infoID);
+
+        mNames = new ArrayList<I18Name>();
+
+        while (payload.hasRemaining()) {
+            mNames.add(new I18Name(payload));
+        }
+    }
+
+    public List<I18Name> getNames() {
+        return Collections.unmodifiableList(mNames);
+    }
+
+    @Override
+    public String toString() {
+        return "HSFriendlyName{" +
+                "mNames=" + mNames +
+                '}';
+    }
+}
diff --git a/service/java/com/android/server/wifi/anqp/HSIconFileElement.java b/service/java/com/android/server/wifi/anqp/HSIconFileElement.java
new file mode 100644
index 0000000..8a7740d
--- /dev/null
+++ b/service/java/com/android/server/wifi/anqp/HSIconFileElement.java
@@ -0,0 +1,59 @@
+package com.android.server.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+import static com.android.server.wifi.anqp.Constants.BYTE_MASK;
+import static com.android.server.wifi.anqp.Constants.SHORT_MASK;
+
+/**
+ * The Icon Binary File vendor specific ANQP Element,
+ * Wi-Fi Alliance Hotspot 2.0 (Release 2) Technical Specification - Version 5.00,
+ * section 4.11
+ */
+public class HSIconFileElement extends ANQPElement {
+
+    public enum StatusCode {Success, FileNotFound, Unspecified}
+
+    private final StatusCode mStatusCode;
+    private final String mType;
+    private final byte[] mIconData;
+
+    public HSIconFileElement(Constants.ANQPElementType infoID, ByteBuffer payload)
+            throws ProtocolException {
+        super(infoID);
+
+        if (payload.remaining() < 4) {
+            throw new ProtocolException("Truncated icon file: " + payload.remaining());
+        }
+
+        int statusID = payload.get() & BYTE_MASK;
+        mStatusCode = statusID < StatusCode.values().length ? StatusCode.values()[statusID] : null;
+        mType = Constants.getPrefixedString(payload, 1, StandardCharsets.US_ASCII);
+
+        int dataLength = payload.getShort() & SHORT_MASK;
+        mIconData = new byte[dataLength];
+        payload.get(mIconData);
+    }
+
+    public StatusCode getStatusCode() {
+        return mStatusCode;
+    }
+
+    public String getType() {
+        return mType;
+    }
+
+    public byte[] getIconData() {
+        return mIconData;
+    }
+
+    @Override
+    public String toString() {
+        return "HSIconFile{" +
+                "mStatusCode=" + mStatusCode +
+                ", mType='" + mType + '\'' +
+                ", mIconData=" + mIconData.length + " bytes }";
+    }
+}
diff --git a/service/java/com/android/server/wifi/anqp/HSOsuProvidersElement.java b/service/java/com/android/server/wifi/anqp/HSOsuProvidersElement.java
new file mode 100644
index 0000000..15ed8c6
--- /dev/null
+++ b/service/java/com/android/server/wifi/anqp/HSOsuProvidersElement.java
@@ -0,0 +1,49 @@
+package com.android.server.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * The OSU Providers List vendor specific ANQP Element,
+ * Wi-Fi Alliance Hotspot 2.0 (Release 2) Technical Specification - Version 5.00,
+ * section 4.8
+ */
+public class HSOsuProvidersElement extends ANQPElement {
+    private final String mSSID;
+    private final List<OSUProvider> mProviders;
+
+    public HSOsuProvidersElement(Constants.ANQPElementType infoID, ByteBuffer payload)
+            throws ProtocolException {
+        super(infoID);
+
+        mSSID = Constants.getPrefixedString(payload, 1, StandardCharsets.UTF_8);
+        int providerCount = payload.get() & Constants.BYTE_MASK;
+
+        mProviders = new ArrayList<OSUProvider>(providerCount);
+
+        while (providerCount > 0) {
+            mProviders.add(new OSUProvider(payload));
+            providerCount--;
+        }
+    }
+
+    public String getSSID() {
+        return mSSID;
+    }
+
+    public List<OSUProvider> getProviders() {
+        return Collections.unmodifiableList(mProviders);
+    }
+
+    @Override
+    public String toString() {
+        return "HSOsuProviders{" +
+                "mSSID='" + mSSID + '\'' +
+                ", mProviders=" + mProviders +
+                '}';
+    }
+}
diff --git a/service/java/com/android/server/wifi/anqp/HSWanMetricsElement.java b/service/java/com/android/server/wifi/anqp/HSWanMetricsElement.java
new file mode 100644
index 0000000..a1e1ca9
--- /dev/null
+++ b/service/java/com/android/server/wifi/anqp/HSWanMetricsElement.java
@@ -0,0 +1,89 @@
+package com.android.server.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+
+import static com.android.server.wifi.anqp.Constants.BYTE_MASK;
+import static com.android.server.wifi.anqp.Constants.INT_MASK;
+import static com.android.server.wifi.anqp.Constants.SHORT_MASK;
+
+/**
+ * The WAN Metrics vendor specific ANQP Element,
+ * Wi-Fi Alliance Hotspot 2.0 (Release 2) Technical Specification - Version 5.00,
+ * section 4.4
+ */
+public class HSWanMetricsElement extends ANQPElement {
+
+    public enum LinkStatus {Reserved, Up, Down, Test}
+
+    private final LinkStatus mStatus;
+    private final boolean mSymmetric;
+    private final boolean mCapped;
+    private final long mDlSpeed;
+    private final long mUlSpeed;
+    private final int mDlLoad;
+    private final int mUlLoad;
+    private final int mLMD;
+
+    public HSWanMetricsElement(Constants.ANQPElementType infoID, ByteBuffer payload)
+            throws ProtocolException {
+        super(infoID);
+
+        if (payload.remaining() != 13) {
+            throw new ProtocolException("Bad WAN metrics length: " + payload.remaining());
+        }
+
+        int status = payload.get() & BYTE_MASK;
+        mStatus = LinkStatus.values()[status & 0x03];
+        mSymmetric = (status & 0x04) != 0;
+        mCapped = (status & 0x08) != 0;
+        mDlSpeed = payload.getInt() & INT_MASK;
+        mUlSpeed = payload.getInt() & INT_MASK;
+        mDlLoad = payload.get() & BYTE_MASK;
+        mUlLoad = payload.get() & BYTE_MASK;
+        mLMD = payload.getShort() & SHORT_MASK;
+    }
+
+    public LinkStatus getStatus() {
+        return mStatus;
+    }
+
+    public boolean isSymmetric() {
+        return mSymmetric;
+    }
+
+    public boolean isCapped() {
+        return mCapped;
+    }
+
+    public long getDlSpeed() {
+        return mDlSpeed;
+    }
+
+    public long getUlSpeed() {
+        return mUlSpeed;
+    }
+
+    public int getDlLoad() {
+        return mDlLoad;
+    }
+
+    public int getUlLoad() {
+        return mUlLoad;
+    }
+
+    public int getLMD() {
+        return mLMD;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("HSWanMetrics{mStatus=%s, mSymmetric=%s, mCapped=%s, " +
+                "mDlSpeed=%d, mUlSpeed=%d, mDlLoad=%f, mUlLoad=%f, mLMD=%d}",
+                mStatus, mSymmetric, mCapped,
+                mDlSpeed, mUlSpeed,
+                (double)mDlLoad * 100.0 / 256.0,
+                (double)mUlLoad * 100.0 / 256.0,
+                mLMD);
+    }
+}
diff --git a/service/java/com/android/server/wifi/anqp/I18Name.java b/service/java/com/android/server/wifi/anqp/I18Name.java
new file mode 100644
index 0000000..1def6cf
--- /dev/null
+++ b/service/java/com/android/server/wifi/anqp/I18Name.java
@@ -0,0 +1,45 @@
+package com.android.server.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Locale;
+
+import static com.android.server.wifi.anqp.Constants.BYTE_MASK;
+
+/**
+ * A generic Internationalized name used in ANQP elements as specified in 802.11-2012 and
+ * "Wi-Fi Alliance Hotspot 2.0 (Release 2) Technical Specification - Version 5.00"
+ */
+public class I18Name {
+    private static final int LANG_CODE_LENGTH = 3;
+
+    private final Locale mLocale;
+    private final String mText;
+
+    public I18Name(ByteBuffer payload) throws ProtocolException {
+        if (payload.remaining() < 4) {
+            throw new ProtocolException("Truncated I18Name: " + payload.remaining());
+        }
+        int nameLength = payload.get() & BYTE_MASK;
+        if (nameLength < 3) {
+            throw new ProtocolException("Runt I18Name: " + nameLength);
+        }
+        String language = Constants.getString(payload, LANG_CODE_LENGTH, StandardCharsets.US_ASCII);
+        mLocale = Locale.forLanguageTag(language);
+        mText = Constants.getString(payload, nameLength - LANG_CODE_LENGTH, StandardCharsets.UTF_8);
+    }
+
+    public Locale getLocale() {
+        return mLocale;
+    }
+
+    public String getText() {
+        return mText;
+    }
+
+    @Override
+    public String toString() {
+        return mText + ':' + mLocale.getLanguage();
+    }
+}
diff --git a/service/java/com/android/server/wifi/anqp/IPAddressTypeAvailabilityElement.java b/service/java/com/android/server/wifi/anqp/IPAddressTypeAvailabilityElement.java
new file mode 100644
index 0000000..8d71a3b
--- /dev/null
+++ b/service/java/com/android/server/wifi/anqp/IPAddressTypeAvailabilityElement.java
@@ -0,0 +1,52 @@
+package com.android.server.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+
+/**
+ * The IP Address Type availability ANQP Element, IEEE802.11-2012 section 8.4.4.9
+ */
+public class IPAddressTypeAvailabilityElement extends ANQPElement {
+    public enum IPv4Availability {
+        NotAvailable, Public, PortRestricted, SingleNAT, DoubleNAT,
+        PortRestrictedAndSingleNAT, PortRestrictedAndDoubleNAT, Unknown
+    }
+
+    public enum IPv6Availability {NotAvailable, Available, Unknown, Reserved}
+
+    private final IPv4Availability mV4Availability;
+    private final IPv6Availability mV6Availability;
+
+    public IPAddressTypeAvailabilityElement(Constants.ANQPElementType infoID, ByteBuffer payload)
+            throws ProtocolException {
+        super(infoID);
+
+        if (payload.remaining() != 1)
+            throw new ProtocolException("Bad IP Address Type Availability length: " +
+                    payload.remaining());
+
+        int ipField = payload.get();
+        mV6Availability = IPv6Availability.values()[ipField & 0x3];
+
+        ipField = (ipField >> 2) & 0x3f;
+        mV4Availability = ipField < IPv4Availability.values().length ?
+                IPv4Availability.values()[ipField] :
+                IPv4Availability.Unknown;
+    }
+
+    public IPv4Availability getV4Availability() {
+        return mV4Availability;
+    }
+
+    public IPv6Availability getV6Availability() {
+        return mV6Availability;
+    }
+
+    @Override
+    public String toString() {
+        return "IPAddressTypeAvailability{" +
+                "mV4Availability=" + mV4Availability +
+                ", mV6Availability=" + mV6Availability +
+                '}';
+    }
+}
diff --git a/service/java/com/android/server/wifi/anqp/IconInfo.java b/service/java/com/android/server/wifi/anqp/IconInfo.java
new file mode 100644
index 0000000..6cc72d7
--- /dev/null
+++ b/service/java/com/android/server/wifi/anqp/IconInfo.java
@@ -0,0 +1,64 @@
+package com.android.server.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Locale;
+
+import static com.android.server.wifi.anqp.Constants.SHORT_MASK;
+
+/**
+ * The Icons available OSU Providers sub field, as specified in
+ * Wi-Fi Alliance Hotspot 2.0 (Release 2) Technical Specification - Version 5.00,
+ * section 4.8.1.4
+ */
+public class IconInfo {
+    private final int mWidth;
+    private final int mHeight;
+    private final Locale mLocale;
+    private final String mIconType;
+    private final String mFileName;
+
+    public IconInfo(ByteBuffer payload) throws ProtocolException {
+        if (payload.remaining() < 9) {
+            throw new ProtocolException("Truncated icon meta data");
+        }
+
+        mWidth = payload.getShort() & SHORT_MASK;
+        mHeight = payload.getShort() & SHORT_MASK;
+        mLocale = Locale.forLanguageTag(Constants.getString(payload, 3, StandardCharsets.US_ASCII));
+        mIconType = Constants.getPrefixedString(payload, 1, StandardCharsets.US_ASCII);
+        mFileName = Constants.getPrefixedString(payload, 1, StandardCharsets.UTF_8);
+    }
+
+    public int getWidth() {
+        return mWidth;
+    }
+
+    public int getHeight() {
+        return mHeight;
+    }
+
+    public Locale getLocale() {
+        return mLocale;
+    }
+
+    public String getIconType() {
+        return mIconType;
+    }
+
+    public String getFileName() {
+        return mFileName;
+    }
+
+    @Override
+    public String toString() {
+        return "IconInfo{" +
+                "mWidth=" + mWidth +
+                ", mHeight=" + mHeight +
+                ", mLocale=" + mLocale +
+                ", mIconType='" + mIconType + '\'' +
+                ", mFileName='" + mFileName + '\'' +
+                '}';
+    }
+}
diff --git a/service/java/com/android/server/wifi/anqp/NAIRealmData.java b/service/java/com/android/server/wifi/anqp/NAIRealmData.java
new file mode 100644
index 0000000..36323f7
--- /dev/null
+++ b/service/java/com/android/server/wifi/anqp/NAIRealmData.java
@@ -0,0 +1,108 @@
+package com.android.server.wifi.anqp;
+
+import com.android.server.wifi.anqp.eap.EAPMethod;
+import com.android.server.wifi.hotspot2.AuthMatch;
+import com.android.server.wifi.hotspot2.Utils;
+import com.android.server.wifi.hotspot2.pps.Credential;
+import com.android.server.wifi.hotspot2.pps.DomainMatcher;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * The NAI Realm Data ANQP sub-element, IEEE802.11-2012 section 8.4.4.10 figure 8-418
+ */
+public class NAIRealmData {
+    private final List<String> mRealms;
+    private final List<EAPMethod> mEAPMethods;
+
+    public NAIRealmData(ByteBuffer payload) throws ProtocolException {
+        if (payload.remaining() < 5) {
+            throw new ProtocolException("Runt payload: " + payload.remaining());
+        }
+
+        int length = payload.getShort() & Constants.SHORT_MASK;
+        if (length > payload.remaining()) {
+            throw new ProtocolException("Invalid data length: " + length);
+        }
+        boolean utf8 = (payload.get() & 1) == Constants.UTF8_INDICATOR;
+
+        String realm = Constants.getPrefixedString(payload, 1, utf8 ?
+                StandardCharsets.UTF_8 :
+                StandardCharsets.US_ASCII);
+        String[] realms = realm.split(";");
+        mRealms = new ArrayList<>();
+        for (String realmElement : realms) {
+            if (realmElement.length() > 0) {
+                mRealms.add(realmElement);
+            }
+        }
+
+        int methodCount = payload.get() & Constants.BYTE_MASK;
+        mEAPMethods = new ArrayList<>(methodCount);
+        while (methodCount > 0) {
+            mEAPMethods.add(new EAPMethod(payload));
+            methodCount--;
+        }
+    }
+
+    public List<String> getRealms() {
+        return Collections.unmodifiableList(mRealms);
+    }
+
+    public List<EAPMethod> getEAPMethods() {
+        return Collections.unmodifiableList(mEAPMethods);
+    }
+
+    public int match(List<String> credLabels, Credential credential) {
+        int realmMatch = AuthMatch.None;
+        if (!mRealms.isEmpty()) {
+            for (String realm : mRealms) {
+                List<String> labels = Utils.splitDomain(realm);
+                if (DomainMatcher.arg2SubdomainOfArg1(credLabels, labels)) {
+                    realmMatch = AuthMatch.Realm;
+                    break;
+                }
+            }
+            if (realmMatch == AuthMatch.None || mEAPMethods.isEmpty()) {
+                return realmMatch;
+            }
+            // else there is a realm match and one or more EAP methods - check them.
+        }
+        else if (mEAPMethods.isEmpty()) {
+            return AuthMatch.Indeterminate;
+        }
+
+        int best = AuthMatch.None;
+        for (EAPMethod eapMethod : mEAPMethods) {
+            int match = eapMethod.match(credential) | realmMatch;
+            if (match > best) {
+                best = match;
+                if (best == AuthMatch.Exact) {
+                    return best;
+                }
+            }
+        }
+        return best;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append("  NAI Realm(s)");
+        for (String realm : mRealms) {
+            sb.append(' ').append(realm);
+        }
+        sb.append('\n');
+
+        for (EAPMethod eapMethod : mEAPMethods) {
+            sb.append( "    " ).append(eapMethod.toString());
+        }
+        return sb.toString();
+    }
+}
diff --git a/service/java/com/android/server/wifi/anqp/NAIRealmElement.java b/service/java/com/android/server/wifi/anqp/NAIRealmElement.java
new file mode 100644
index 0000000..c21dbee
--- /dev/null
+++ b/service/java/com/android/server/wifi/anqp/NAIRealmElement.java
@@ -0,0 +1,76 @@
+package com.android.server.wifi.anqp;
+
+import com.android.server.wifi.SIMAccessor;
+import com.android.server.wifi.hotspot2.AuthMatch;
+import com.android.server.wifi.hotspot2.Utils;
+import com.android.server.wifi.hotspot2.pps.Credential;
+import com.android.server.wifi.hotspot2.pps.HomeSP;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static com.android.server.wifi.anqp.Constants.BYTES_IN_SHORT;
+import static com.android.server.wifi.anqp.Constants.SHORT_MASK;
+
+/**
+ * The NAI Realm ANQP Element, IEEE802.11-2012 section 8.4.4.10
+ */
+public class NAIRealmElement extends ANQPElement {
+    private final List<NAIRealmData> mRealmData;
+
+    public NAIRealmElement(Constants.ANQPElementType infoID, ByteBuffer payload)
+            throws ProtocolException {
+        super(infoID);
+
+        if (!payload.hasRemaining()) {
+            mRealmData = Collections.emptyList();
+            return;
+        }
+
+        if (payload.remaining() < BYTES_IN_SHORT) {
+            throw new ProtocolException("Runt NAI Realm: " + payload.remaining());
+        }
+
+        int count = payload.getShort() & SHORT_MASK;
+        mRealmData = new ArrayList<>(count);
+        while (count > 0) {
+            mRealmData.add(new NAIRealmData(payload));
+            count--;
+        }
+    }
+
+    public List<NAIRealmData> getRealmData() {
+        return Collections.unmodifiableList(mRealmData);
+    }
+
+    public int match(Credential credential) {
+        if (mRealmData.isEmpty())
+            return AuthMatch.Indeterminate;
+
+        List<String> credLabels = Utils.splitDomain(credential.getRealm());
+        int best = AuthMatch.None;
+        for (NAIRealmData realmData : mRealmData) {
+            int match = realmData.match(credLabels, credential);
+            if (match > best) {
+                best = match;
+                if (best == AuthMatch.Exact) {
+                    return best;
+                }
+            }
+        }
+        return best;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("NAI Realm:\n");
+        for (NAIRealmData data : mRealmData) {
+            sb.append(data);
+        }
+        return sb.toString();
+    }
+}
diff --git a/service/java/com/android/server/wifi/anqp/NetworkAuthenticationTypeElement.java b/service/java/com/android/server/wifi/anqp/NetworkAuthenticationTypeElement.java
new file mode 100644
index 0000000..b496e3e
--- /dev/null
+++ b/service/java/com/android/server/wifi/anqp/NetworkAuthenticationTypeElement.java
@@ -0,0 +1,82 @@
+package com.android.server.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static com.android.server.wifi.anqp.Constants.BYTE_MASK;
+
+/**
+ * The Network Authentication Type ANQP Element, IEEE802.11-2012 section 8.4.4.6
+ */
+public class NetworkAuthenticationTypeElement extends ANQPElement {
+
+    private final List<NetworkAuthentication> m_authenticationTypes;
+
+    public enum NwkAuthTypeEnum {
+        TermsAndConditions,
+        OnLineEnrollment,
+        HTTPRedirection,
+        DNSRedirection,
+        Reserved
+    }
+
+    public static class NetworkAuthentication {
+        private final NwkAuthTypeEnum m_type;
+        private final String m_url;
+
+        private NetworkAuthentication(NwkAuthTypeEnum type, String url) {
+            m_type = type;
+            m_url = url;
+        }
+
+        public NwkAuthTypeEnum getType() {
+            return m_type;
+        }
+
+        public String getURL() {
+            return m_url;
+        }
+
+        @Override
+        public String toString() {
+            return "NetworkAuthentication{" +
+                    "m_type=" + m_type +
+                    ", m_url='" + m_url + '\'' +
+                    '}';
+        }
+    }
+
+    public NetworkAuthenticationTypeElement(Constants.ANQPElementType infoID, ByteBuffer payload)
+            throws ProtocolException {
+
+        super(infoID);
+
+        m_authenticationTypes = new ArrayList<NetworkAuthentication>();
+
+        while (payload.hasRemaining()) {
+            int typeNumber = payload.get() & BYTE_MASK;
+            NwkAuthTypeEnum type;
+            type = typeNumber >= NwkAuthTypeEnum.values().length ?
+                    NwkAuthTypeEnum.Reserved :
+                    NwkAuthTypeEnum.values()[typeNumber];
+
+            m_authenticationTypes.add(new NetworkAuthentication(type,
+                    Constants.getPrefixedString(payload, 2, StandardCharsets.UTF_8)));
+        }
+    }
+
+    public List<NetworkAuthentication> getAuthenticationTypes() {
+        return Collections.unmodifiableList(m_authenticationTypes);
+    }
+
+    @Override
+    public String toString() {
+        return "NetworkAuthenticationType{" +
+                "m_authenticationTypes=" + m_authenticationTypes +
+                '}';
+    }
+}
diff --git a/service/java/com/android/server/wifi/anqp/OSUProvider.java b/service/java/com/android/server/wifi/anqp/OSUProvider.java
new file mode 100644
index 0000000..422343d
--- /dev/null
+++ b/service/java/com/android/server/wifi/anqp/OSUProvider.java
@@ -0,0 +1,118 @@
+package com.android.server.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.android.server.wifi.anqp.Constants.BYTE_MASK;
+import static com.android.server.wifi.anqp.Constants.SHORT_MASK;
+
+/**
+ * An OSU Provider, as specified in
+ * Wi-Fi Alliance Hotspot 2.0 (Release 2) Technical Specification - Version 5.00,
+ * section 4.8.1
+ */
+public class OSUProvider {
+
+    public enum OSUMethod {OmaDm, SoapXml}
+
+    private final List<I18Name> mNames;
+    private final String mOSUServer;
+    private final List<OSUMethod> mOSUMethods;
+    private final List<IconInfo> mIcons;
+    private final String mOsuNai;
+    private final List<I18Name> mServiceDescriptions;
+
+    public OSUProvider(ByteBuffer payload) throws ProtocolException {
+        if (payload.remaining() < 11) {
+            throw new ProtocolException("Truncated OSU provider: " + payload.remaining());
+        }
+
+        int length = payload.getShort() & SHORT_MASK;
+        int namesLength = payload.getShort() & SHORT_MASK;
+
+        ByteBuffer namesBuffer = payload.duplicate().order(ByteOrder.LITTLE_ENDIAN);
+        namesBuffer.limit(namesBuffer.position() + namesLength);
+        payload.position(payload.position() + namesLength);
+
+        mNames = new ArrayList<I18Name>();
+
+        while (namesBuffer.hasRemaining()) {
+            mNames.add(new I18Name(namesBuffer));
+        }
+
+        mOSUServer = Constants.getPrefixedString(payload, 1, StandardCharsets.UTF_8);
+        int methodLength = payload.get() & BYTE_MASK;
+        mOSUMethods = new ArrayList<OSUMethod>(methodLength);
+        while (methodLength > 0) {
+            int methodID = payload.get() & BYTE_MASK;
+            mOSUMethods.add(methodID < OSUMethod.values().length ?
+                    OSUMethod.values()[methodID] :
+                    null);
+            methodLength--;
+        }
+
+        int iconsLength = payload.getShort() & SHORT_MASK;
+        ByteBuffer iconsBuffer = payload.duplicate().order(ByteOrder.LITTLE_ENDIAN);
+        iconsBuffer.limit(iconsBuffer.position() + iconsLength);
+        payload.position(payload.position() + iconsLength);
+
+        mIcons = new ArrayList<IconInfo>();
+
+        while (iconsBuffer.hasRemaining()) {
+            mIcons.add(new IconInfo(iconsBuffer));
+        }
+
+        mOsuNai = Constants.getPrefixedString(payload, 1, StandardCharsets.UTF_8, true);
+
+        int descriptionsLength = payload.getShort() & SHORT_MASK;
+        ByteBuffer descriptionsBuffer = payload.duplicate().order(ByteOrder.LITTLE_ENDIAN);
+        descriptionsBuffer.limit(descriptionsBuffer.position() + descriptionsLength);
+        payload.position(payload.position() + descriptionsLength);
+
+        mServiceDescriptions = new ArrayList<I18Name>();
+
+        while (descriptionsBuffer.hasRemaining()) {
+            mServiceDescriptions.add(new I18Name(descriptionsBuffer));
+        }
+    }
+
+    public List<I18Name> getNames() {
+        return mNames;
+    }
+
+    public String getOSUServer() {
+        return mOSUServer;
+    }
+
+    public List<OSUMethod> getOSUMethods() {
+        return mOSUMethods;
+    }
+
+    public List<IconInfo> getIcons() {
+        return mIcons;
+    }
+
+    public String getOsuNai() {
+        return mOsuNai;
+    }
+
+    public List<I18Name> getServiceDescriptions() {
+        return mServiceDescriptions;
+    }
+
+    @Override
+    public String toString() {
+        return "OSUProvider{" +
+                "mNames=" + mNames +
+                ", mOSUServer='" + mOSUServer + '\'' +
+                ", mOSUMethods=" + mOSUMethods +
+                ", mIcons=" + mIcons +
+                ", mOsuNai='" + mOsuNai + '\'' +
+                ", mServiceDescriptions=" + mServiceDescriptions +
+                '}';
+    }
+}
diff --git a/service/java/com/android/server/wifi/anqp/RoamingConsortiumElement.java b/service/java/com/android/server/wifi/anqp/RoamingConsortiumElement.java
new file mode 100644
index 0000000..80d9b72
--- /dev/null
+++ b/service/java/com/android/server/wifi/anqp/RoamingConsortiumElement.java
@@ -0,0 +1,45 @@
+package com.android.server.wifi.anqp;
+
+import com.android.server.wifi.hotspot2.Utils;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static com.android.server.wifi.anqp.Constants.BYTE_MASK;
+import static com.android.server.wifi.anqp.Constants.getInteger;
+
+/**
+ * The Roaming Consortium ANQP Element, IEEE802.11-2012 section 8.4.4.7
+ */
+public class RoamingConsortiumElement extends ANQPElement {
+
+    private final List<Long> mOis;
+
+    public RoamingConsortiumElement(Constants.ANQPElementType infoID, ByteBuffer payload)
+            throws ProtocolException {
+        super(infoID);
+
+        mOis = new ArrayList<Long>();
+
+        while (payload.hasRemaining()) {
+            int length = payload.get() & BYTE_MASK;
+            if (length > payload.remaining()) {
+                throw new ProtocolException("Bad OI length: " + length);
+            }
+            mOis.add(getInteger(payload, ByteOrder.BIG_ENDIAN, length));
+        }
+    }
+
+    public List<Long> getOIs() {
+        return Collections.unmodifiableList(mOis);
+    }
+
+    @Override
+    public String toString() {
+        return "RoamingConsortium{mOis=[" + Utils.roamingConsortiumsToString(mOis) + "]}";
+    }
+}
diff --git a/service/java/com/android/server/wifi/anqp/TestDriver.java b/service/java/com/android/server/wifi/anqp/TestDriver.java
new file mode 100644
index 0000000..b6be0e4
--- /dev/null
+++ b/service/java/com/android/server/wifi/anqp/TestDriver.java
@@ -0,0 +1,168 @@
+package com.android.server.wifi.anqp;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Test ANQP code by talking to an ANQP server of a socket.
+ */
+public class TestDriver {
+
+    private static final Constants.ANQPElementType[] QueryElements = {
+            Constants.ANQPElementType.ANQPCapabilityList,
+            Constants.ANQPElementType.ANQPVenueName,
+            Constants.ANQPElementType.ANQPEmergencyNumber,
+            Constants.ANQPElementType.ANQPNwkAuthType,
+            Constants.ANQPElementType.ANQPRoamingConsortium,
+            Constants.ANQPElementType.ANQPIPAddrAvailability,
+            Constants.ANQPElementType.ANQPNAIRealm,
+            Constants.ANQPElementType.ANQP3GPPNetwork,
+            Constants.ANQPElementType.ANQPGeoLoc,
+            Constants.ANQPElementType.ANQPCivicLoc,
+            Constants.ANQPElementType.ANQPLocURI,
+            Constants.ANQPElementType.ANQPDomName,
+            Constants.ANQPElementType.ANQPEmergencyAlert,
+            Constants.ANQPElementType.ANQPTDLSCap,
+            Constants.ANQPElementType.ANQPEmergencyNAI,
+            Constants.ANQPElementType.ANQPNeighborReport,
+
+            Constants.ANQPElementType.HSCapabilityList,
+            Constants.ANQPElementType.HSFriendlyName,
+            Constants.ANQPElementType.HSWANMetrics,
+            Constants.ANQPElementType.HSConnCapability,
+            Constants.ANQPElementType.HSNAIHomeRealmQuery,
+            Constants.ANQPElementType.HSOperatingclass,
+            Constants.ANQPElementType.HSOSUProviders
+    };
+
+    public static void runTest() throws IOException {
+
+        Set<Constants.ANQPElementType> elements =
+                new HashSet<Constants.ANQPElementType>(QueryElements.length);
+        elements.addAll(Arrays.asList(QueryElements));
+
+        ByteBuffer request = ByteBuffer.allocate(8192);
+        request.order(ByteOrder.LITTLE_ENDIAN);
+        int lenPos = request.position();
+        request.putShort((short) 0);
+        ANQPFactory.buildQueryRequest(elements, request);
+
+        byte[] requestBytes = prepRequest(lenPos, request);
+
+        System.out.println( "Connecting...");
+        Socket sock = new Socket(InetAddress.getLoopbackAddress(), 6104);
+        BufferedOutputStream out = new BufferedOutputStream(sock.getOutputStream());
+        System.out.println(" ### Querying for " + Arrays.toString(QueryElements));
+        out.write(requestBytes);
+        out.flush();
+
+        BufferedInputStream in = new BufferedInputStream(sock.getInputStream());
+        ByteBuffer payload = getResponse(in);
+
+        HSOsuProvidersElement osuProvidersElement = null;
+        List<ANQPElement> anqpResult = ANQPFactory.parsePayload(payload);
+        for ( ANQPElement element : anqpResult ) {
+            System.out.println( element );
+            if (element.getID() == Constants.ANQPElementType.HSOSUProviders) {
+                osuProvidersElement = (HSOsuProvidersElement)element;
+            }
+        }
+
+        if ( osuProvidersElement != null ) {
+            for (OSUProvider provider : osuProvidersElement.getProviders()) {
+                for (IconInfo iconInfo : provider.getIcons()) {
+                    sendIconRequest(iconInfo.getFileName());
+                }
+            }
+        }
+        sendIconRequest("doesNotExist.noimg");
+
+        sendHomeRealmQuery("nxdomain.abc", "jan.com");
+    }
+
+    private static void sendIconRequest(String fileName) throws IOException {
+        ByteBuffer iconRequest = ByteBuffer.allocate(fileName.length()*2)
+                .order(ByteOrder.LITTLE_ENDIAN);
+        int iconPos = iconRequest.position();
+        iconRequest.putShort((short) 0);
+        ANQPFactory.buildIconRequest(fileName, iconRequest);
+        byte[] iconBytes = prepRequest(iconPos, iconRequest);
+
+        System.out.println( "Connecting...");
+        Socket sock = new Socket(InetAddress.getLoopbackAddress(), 6104);
+        BufferedOutputStream out = new BufferedOutputStream(sock.getOutputStream());
+
+        System.out.println(" ### Requesting icon '" + fileName + "'");
+        out.write(iconBytes);
+        out.flush();
+
+        BufferedInputStream in = new BufferedInputStream(sock.getInputStream());
+        ByteBuffer payload = getResponse(in);
+        List<ANQPElement> anqpResult = ANQPFactory.parsePayload(payload);
+        System.out.println("Icon: " + anqpResult );
+    }
+
+    private static void sendHomeRealmQuery(String ... realms) throws IOException{
+        ByteBuffer request = ByteBuffer.allocate(1024).order(ByteOrder.LITTLE_ENDIAN);
+        int iconPos = request.position();
+        request.putShort((short) 0);
+        ANQPFactory.buildHomeRealmRequest(Arrays.asList(realms), request);
+        byte[] iconBytes = prepRequest(iconPos, request);
+
+        System.out.println( "Connecting...");
+        Socket sock = new Socket(InetAddress.getLoopbackAddress(), 6104);
+        BufferedOutputStream out = new BufferedOutputStream(sock.getOutputStream());
+
+        System.out.println(" ### Home realm query for " + Arrays.toString(realms));
+        out.write(iconBytes);
+        out.flush();
+
+        BufferedInputStream in = new BufferedInputStream(sock.getInputStream());
+        ByteBuffer payload = getResponse(in);
+        List<ANQPElement> anqpResult = ANQPFactory.parsePayload(payload);
+        System.out.println("Home realm query: " + anqpResult );
+    }
+
+    private static ByteBuffer getResponse(InputStream in) throws IOException {
+        ByteBuffer lengthBuffer = read( in, 2 );
+        int length = lengthBuffer.getShort() & Constants.SHORT_MASK;
+        System.out.println("Length " + length);
+
+        return read(in, length);
+    }
+
+    private static byte[] prepRequest(int pos0, ByteBuffer request) {
+        request.putShort(pos0, (short)( request.limit() - pos0 - Constants.BYTES_IN_SHORT ));
+        byte[] octets = new byte[request.remaining()];
+        request.get(octets);
+        return octets;
+    }
+
+    private static ByteBuffer read(InputStream in, int length) throws IOException {
+        byte[] payload = new byte[length];
+        int position = 0;
+        while ( position < length ) {
+            int amount = in.read(payload, position, length - position);
+            if ( amount <= 0 ) {
+                throw new EOFException("Got " + amount);
+            }
+            position += amount;
+        }
+        return ByteBuffer.wrap(payload).order(ByteOrder.LITTLE_ENDIAN);
+    }
+
+    public static void main(String[] args) throws IOException {
+        runTest();
+    }
+}
diff --git a/service/java/com/android/server/wifi/anqp/ThreeGPPNetworkElement.java b/service/java/com/android/server/wifi/anqp/ThreeGPPNetworkElement.java
new file mode 100644
index 0000000..083d280
--- /dev/null
+++ b/service/java/com/android/server/wifi/anqp/ThreeGPPNetworkElement.java
@@ -0,0 +1,53 @@
+package com.android.server.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static com.android.server.wifi.anqp.Constants.BYTE_MASK;
+
+
+/**
+ * The 3GPP Cellular Network ANQP Element, IEEE802.11-2012 section 8.4.4.11
+ */
+public class ThreeGPPNetworkElement extends ANQPElement {
+    private final int mUserData;
+    private final List<CellularNetwork> mPlmns;
+
+    public ThreeGPPNetworkElement(Constants.ANQPElementType infoID, ByteBuffer payload)
+            throws ProtocolException {
+        super(infoID);
+
+        mPlmns = new ArrayList<CellularNetwork>();
+        mUserData = payload.get() & BYTE_MASK;
+        int length = payload.get() & BYTE_MASK;
+        if (length > payload.remaining()) {
+            throw new ProtocolException("Runt payload");
+        }
+
+        while (payload.hasRemaining()) {
+            CellularNetwork network = CellularNetwork.buildCellularNetwork(payload);
+            if (network != null) {
+                mPlmns.add(network);
+            }
+        }
+    }
+
+    public int getUserData() {
+        return mUserData;
+    }
+
+    public List<CellularNetwork> getPlmns() {
+        return Collections.unmodifiableList(mPlmns);
+    }
+
+    @Override
+    public String toString() {
+        return "ThreeGPPNetwork{" +
+                "mUserData=" + mUserData +
+                ", mPlmns=" + mPlmns +
+                '}';
+    }
+}
diff --git a/service/java/com/android/server/wifi/anqp/VenueNameElement.java b/service/java/com/android/server/wifi/anqp/VenueNameElement.java
new file mode 100644
index 0000000..f944c40
--- /dev/null
+++ b/service/java/com/android/server/wifi/anqp/VenueNameElement.java
@@ -0,0 +1,192 @@
+package com.android.server.wifi.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The Venue Name ANQP Element, IEEE802.11-2012 section 8.4.4.4
+ */
+public class VenueNameElement extends ANQPElement {
+    private final VenueGroup mGroup;
+    private final VenueType mType;
+    private final List<I18Name> mNames;
+
+    private static final Map<VenueGroup, Integer> sGroupBases =
+            new EnumMap<VenueGroup, Integer>(VenueGroup.class);
+
+    public VenueNameElement(Constants.ANQPElementType infoID, ByteBuffer payload)
+            throws ProtocolException {
+        super(infoID);
+
+        if (payload.remaining() < 2)
+            throw new ProtocolException("Runt Venue Name");
+
+        int group = payload.get() & Constants.BYTE_MASK;
+        int type = payload.get() & Constants.BYTE_MASK;
+
+        if (group >= VenueGroup.Reserved.ordinal()) {
+            mGroup = VenueGroup.Reserved;
+            mType = VenueType.Reserved;
+        } else {
+            mGroup = VenueGroup.values()[group];
+            type += sGroupBases.get(mGroup);
+            if (type >= VenueType.Reserved.ordinal()) {
+                mType = VenueType.Reserved;
+            } else {
+                mType = VenueType.values()[type];
+            }
+        }
+
+        mNames = new ArrayList<I18Name>();
+        while (payload.hasRemaining()) {
+            mNames.add(new I18Name(payload));
+        }
+    }
+
+    public VenueGroup getGroup() {
+        return mGroup;
+    }
+
+    public VenueType getType() {
+        return mType;
+    }
+
+    public List<I18Name> getNames() {
+        return Collections.unmodifiableList(mNames);
+    }
+
+    @Override
+    public String toString() {
+        return "VenueName{" +
+                "m_group=" + mGroup +
+                ", m_type=" + mType +
+                ", m_names=" + mNames +
+                '}';
+    }
+
+    public enum VenueGroup {
+        Unspecified,
+        Assembly,
+        Business,
+        Educational,
+        FactoryIndustrial,
+        Institutional,
+        Mercantile,
+        Residential,
+        Storage,
+        UtilityMiscellaneous,
+        Vehicular,
+        Outdoor,
+        Reserved  // Note: this must be the last enum constant
+    }
+
+    public enum VenueType {
+        Unspecified,
+
+        UnspecifiedAssembly,
+        Arena,
+        Stadium,
+        PassengerTerminal,
+        Amphitheater,
+        AmusementPark,
+        PlaceOfWorship,
+        ConventionCenter,
+        Library,
+        Museum,
+        Restaurant,
+        Theater,
+        Bar,
+        CoffeeShop,
+        ZooOrAquarium,
+        EmergencyCoordinationCenter,
+
+        UnspecifiedBusiness,
+        DoctorDentistoffice,
+        Bank,
+        FireStation,
+        PoliceStation,
+        PostOffice,
+        ProfessionalOffice,
+        ResearchDevelopmentFacility,
+        AttorneyOffice,
+
+        UnspecifiedEducational,
+        SchoolPrimary,
+        SchoolSecondary,
+        UniversityCollege,
+
+        UnspecifiedFactoryIndustrial,
+        Factory,
+
+        UnspecifiedInstitutional,
+        Hospital,
+        LongTermCareFacility,
+        AlcoholAndDrugRehabilitationCenter,
+        GroupHome,
+        PrisonJail,
+
+        UnspecifiedMercantile,
+        RetailStore,
+        GroceryMarket,
+        AutomotiveServiceStation,
+        ShoppingMall,
+        GasStation,
+
+        UnspecifiedResidential,
+        PrivateResidence,
+        HotelMotel,
+        Dormitory,
+        BoardingHouse,
+
+        UnspecifiedStorage,
+
+        UnspecifiedUtilityMiscellaneous,
+
+        UnspecifiedVehicular,
+        AutomobileOrTruck,
+        Airplane,
+        Bus,
+        Ferry,
+        ShipOrBoat,
+        Train,
+        MotorBike,
+
+        UnspecifiedOutdoor,
+        MuniMeshNetwork,
+        CityPark,
+        RestArea,
+        TrafficControl,
+        BusStop,
+        Kiosk,
+
+        Reserved  // Note: this must be the last enum constant
+    }
+
+    private static final VenueType[] PerGroup =
+            {
+                    VenueType.Unspecified,
+                    VenueType.UnspecifiedAssembly,
+                    VenueType.UnspecifiedBusiness,
+                    VenueType.UnspecifiedEducational,
+                    VenueType.UnspecifiedFactoryIndustrial,
+                    VenueType.UnspecifiedInstitutional,
+                    VenueType.UnspecifiedMercantile,
+                    VenueType.UnspecifiedResidential,
+                    VenueType.UnspecifiedStorage,
+                    VenueType.UnspecifiedUtilityMiscellaneous,
+                    VenueType.UnspecifiedVehicular,
+                    VenueType.UnspecifiedOutdoor
+            };
+
+    static {
+        int index = 0;
+        for (VenueType venue : PerGroup) {
+            sGroupBases.put(VenueGroup.values()[index++], venue.ordinal());
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/anqp/eap/AuthParam.java b/service/java/com/android/server/wifi/anqp/eap/AuthParam.java
new file mode 100644
index 0000000..f7c877a
--- /dev/null
+++ b/service/java/com/android/server/wifi/anqp/eap/AuthParam.java
@@ -0,0 +1,9 @@
+package com.android.server.wifi.anqp.eap;
+
+/**
+ * An Authentication parameter, part of the NAI Realm ANQP element, specified in
+ * IEEE802.11-2012 section 8.4.4.10, table 8-188
+ */
+public interface AuthParam {
+    public EAP.AuthInfoID getAuthInfoID();
+}
diff --git a/service/java/com/android/server/wifi/anqp/eap/Credential.java b/service/java/com/android/server/wifi/anqp/eap/Credential.java
new file mode 100644
index 0000000..d3aca07
--- /dev/null
+++ b/service/java/com/android/server/wifi/anqp/eap/Credential.java
@@ -0,0 +1,72 @@
+package com.android.server.wifi.anqp.eap;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+
+import static com.android.server.wifi.anqp.Constants.BYTE_MASK;
+
+/**
+ * An EAP authentication parameter, IEEE802.11-2012, table 8-188
+ */
+public class Credential implements AuthParam {
+
+    public enum CredType {
+        Reserved,
+        SIM,
+        USIM,
+        NFC,
+        HWToken,
+        Softoken,
+        Certificate,
+        Username,
+        None,
+        Anonymous,
+        VendorSpecific}
+
+    private final EAP.AuthInfoID mAuthInfoID;
+    private final CredType mCredType;
+
+    public Credential(EAP.AuthInfoID infoID, int length, ByteBuffer payload)
+            throws ProtocolException {
+        if (length != 1) {
+            throw new ProtocolException("Bad length: " + length);
+        }
+
+        mAuthInfoID = infoID;
+        int typeID = payload.get() & BYTE_MASK;
+
+        mCredType = typeID < CredType.values().length ?
+                CredType.values()[typeID] :
+                CredType.Reserved;
+    }
+
+    @Override
+    public EAP.AuthInfoID getAuthInfoID() {
+        return mAuthInfoID;
+    }
+
+    @Override
+    public int hashCode() {
+        return mAuthInfoID.hashCode() * 31 + mCredType.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (thatObject == this) {
+            return true;
+        } else if (thatObject == null || thatObject.getClass() != Credential.class) {
+            return false;
+        } else {
+            return ((Credential) thatObject).getCredType() == getCredType();
+        }
+    }
+
+    public CredType getCredType() {
+        return mCredType;
+    }
+
+    @Override
+    public String toString() {
+        return "Auth method " + mAuthInfoID + " = " + mCredType + "\n";
+    }
+}
diff --git a/service/java/com/android/server/wifi/anqp/eap/EAP.java b/service/java/com/android/server/wifi/anqp/eap/EAP.java
new file mode 100644
index 0000000..bdb88e4
--- /dev/null
+++ b/service/java/com/android/server/wifi/anqp/eap/EAP.java
@@ -0,0 +1,155 @@
+package com.android.server.wifi.anqp.eap;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * EAP Related constants for the ANQP NAIRealm element, IEEE802.11-2012 section 8.4.4.10
+ */
+public abstract class EAP {
+
+    private static final Map<Integer, EAPMethodID> sEapIds = new HashMap<>();
+    private static final Map<EAPMethodID, Integer> sRevEapIds = new HashMap<>();
+    private static final Map<Integer, AuthInfoID> sAuthIds = new HashMap<>();
+
+    public static final int EAP_MD5 = 4;
+    public static final int EAP_OTP = 5;
+    public static final int EAP_RSA = 9;
+    public static final int EAP_KEA = 11;
+    public static final int EAP_KEA_VALIDATE = 12;
+    public static final int EAP_TLS = 13;
+    public static final int EAP_LEAP = 17;
+    public static final int EAP_SIM = 18;
+    public static final int EAP_TTLS = 21;
+    public static final int EAP_AKA = 23;
+    public static final int EAP_3Com = 24;
+    public static final int EAP_MSCHAPv2 = 26;
+    public static final int EAP_PEAP = 29;
+    public static final int EAP_POTP = 32;
+    public static final int EAP_ActiontecWireless = 35;
+    public static final int EAP_HTTPDigest = 38;
+    public static final int EAP_SPEKE = 41;
+    public static final int EAP_MOBAC = 42;
+    public static final int EAP_FAST = 43;
+    public static final int EAP_ZLXEAP = 44;
+    public static final int EAP_Link = 45;
+    public static final int EAP_PAX = 46;
+    public static final int EAP_PSK = 47;
+    public static final int EAP_SAKE = 48;
+    public static final int EAP_IKEv2 = 49;
+    public static final int EAP_AKAPrim = 50;
+    public static final int EAP_GPSK = 51;
+    public static final int EAP_PWD = 52;
+    public static final int EAP_EKE = 53;
+    public static final int EAP_TEAP = 55;
+
+    public enum EAPMethodID {
+        EAP_MD5,
+        EAP_OTP,
+        EAP_RSA,
+        EAP_KEA,
+        EAP_KEA_VALIDATE,
+        EAP_TLS,
+        EAP_LEAP,
+        EAP_SIM,
+        EAP_TTLS,
+        EAP_AKA,
+        EAP_3Com,
+        EAP_MSCHAPv2,
+        EAP_PEAP,
+        EAP_POTP,
+        EAP_ActiontecWireless,
+        EAP_HTTPDigest,
+        EAP_SPEKE,
+        EAP_MOBAC,
+        EAP_FAST,
+        EAP_ZLXEAP,
+        EAP_Link,
+        EAP_PAX,
+        EAP_PSK,
+        EAP_SAKE,
+        EAP_IKEv2,
+        EAP_AKAPrim,
+        EAP_GPSK,
+        EAP_PWD,
+        EAP_EKE,
+        EAP_TEAP
+    }
+
+    public static final int ExpandedEAPMethod = 1;
+    public static final int NonEAPInnerAuthType = 2;
+    public static final int InnerAuthEAPMethodType = 3;
+    public static final int ExpandedInnerEAPMethod = 4;
+    public static final int CredentialType = 5;
+    public static final int TunneledEAPMethodCredType = 6;
+    public static final int VendorSpecific = 221;
+
+    public enum AuthInfoID {
+        Undefined,
+        ExpandedEAPMethod,
+        NonEAPInnerAuthType,
+        InnerAuthEAPMethodType,
+        ExpandedInnerEAPMethod,
+        CredentialType,
+        TunneledEAPMethodCredType,
+        VendorSpecific
+    }
+
+    static {
+        sEapIds.put(EAP_MD5, EAPMethodID.EAP_MD5);
+        sEapIds.put(EAP_OTP, EAPMethodID.EAP_OTP);
+        sEapIds.put(EAP_RSA, EAPMethodID.EAP_RSA);
+        sEapIds.put(EAP_KEA, EAPMethodID.EAP_KEA);
+        sEapIds.put(EAP_KEA_VALIDATE, EAPMethodID.EAP_KEA_VALIDATE);
+        sEapIds.put(EAP_TLS, EAPMethodID.EAP_TLS);
+        sEapIds.put(EAP_LEAP, EAPMethodID.EAP_LEAP);
+        sEapIds.put(EAP_SIM, EAPMethodID.EAP_SIM);
+        sEapIds.put(EAP_TTLS, EAPMethodID.EAP_TTLS);
+        sEapIds.put(EAP_AKA, EAPMethodID.EAP_AKA);
+        sEapIds.put(EAP_3Com, EAPMethodID.EAP_3Com);
+        sEapIds.put(EAP_MSCHAPv2, EAPMethodID.EAP_MSCHAPv2);
+        sEapIds.put(EAP_PEAP, EAPMethodID.EAP_PEAP);
+        sEapIds.put(EAP_POTP, EAPMethodID.EAP_POTP);
+        sEapIds.put(EAP_ActiontecWireless, EAPMethodID.EAP_ActiontecWireless);
+        sEapIds.put(EAP_HTTPDigest, EAPMethodID.EAP_HTTPDigest);
+        sEapIds.put(EAP_SPEKE, EAPMethodID.EAP_SPEKE);
+        sEapIds.put(EAP_MOBAC, EAPMethodID.EAP_MOBAC);
+        sEapIds.put(EAP_FAST, EAPMethodID.EAP_FAST);
+        sEapIds.put(EAP_ZLXEAP, EAPMethodID.EAP_ZLXEAP);
+        sEapIds.put(EAP_Link, EAPMethodID.EAP_Link);
+        sEapIds.put(EAP_PAX, EAPMethodID.EAP_PAX);
+        sEapIds.put(EAP_PSK, EAPMethodID.EAP_PSK);
+        sEapIds.put(EAP_SAKE, EAPMethodID.EAP_SAKE);
+        sEapIds.put(EAP_IKEv2, EAPMethodID.EAP_IKEv2);
+        sEapIds.put(EAP_AKAPrim, EAPMethodID.EAP_AKAPrim);
+        sEapIds.put(EAP_GPSK, EAPMethodID.EAP_GPSK);
+        sEapIds.put(EAP_PWD, EAPMethodID.EAP_PWD);
+        sEapIds.put(EAP_EKE, EAPMethodID.EAP_EKE);
+        sEapIds.put(EAP_TEAP, EAPMethodID.EAP_TEAP);
+
+        for (Map.Entry<Integer, EAPMethodID> entry : sEapIds.entrySet()) {
+            sRevEapIds.put(entry.getValue(), entry.getKey());
+        }
+
+        sAuthIds.put(ExpandedEAPMethod, AuthInfoID.ExpandedEAPMethod);
+        sAuthIds.put(NonEAPInnerAuthType, AuthInfoID.NonEAPInnerAuthType);
+        sAuthIds.put(InnerAuthEAPMethodType, AuthInfoID.InnerAuthEAPMethodType);
+        sAuthIds.put(ExpandedInnerEAPMethod, AuthInfoID.ExpandedInnerEAPMethod);
+        sAuthIds.put(CredentialType, AuthInfoID.CredentialType);
+        sAuthIds.put(TunneledEAPMethodCredType, AuthInfoID.TunneledEAPMethodCredType);
+        sAuthIds.put(VendorSpecific, AuthInfoID.VendorSpecific);
+    }
+
+    public static EAPMethodID mapEAPMethod(int methodID) {
+        return sEapIds.get(methodID);
+    }
+
+    public static Integer mapEAPMethod(EAPMethodID methodID) {
+        return sRevEapIds.get(methodID);
+    }
+
+    public static AuthInfoID mapAuthMethod(int methodID) {
+        return sAuthIds.get(methodID);
+    }
+}
diff --git a/service/java/com/android/server/wifi/anqp/eap/EAPMethod.java b/service/java/com/android/server/wifi/anqp/eap/EAPMethod.java
new file mode 100644
index 0000000..97c53a4
--- /dev/null
+++ b/service/java/com/android/server/wifi/anqp/eap/EAPMethod.java
@@ -0,0 +1,192 @@
+package com.android.server.wifi.anqp.eap;
+
+
+import com.android.server.wifi.anqp.Constants;
+import com.android.server.wifi.hotspot2.AuthMatch;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * An EAP Method, part of the NAI Realm ANQP element, specified in
+ * IEEE802.11-2012 section 8.4.4.10, figure 8-420
+ */
+public class EAPMethod {
+    private final EAP.EAPMethodID mEAPMethodID;
+    private final Map<EAP.AuthInfoID, Set<AuthParam>> mAuthParams;
+
+    public EAPMethod(ByteBuffer payload) throws ProtocolException {
+        if (payload.remaining() < 3) {
+            throw new ProtocolException("Runt EAP Method: " + payload.remaining());
+        }
+
+        int length = payload.get() & Constants.BYTE_MASK;
+        int methodID = payload.get() & Constants.BYTE_MASK;
+        int count = payload.get() & Constants.BYTE_MASK;
+
+        mEAPMethodID = EAP.mapEAPMethod(methodID);
+        mAuthParams = new EnumMap<>(EAP.AuthInfoID.class);
+
+        int realCount = 0;
+
+        ByteBuffer paramPayload = payload.duplicate().order(ByteOrder.LITTLE_ENDIAN);
+        paramPayload.limit(paramPayload.position() + length - 2);
+        payload.position(payload.position() + length - 2);
+        while (paramPayload.hasRemaining()) {
+            int id = paramPayload.get() & Constants.BYTE_MASK;
+
+            EAP.AuthInfoID authInfoID = EAP.mapAuthMethod(id);
+            if (authInfoID == null) {
+                throw new ProtocolException("Unknown auth parameter ID: " + id);
+            }
+
+            int len = paramPayload.get() & Constants.BYTE_MASK;
+            if (len == 0 || len > paramPayload.remaining()) {
+                throw new ProtocolException("Bad auth method length: " + len);
+            }
+
+            switch (authInfoID) {
+                case ExpandedEAPMethod:
+                    addAuthParam(new ExpandedEAPMethod(authInfoID, len, paramPayload));
+                    break;
+                case NonEAPInnerAuthType:
+                    addAuthParam(new NonEAPInnerAuth(len, paramPayload));
+                    break;
+                case InnerAuthEAPMethodType:
+                    addAuthParam(new InnerAuthEAP(len, paramPayload));
+                    break;
+                case ExpandedInnerEAPMethod:
+                    addAuthParam(new ExpandedEAPMethod(authInfoID, len, paramPayload));
+                    break;
+                case CredentialType:
+                    addAuthParam(new Credential(authInfoID, len, paramPayload));
+                    break;
+                case TunneledEAPMethodCredType:
+                    addAuthParam(new Credential(authInfoID, len, paramPayload));
+                    break;
+                case VendorSpecific:
+                    addAuthParam(new VendorSpecificAuth(len, paramPayload));
+                    break;
+            }
+
+            realCount++;
+        }
+        if (realCount != count)
+            throw new ProtocolException("Invalid parameter count: " + realCount +
+                    ", expected " + count);
+    }
+
+    public EAPMethod(EAP.EAPMethodID eapMethodID, AuthParam authParam) {
+        mEAPMethodID = eapMethodID;
+        mAuthParams = new HashMap<>(1);
+        if (authParam != null) {
+            Set<AuthParam> authParams = new HashSet<>();
+            authParams.add(authParam);
+            mAuthParams.put(authParam.getAuthInfoID(), authParams);
+        }
+    }
+
+    private void addAuthParam(AuthParam param) {
+        Set<AuthParam> authParams = mAuthParams.get(param.getAuthInfoID());
+        if (authParams == null) {
+            authParams = new HashSet<>();
+            mAuthParams.put(param.getAuthInfoID(), authParams);
+        }
+        authParams.add(param);
+    }
+
+    public Map<EAP.AuthInfoID, Set<AuthParam>> getAuthParams() {
+        return Collections.unmodifiableMap(mAuthParams);
+    }
+
+    public EAP.EAPMethodID getEAPMethodID() {
+        return mEAPMethodID;
+    }
+
+    public int match(com.android.server.wifi.hotspot2.pps.Credential credential) {
+
+        EAPMethod credMethod = credential.getEAPMethod();
+        if (mEAPMethodID != credMethod.getEAPMethodID()) {
+            return AuthMatch.None;
+        }
+
+        switch (mEAPMethodID) {
+            case EAP_TTLS:
+                if (mAuthParams.isEmpty()) {
+                    return AuthMatch.Method;
+                }
+                int paramCount = 0;
+                for (Map.Entry<EAP.AuthInfoID, Set<AuthParam>> entry :
+                        credMethod.getAuthParams().entrySet()) {
+                    Set<AuthParam> params = mAuthParams.get(entry.getKey());
+                    if (params == null) {
+                        continue;
+                    }
+
+                    if (!Collections.disjoint(params, entry.getValue())) {
+                        return AuthMatch.MethodParam;
+                    }
+                    paramCount += params.size();
+                }
+                return paramCount > 0 ? AuthMatch.None : AuthMatch.Method;
+            case EAP_TLS:
+                return AuthMatch.MethodParam;
+            case EAP_SIM:
+            case EAP_AKA:
+            case EAP_AKAPrim:
+                return AuthMatch.Method;
+            default:
+                return AuthMatch.Method;
+        }
+    }
+
+    public AuthParam getAuthParam() {
+        if (mAuthParams.isEmpty()) {
+            return null;
+        }
+        Set<AuthParam> params = mAuthParams.values().iterator().next();
+        if (params.isEmpty()) {
+            return null;
+        }
+        return params.iterator().next();
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        else if (thatObject == null || getClass() != thatObject.getClass()) {
+            return false;
+        }
+
+        EAPMethod that = (EAPMethod) thatObject;
+        return mEAPMethodID == that.mEAPMethodID && mAuthParams.equals(that.mAuthParams);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mEAPMethodID.hashCode();
+        result = 31 * result + mAuthParams.hashCode();
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("EAP Method ").append(mEAPMethodID).append('\n');
+        for (Set<AuthParam> paramSet : mAuthParams.values()) {
+            for (AuthParam param : paramSet) {
+                sb.append("      ").append(param.toString());
+            }
+        }
+        return sb.toString();
+    }
+}
diff --git a/service/java/com/android/server/wifi/anqp/eap/ExpandedEAPMethod.java b/service/java/com/android/server/wifi/anqp/eap/ExpandedEAPMethod.java
new file mode 100644
index 0000000..95763a4
--- /dev/null
+++ b/service/java/com/android/server/wifi/anqp/eap/ExpandedEAPMethod.java
@@ -0,0 +1,78 @@
+package com.android.server.wifi.anqp.eap;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import static com.android.server.wifi.anqp.Constants.BYTE_MASK;
+import static com.android.server.wifi.anqp.Constants.INT_MASK;
+import static com.android.server.wifi.anqp.Constants.SHORT_MASK;
+
+/**
+ * An EAP authentication parameter, IEEE802.11-2012, table 8-188
+ */
+public class ExpandedEAPMethod implements AuthParam {
+
+    private final EAP.AuthInfoID mAuthInfoID;
+    private final int mVendorID;
+    private final long mVendorType;
+
+    public ExpandedEAPMethod(EAP.AuthInfoID authInfoID, int length, ByteBuffer payload)
+            throws ProtocolException {
+        if (length != 7) {
+            throw new ProtocolException("Bad length: " + payload.remaining());
+        }
+
+        mAuthInfoID = authInfoID;
+
+        ByteBuffer vndBuffer = payload.duplicate().order(ByteOrder.BIG_ENDIAN);
+
+        int id = vndBuffer.getShort() & SHORT_MASK;
+        id = (id << Byte.SIZE) | (vndBuffer.get() & BYTE_MASK);
+        mVendorID = id;
+        mVendorType = vndBuffer.getInt() & INT_MASK;
+
+        payload.position(payload.position()+7);
+    }
+
+    public ExpandedEAPMethod(EAP.AuthInfoID authInfoID, int vendorID, long vendorType) {
+        mAuthInfoID = authInfoID;
+        mVendorID = vendorID;
+        mVendorType = vendorType;
+    }
+
+    @Override
+    public EAP.AuthInfoID getAuthInfoID() {
+        return mAuthInfoID;
+    }
+
+    @Override
+    public int hashCode() {
+        return (mAuthInfoID.hashCode() * 31 + mVendorID) * 31 + (int) mVendorType;
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (thatObject == this) {
+            return true;
+        } else if (thatObject == null || thatObject.getClass() != ExpandedEAPMethod.class) {
+            return false;
+        } else {
+            ExpandedEAPMethod that = (ExpandedEAPMethod) thatObject;
+            return that.getVendorID() == getVendorID() && that.getVendorType() == getVendorType();
+        }
+    }
+
+    public int getVendorID() {
+        return mVendorID;
+    }
+
+    public long getVendorType() {
+        return mVendorType;
+    }
+
+    @Override
+    public String toString() {
+        return "Auth method " + mAuthInfoID + ", id " + mVendorID + ", type " + mVendorType + "\n";
+    }
+}
diff --git a/service/java/com/android/server/wifi/anqp/eap/InnerAuthEAP.java b/service/java/com/android/server/wifi/anqp/eap/InnerAuthEAP.java
new file mode 100644
index 0000000..a5bc4f1
--- /dev/null
+++ b/service/java/com/android/server/wifi/anqp/eap/InnerAuthEAP.java
@@ -0,0 +1,56 @@
+package com.android.server.wifi.anqp.eap;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+
+import static com.android.server.wifi.anqp.Constants.BYTE_MASK;
+
+/**
+ * An EAP authentication parameter, IEEE802.11-2012, table 8-188
+ */
+public class InnerAuthEAP implements AuthParam {
+
+    private final EAP.EAPMethodID mEapMethodID;
+
+    public InnerAuthEAP(int length, ByteBuffer payload) throws ProtocolException {
+        if (length != 1) {
+            throw new ProtocolException("Bad length: " + length);
+        }
+        int typeID = payload.get() & BYTE_MASK;
+        mEapMethodID = EAP.mapEAPMethod(typeID);
+    }
+
+    public InnerAuthEAP(EAP.EAPMethodID eapMethodID) {
+        mEapMethodID = eapMethodID;
+    }
+
+    @Override
+    public EAP.AuthInfoID getAuthInfoID() {
+        return EAP.AuthInfoID.InnerAuthEAPMethodType;
+    }
+
+    public EAP.EAPMethodID getEAPMethodID() {
+        return mEapMethodID;
+    }
+
+    @Override
+    public int hashCode() {
+        return mEapMethodID != null ? mEapMethodID.hashCode() : 0;
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (thatObject == this) {
+            return true;
+        } else if (thatObject == null || thatObject.getClass() != InnerAuthEAP.class) {
+            return false;
+        } else {
+            return ((InnerAuthEAP) thatObject).getEAPMethodID() == getEAPMethodID();
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "Auth method InnerAuthEAP, inner = " + mEapMethodID + '\n';
+    }
+}
diff --git a/service/java/com/android/server/wifi/anqp/eap/NonEAPInnerAuth.java b/service/java/com/android/server/wifi/anqp/eap/NonEAPInnerAuth.java
new file mode 100644
index 0000000..0e04a23
--- /dev/null
+++ b/service/java/com/android/server/wifi/anqp/eap/NonEAPInnerAuth.java
@@ -0,0 +1,89 @@
+package com.android.server.wifi.anqp.eap;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.android.server.wifi.anqp.Constants.BYTE_MASK;
+
+/**
+ * An EAP authentication parameter, IEEE802.11-2012, table 8-188
+ */
+public class NonEAPInnerAuth implements AuthParam {
+
+    public enum NonEAPType {Reserved, PAP, CHAP, MSCHAP, MSCHAPv2}
+    private static final Map<NonEAPType, String> sOmaMap = new EnumMap<>(NonEAPType.class);
+    private static final Map<String, NonEAPType> sRevOmaMap = new HashMap<>();
+
+    private final NonEAPType mType;
+
+    static {
+        sOmaMap.put(NonEAPType.PAP, "PAP");
+        sOmaMap.put(NonEAPType.CHAP, "CHAP");
+        sOmaMap.put(NonEAPType.MSCHAP, "MS-CHAP");
+        sOmaMap.put(NonEAPType.MSCHAPv2, "MS-CHAP-V2");
+
+        for (Map.Entry<NonEAPType, String> entry : sOmaMap.entrySet()) {
+            sRevOmaMap.put(entry.getValue(), entry.getKey());
+        }
+    }
+
+    public NonEAPInnerAuth(int length, ByteBuffer payload) throws ProtocolException {
+        if (length != 1) {
+            throw new ProtocolException("Bad length: " + payload.remaining());
+        }
+
+        int typeID = payload.get() & BYTE_MASK;
+        mType = typeID < NonEAPType.values().length ?
+                NonEAPType.values()[typeID] :
+                NonEAPType.Reserved;
+    }
+
+    public NonEAPInnerAuth(NonEAPType type) {
+        mType = type;
+    }
+
+    /**
+     * Construct from the OMA-DM PPS data
+     * @param eapType as defined in the HS2.0 spec.
+     */
+    public NonEAPInnerAuth(String eapType) {
+        mType = sRevOmaMap.get(eapType);
+    }
+
+    @Override
+    public EAP.AuthInfoID getAuthInfoID() {
+        return EAP.AuthInfoID.NonEAPInnerAuthType;
+    }
+
+    public NonEAPType getType() {
+        return mType;
+    }
+
+    public String getOMAtype() {
+        return sOmaMap.get(mType);
+    }
+
+    @Override
+    public int hashCode() {
+        return mType.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (thatObject == this) {
+            return true;
+        } else if (thatObject == null || thatObject.getClass() != NonEAPInnerAuth.class) {
+            return false;
+        } else {
+            return ((NonEAPInnerAuth) thatObject).getType() == getType();
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "Auth method NonEAPInnerAuthEAP, inner = " + mType + '\n';
+    }
+}
diff --git a/service/java/com/android/server/wifi/anqp/eap/VendorSpecificAuth.java b/service/java/com/android/server/wifi/anqp/eap/VendorSpecificAuth.java
new file mode 100644
index 0000000..1d94192
--- /dev/null
+++ b/service/java/com/android/server/wifi/anqp/eap/VendorSpecificAuth.java
@@ -0,0 +1,47 @@
+package com.android.server.wifi.anqp.eap;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+/**
+ * An EAP authentication parameter, IEEE802.11-2012, table 8-188
+ */
+public class VendorSpecificAuth implements AuthParam {
+
+    private final byte[] mData;
+
+    public VendorSpecificAuth(int length, ByteBuffer payload) throws ProtocolException {
+        mData = new byte[length];
+        payload.get(mData);
+    }
+
+    @Override
+    public EAP.AuthInfoID getAuthInfoID() {
+        return EAP.AuthInfoID.VendorSpecific;
+    }
+
+    public int hashCode() {
+        return Arrays.hashCode(mData);
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (thatObject == this) {
+            return true;
+        } else if (thatObject == null || thatObject.getClass() != VendorSpecificAuth.class) {
+            return false;
+        } else {
+            return Arrays.equals(((VendorSpecificAuth) thatObject).getData(), getData());
+        }
+    }
+
+    public byte[] getData() {
+        return mData;
+    }
+
+    @Override
+    public String toString() {
+        return "Auth method VendorSpecificAuth, data = " + Arrays.toString(mData) + '\n';
+    }
+}
diff --git a/service/java/com/android/server/wifi/configparse/ConfigBuilder.java b/service/java/com/android/server/wifi/configparse/ConfigBuilder.java
new file mode 100644
index 0000000..e8e5e6a
--- /dev/null
+++ b/service/java/com/android/server/wifi/configparse/ConfigBuilder.java
@@ -0,0 +1,388 @@
+package com.android.server.wifi.configparse;
+
+import android.content.Context;
+import android.net.Uri;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiEnterpriseConfig;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Base64;
+import android.util.Log;
+
+import com.android.server.wifi.IMSIParameter;
+import com.android.server.wifi.anqp.eap.AuthParam;
+import com.android.server.wifi.anqp.eap.EAP;
+import com.android.server.wifi.anqp.eap.EAPMethod;
+import com.android.server.wifi.anqp.eap.NonEAPInnerAuth;
+import com.android.server.wifi.hotspot2.omadm.MOManager;
+import com.android.server.wifi.hotspot2.pps.Credential;
+import com.android.server.wifi.hotspot2.pps.HomeSP;
+
+import org.xml.sax.SAXException;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.MessageDigest;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
+
+public class ConfigBuilder {
+    public static final String WifiConfigType = "application/x-wifi-config";
+    private static final String ProfileTag = "application/x-passpoint-profile";
+    private static final String KeyTag = "application/x-pkcs12";
+    private static final String CATag = "application/x-x509-ca-cert";
+
+    private static final String X509 = "X.509";
+
+    private static final String TAG = "WCFG";
+
+    public static WifiConfiguration buildConfig(String uriString, byte[] data, Context context)
+            throws IOException, GeneralSecurityException, SAXException {
+        Log.d(TAG, "Content: " + (data != null ? data.length : -1));
+
+        byte[] b64 = Base64.decode(new String(data, StandardCharsets.ISO_8859_1), Base64.DEFAULT);
+        Log.d(TAG, "Decoded: " + b64.length + " bytes.");
+
+        dropFile(Uri.parse(uriString), context);
+
+        MIMEContainer mimeContainer = new
+                MIMEContainer(new LineNumberReader(
+                new InputStreamReader(new ByteArrayInputStream(b64), StandardCharsets.ISO_8859_1)),
+                null);
+        if (!mimeContainer.isBase64()) {
+            throw new IOException("Encoding for " +
+                    mimeContainer.getContentType() + " is not base64");
+        }
+        MIMEContainer inner;
+        if (mimeContainer.getContentType().equals(WifiConfigType)) {
+            byte[] wrappedContent = Base64.decode(mimeContainer.getText(), Base64.DEFAULT);
+            Log.d(TAG, "Building container from '" +
+                    new String(wrappedContent, StandardCharsets.ISO_8859_1) + "'");
+            inner = new MIMEContainer(new LineNumberReader(
+                    new InputStreamReader(new ByteArrayInputStream(wrappedContent),
+                            StandardCharsets.ISO_8859_1)), null);
+        }
+        else {
+            inner = mimeContainer;
+        }
+        return parse(inner, context);
+    }
+
+    private static void dropFile(Uri uri, Context context) {
+        context.getContentResolver().delete(uri, null, null);
+    }
+
+    private static WifiConfiguration parse(MIMEContainer root, Context context)
+            throws IOException, GeneralSecurityException, SAXException {
+
+        if (root.getMimeContainers() == null) {
+            throw new IOException("Malformed MIME content: not multipart");
+        }
+
+        String moText = null;
+        X509Certificate caCert = null;
+        PrivateKey clientKey = null;
+        List<X509Certificate> clientChain = null;
+
+        for (MIMEContainer subContainer : root.getMimeContainers()) {
+            Log.d(TAG, " + Content Type: " + subContainer.getContentType());
+            switch (subContainer.getContentType()) {
+                case ProfileTag:
+                    if (subContainer.isBase64()) {
+                        byte[] octets = Base64.decode(subContainer.getText(), Base64.DEFAULT);
+                        moText = new String(octets, StandardCharsets.UTF_8);
+                    } else {
+                        moText = subContainer.getText();
+                    }
+                    Log.d(TAG, "OMA: " + moText);
+                    break;
+                case CATag: {
+                    if (!subContainer.isBase64()) {
+                        throw new IOException("Can't read non base64 encoded cert");
+                    }
+
+                    byte[] octets = Base64.decode(subContainer.getText(), Base64.DEFAULT);
+                    CertificateFactory factory = CertificateFactory.getInstance(X509);
+                    caCert = (X509Certificate) factory.generateCertificate(
+                            new ByteArrayInputStream(octets));
+                    Log.d(TAG, "Cert subject " + caCert.getSubjectX500Principal());
+                    Log.d(TAG, "Full Cert: " + caCert);
+                    break;
+                }
+                case KeyTag: {
+                    if (!subContainer.isBase64()) {
+                        throw new IOException("Can't read non base64 encoded key");
+                    }
+
+                    byte[] octets = Base64.decode(subContainer.getText(), Base64.DEFAULT);
+
+                    KeyStore ks = KeyStore.getInstance("PKCS12");
+                    ByteArrayInputStream in = new ByteArrayInputStream(octets);
+                    ks.load(in, new char[0]);
+                    in.close();
+                    Log.d(TAG, "---- Start PKCS12 info " + octets.length + ", size " + ks.size());
+                    Enumeration<String> aliases = ks.aliases();
+                    while (aliases.hasMoreElements()) {
+                        String alias = aliases.nextElement();
+                        clientKey = (PrivateKey) ks.getKey(alias, null);
+                        Log.d(TAG, "Key: " + clientKey.getFormat());
+                        Certificate[] chain = ks.getCertificateChain(alias);
+                        if (chain != null) {
+                            clientChain = new ArrayList<>();
+                            for (Certificate certificate : chain) {
+                                if (!(certificate instanceof X509Certificate)) {
+                                    Log.w(TAG, "Element in cert chain is not an X509Certificate: " +
+                                            certificate.getClass());
+                                }
+                                clientChain.add((X509Certificate) certificate);
+                            }
+                            Log.d(TAG, "Chain: " + clientChain.size());
+                        }
+                    }
+                    Log.d(TAG, "---- End PKCS12 info.");
+                    break;
+                }
+            }
+        }
+
+        if (moText == null) {
+            throw new IOException("Missing profile");
+        }
+
+        return buildConfig(moText, caCert, clientChain, clientKey, context);
+    }
+
+    private static WifiConfiguration buildConfig(String text, X509Certificate caCert,
+                                                 List<X509Certificate> clientChain, PrivateKey key,
+                                                 Context context)
+            throws IOException, SAXException, GeneralSecurityException {
+
+        HomeSP homeSP = MOManager.buildSP(text);
+        Credential credential = homeSP.getCredential();
+
+        WifiConfiguration config;
+
+        EAP.EAPMethodID eapMethodID = credential.getEAPMethod().getEAPMethodID();
+        switch (eapMethodID) {
+            case EAP_TTLS:
+                if (key != null || clientChain != null) {
+                    Log.w(TAG, "Client cert and/or key included with EAP-TTLS profile");
+                }
+                config = buildTTLSConfig(homeSP);
+                break;
+            case EAP_TLS:
+                config = buildTLSConfig(homeSP, clientChain, key);
+                break;
+            case EAP_AKA:
+            case EAP_AKAPrim:
+            case EAP_SIM:
+                if (key != null || clientChain != null || caCert != null) {
+                    Log.i(TAG, "Client/CA cert and/or key included with " +
+                            eapMethodID + " profile");
+                }
+                config = buildSIMConfig(homeSP, context);
+                break;
+            default:
+                throw new IOException("Unsupported EAP Method: " + eapMethodID);
+        }
+
+        WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
+
+        enterpriseConfig.setCaCertificate(caCert);
+        enterpriseConfig.setAnonymousIdentity("anonymous@" + credential.getRealm());
+        enterpriseConfig.setRealm(credential.getRealm());
+
+        return config;
+    }
+
+    // Retain for debugging purposes
+    /*
+    private static void xIterateCerts(KeyStore ks, X509Certificate caCert)
+            throws GeneralSecurityException {
+        Enumeration<String> aliases = ks.aliases();
+        while (aliases.hasMoreElements()) {
+            String alias = aliases.nextElement();
+            Certificate cert = ks.getCertificate(alias);
+            Log.d("HS2J", "Checking " + alias);
+            if (cert instanceof X509Certificate) {
+                X509Certificate x509Certificate = (X509Certificate) cert;
+                boolean sm = x509Certificate.getSubjectX500Principal().equals(
+                        caCert.getSubjectX500Principal());
+                boolean eq = false;
+                if (sm) {
+                    eq = Arrays.equals(x509Certificate.getEncoded(), caCert.getEncoded());
+                }
+                Log.d("HS2J", "Subject: " + x509Certificate.getSubjectX500Principal() +
+                        ": " + sm + "/" + eq);
+            }
+        }
+    }
+    */
+
+    private static WifiConfiguration buildTTLSConfig(HomeSP homeSP)
+            throws IOException {
+        Credential credential = homeSP.getCredential();
+
+        if (credential.getUserName() == null || credential.getPassword() == null) {
+            throw new IOException("EAP-TTLS provisioned without user name or password");
+        }
+
+        EAPMethod eapMethod = credential.getEAPMethod();
+
+        AuthParam authParam = eapMethod.getAuthParam();
+        if (authParam == null ||
+                authParam.getAuthInfoID() != EAP.AuthInfoID.NonEAPInnerAuthType) {
+            throw new IOException("Bad auth parameter for EAP-TTLS: " + authParam);
+        }
+
+        WifiConfiguration config = buildBaseConfiguration(homeSP);
+        NonEAPInnerAuth ttlsParam = (NonEAPInnerAuth) authParam;
+        WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
+        enterpriseConfig.setPhase2Method(remapInnerMethod(ttlsParam.getType()));
+        enterpriseConfig.setIdentity(credential.getUserName());
+        enterpriseConfig.setPassword(credential.getPassword());
+
+        return config;
+    }
+
+    private static WifiConfiguration buildTLSConfig(HomeSP homeSP,
+                                                    List<X509Certificate> clientChain,
+                                                    PrivateKey clientKey)
+            throws IOException, GeneralSecurityException {
+
+        Credential credential = homeSP.getCredential();
+
+        X509Certificate clientCertificate = null;
+
+        if (clientKey == null || clientChain == null) {
+            throw new IOException("No key and/or cert passed for EAP-TLS");
+        }
+        if (credential.getCertType() != Credential.CertType.x509v3) {
+            throw new IOException("Invalid certificate type for TLS: " +
+                    credential.getCertType());
+        }
+
+        byte[] reference = credential.getFingerPrint();
+        MessageDigest digester = MessageDigest.getInstance("SHA-256");
+        for (X509Certificate certificate : clientChain) {
+            digester.reset();
+            byte[] fingerprint = digester.digest(certificate.getEncoded());
+            if (Arrays.equals(reference, fingerprint)) {
+                clientCertificate = certificate;
+                break;
+            }
+        }
+        if (clientCertificate == null) {
+            throw new IOException("No certificate in chain matches supplied fingerprint");
+        }
+
+        String alias = Base64.encodeToString(reference, Base64.DEFAULT);
+
+        WifiConfiguration config = buildBaseConfiguration(homeSP);
+        WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
+        enterpriseConfig.setClientCertificateAlias(alias);
+        enterpriseConfig.setClientKeyEntry(clientKey, clientCertificate);
+
+        return config;
+    }
+
+    private static WifiConfiguration buildSIMConfig(HomeSP homeSP, Context context)
+            throws IOException {
+
+        Credential credential = homeSP.getCredential();
+        IMSIParameter credImsi = credential.getImsi();
+
+        /*
+         * Uncomment to enforce strict IMSI matching with currently installed SIM cards.
+         *
+        TelephonyManager tm = TelephonyManager.from(context);
+        SubscriptionManager sub = SubscriptionManager.from(context);
+        boolean match = false;
+
+        for (int subId : sub.getActiveSubscriptionIdList()) {
+            String imsi = tm.getSubscriberId(subId);
+            if (credImsi.matches(imsi)) {
+                match = true;
+                break;
+            }
+        }
+        if (!match) {
+            throw new IOException("Supplied IMSI does not match any SIM card");
+        }
+        */
+
+        WifiConfiguration config = buildBaseConfiguration(homeSP);
+        config.enterpriseConfig.setPlmn(credImsi.toString());
+        return config;
+    }
+
+    private static WifiConfiguration buildBaseConfiguration(HomeSP homeSP) throws IOException {
+        EAP.EAPMethodID eapMethodID = homeSP.getCredential().getEAPMethod().getEAPMethodID();
+
+        WifiConfiguration config = new WifiConfiguration();
+
+        config.FQDN = homeSP.getFQDN();
+
+        HashSet<Long> roamingConsortiumIds = homeSP.getRoamingConsortiums();
+        config.roamingConsortiumIds = new long[roamingConsortiumIds.size()];
+        int i = 0;
+        for (long id : roamingConsortiumIds) {
+            config.roamingConsortiumIds[i] = id;
+            i++;
+        }
+        config.providerFriendlyName = homeSP.getFriendlyName();
+
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
+
+        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+        enterpriseConfig.setEapMethod(remapEAPMethod(eapMethodID));
+        enterpriseConfig.setRealm(homeSP.getCredential().getRealm());
+        config.enterpriseConfig = enterpriseConfig;
+
+        return config;
+    }
+
+    private static int remapEAPMethod(EAP.EAPMethodID eapMethodID) throws IOException {
+        switch (eapMethodID) {
+            case EAP_TTLS:
+                return WifiEnterpriseConfig.Eap.TTLS;
+            case EAP_TLS:
+                return WifiEnterpriseConfig.Eap.TLS;
+            case EAP_SIM:
+                return WifiEnterpriseConfig.Eap.SIM;
+            case EAP_AKA:
+                return WifiEnterpriseConfig.Eap.AKA;
+            case EAP_AKAPrim:
+                return WifiEnterpriseConfig.Eap.AKA_PRIME;
+            default:
+                throw new IOException("Bad EAP method: " + eapMethodID);
+        }
+    }
+
+    private static int remapInnerMethod(NonEAPInnerAuth.NonEAPType type) throws IOException {
+        switch (type) {
+            case PAP:
+                return WifiEnterpriseConfig.Phase2.PAP;
+            case MSCHAP:
+                return WifiEnterpriseConfig.Phase2.MSCHAP;
+            case MSCHAPv2:
+                return WifiEnterpriseConfig.Phase2.MSCHAPV2;
+            case CHAP:
+            default:
+                throw new IOException("Inner method " + type + " not supported");
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/configparse/MIMEContainer.java b/service/java/com/android/server/wifi/configparse/MIMEContainer.java
new file mode 100644
index 0000000..10ad456
--- /dev/null
+++ b/service/java/com/android/server/wifi/configparse/MIMEContainer.java
@@ -0,0 +1,345 @@
+package com.android.server.wifi.configparse;
+
+import android.util.Log;
+
+import com.android.server.wifi.hotspot2.Utils;
+
+import java.io.IOException;
+import java.io.LineNumberReader;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class MIMEContainer {
+    private static final String Type = "Content-Type";
+    private static final String Encoding = "Content-Transfer-Encoding";
+
+    private static final String Boundary = "boundary=";
+    private static final String CharsetTag = "charset=";
+
+    private final boolean mLast;
+    private final List<MIMEContainer> mMimeContainers;
+    private final String mText;
+
+    private final boolean mMixed;
+    private final boolean mBase64;
+    private final Charset mCharset;
+    private final String mContentType;
+
+    /**
+     * Parse nested MIME content
+     * @param in A reader to read MIME data from; Note that the charset should be ISO-8859-1 to
+     *           ensure transparent octet to character mapping. This is because the content will
+     *           be re-encoded using the correct charset once it is discovered.
+     * @param boundary A boundary string for the MIME section that this container is in.
+     *                 Pass null for the top level object.
+     * @throws java.io.IOException
+     */
+    public MIMEContainer(LineNumberReader in, String boundary) throws IOException {
+        Map<String,List<String>> headers = parseHeader(in);
+
+        List<String> type = headers.get(Type);
+        if (type == null || type.isEmpty()) {
+            throw new IOException("Missing " + Type + " @ " + in.getLineNumber());
+        }
+
+        boolean multiPart = false;
+        boolean mixed = false;
+        String subBoundary = null;
+        Charset charset = StandardCharsets.ISO_8859_1;
+
+        mContentType = type.get(0);
+
+        if (mContentType.startsWith("multipart/")) {
+            multiPart = true;
+
+            for (String attribute : type) {
+                if (attribute.startsWith(Boundary)) {
+                    subBoundary = Utils.unquote(attribute.substring(Boundary.length()));
+                }
+            }
+
+            if (mContentType.endsWith("/mixed")) {
+                mixed = true;
+            }
+        }
+        else if (mContentType.startsWith("text/")) {
+            for (String attribute : type) {
+                if (attribute.startsWith(CharsetTag)) {
+                    charset = Charset.forName(attribute.substring(CharsetTag.length()));
+                }
+            }
+        }
+
+        mMixed = mixed;
+        mCharset = charset;
+
+        if (multiPart && subBoundary != null) {
+            for (;;) {
+                String line = in.readLine();
+                if (line == null) {
+                    throw new IOException("Unexpected EOF before first boundary @ " +
+                            in.getLineNumber());
+                }
+                if (line.startsWith("--") && line.length() == subBoundary.length() + 2 &&
+                        line.regionMatches(2, subBoundary, 0, subBoundary.length())) {
+                    break;
+                }
+            }
+
+            mMimeContainers = new ArrayList<>();
+            for (;;) {
+                MIMEContainer container = new MIMEContainer(in, subBoundary);
+                mMimeContainers.add(container);
+                if (container.isLast()) {
+                    break;
+                }
+            }
+        }
+        else {
+            mMimeContainers = null;
+        }
+
+        List<String> encoding = headers.get(Encoding);
+        boolean quoted = false;
+        boolean base64 = false;
+        if (encoding != null) {
+            for (String text : encoding) {
+                if (text.equalsIgnoreCase("quoted-printable")) {
+                    quoted = true;
+                    break;
+                }
+                else if (text.equalsIgnoreCase("base64")) {
+                    base64 = true;
+                    break;
+                }
+            }
+        }
+        mBase64 = base64;
+
+        Log.d(Utils.hs2LogTag(getClass()),
+                String.format("%s MIME container, boundary '%s', type '%s', encoding %s",
+                multiPart ? "multipart" : "plain", boundary, mContentType, encoding));
+
+        AtomicBoolean eof = new AtomicBoolean();
+        mText = recode(getBody(in, boundary, quoted, eof), charset);
+        mLast = eof.get();
+    }
+
+    public List<MIMEContainer> getMimeContainers() {
+        return mMimeContainers;
+    }
+
+    public String getText() {
+        return mText;
+    }
+
+    public boolean isMixed() {
+        return mMixed;
+    }
+
+    public boolean isBase64() {
+        return mBase64;
+    }
+
+    public String getContentType() {
+        return mContentType;
+    }
+
+    private boolean isLast() {
+        return mLast;
+    }
+
+    private void toString(StringBuilder sb, int nesting) {
+        char[] indent = new char[nesting*4];
+        Arrays.fill(indent, ' ');
+        if (mBase64) {
+            sb.append("base64, type ").append(mContentType).append('\n');
+        }
+        else if (mMimeContainers != null) {
+            sb.append(indent).append("multipart/").append((mMixed ? "mixed" : "other" )).append('\n');
+        }
+        else {
+            sb.append(indent).append(
+                    String.format("%s, type %s",
+                            mCharset,
+                            mContentType)
+            ).append('\n');
+        }
+
+        if (mMimeContainers != null) {
+            for (MIMEContainer mimeContainer : mMimeContainers) {
+                mimeContainer.toString(sb, nesting + 1);
+            }
+        }
+        sb.append(indent).append("Text: ");
+        if (mText.length() < 100000) {
+            sb.append("'").append(mText).append("'\n");
+        }
+        else {
+            sb.append(mText.length()).append(" chars\n");
+        }
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        toString(sb, 0);
+        return sb.toString();
+    }
+
+    private static Map<String,List<String>> parseHeader(LineNumberReader in) throws IOException {
+
+        StringBuilder value = null;
+        String header = null;
+
+        Map<String,List<String>> headers = new HashMap<>();
+
+        for (;;) {
+            String line = in.readLine();
+            if ( line == null ) {
+                throw new IOException("Missing body @ " + in.getLineNumber());
+            }
+            else if (line.length() == 0) {
+                break;
+            }
+
+            if (line.charAt(0) <= ' ') {
+                if (value == null) {
+                    throw new IOException("Illegal blank prefix in header line '" + line + "' @ " + in.getLineNumber());
+                }
+                value.append(' ').append(line.trim());
+                continue;
+            }
+
+            int nameEnd = line.indexOf(':');
+            if (nameEnd < 0) {
+                throw new IOException("Bad header line: '" + line + "' @ " + in.getLineNumber());
+            }
+
+            if (header != null) {
+                String[] values = value.toString().split(";");
+                List<String> valueList = new ArrayList<>(values.length);
+                for (String segment : values) {
+                    valueList.add(segment.trim());
+                }
+                headers.put(header, valueList);
+                //System.out.println("Header '" + header + "' = " + valueList);
+            }
+
+            header = line.substring(0, nameEnd);
+            value = new StringBuilder();
+            value.append(line.substring(nameEnd+1).trim());
+        }
+
+        if (header != null) {
+            String[] values = value.toString().split(";");
+            List<String> valueList = new ArrayList<>(values.length);
+            for (String segment : values) {
+                valueList.add(segment.trim());
+            }
+            headers.put(header, valueList);
+            //System.out.println("Header '" + header + "' = " + valueList);
+        }
+
+        return headers;
+    }
+
+    private static String getBody(LineNumberReader in, String boundary, boolean quoted, AtomicBoolean eof)
+            throws IOException {
+
+        StringBuilder text = new StringBuilder();
+        for (;;) {
+            String line = in.readLine();
+            if (line == null) {
+                if (boundary != null) {
+                    throw new IOException("Unexpected EOF file in body @ " + in.getLineNumber());
+                }
+                else {
+                    return text.toString();
+                }
+            }
+            Boolean end = boundaryCheck(line, boundary);
+            if (end != null) {
+                eof.set(end);
+                //System.out.println("Boundary " + boundary + ": " + end);
+                return text.toString();
+            }
+
+            if (quoted) {
+                if (line.endsWith("=")) {
+                    text.append(unescape(line.substring(line.length() - 1), in.getLineNumber()));
+                }
+                else {
+                    text.append(unescape(line, in.getLineNumber()));
+                }
+            }
+            else {
+                text.append(line);
+            }
+        }
+    }
+
+    private static String recode(String s, Charset charset) {
+        if (charset.equals(StandardCharsets.ISO_8859_1) || charset.equals(StandardCharsets.US_ASCII)) {
+            return s;
+        }
+
+        byte[] octets = s.getBytes(StandardCharsets.ISO_8859_1);
+        return new String(octets, charset);
+    }
+
+    private static Boolean boundaryCheck(String line, String boundary) {
+        if (line.startsWith("--") && line.regionMatches(2, boundary, 0, boundary.length())) {
+            if (line.length() == boundary.length() + 2) {
+                return Boolean.FALSE;
+            }
+            else if (line.length() == boundary.length() + 4 && line.endsWith("--") ) {
+                return Boolean.TRUE;
+            }
+        }
+        return null;
+    }
+
+    private static String unescape(String text, int line) throws IOException {
+        StringBuilder sb = new StringBuilder();
+        for (int n = 0; n < text.length(); n++) {
+            char ch = text.charAt(n);
+            if (ch > 127) {
+                throw new IOException("Bad codepoint " + (int)ch + " in quoted printable @ " + line);
+            }
+            if (ch == '=' && n < text.length() - 2) {
+                int h1 = fromStrictHex(text.charAt(n+1));
+                int h2 = fromStrictHex(text.charAt(n+2));
+                if (h1 >= 0 && h2 >= 0) {
+                    sb.append((char)((h1 << 4) | h2));
+                    n += 2;
+                }
+                else {
+                    sb.append(ch);
+                }
+            }
+            else {
+                sb.append(ch);
+            }
+        }
+        return sb.toString();
+    }
+
+    private static int fromStrictHex(char ch) {
+        if (ch >= '0' && ch <= '9') {
+            return ch - '0';
+        }
+        else if (ch >= 'A' && ch <= 'F') {
+            return ch - 'A' + 10;
+        }
+        else {
+            return -1;
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/ANQPData.java b/service/java/com/android/server/wifi/hotspot2/ANQPData.java
new file mode 100644
index 0000000..8f2dc0b
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/ANQPData.java
@@ -0,0 +1,122 @@
+package com.android.server.wifi.hotspot2;
+
+import com.android.server.wifi.anqp.ANQPElement;
+import com.android.server.wifi.anqp.Constants;
+
+import java.util.Collections;
+import java.util.Map;
+
+public class ANQPData {
+    /**
+     * The regular cache time for entries with a non-zero domain id.
+     */
+    private static final long ANQP_QUALIFIED_CACHE_TIMEOUT = 3600000L;
+    /**
+     * The cache time for entries with a zero domain id. The zero domain id indicates that ANQP
+     * data from the AP may change at any time, thus a relatively short cache time is given to
+     * such data, but still long enough to avoid excessive querying.
+     */
+    private static final long ANQP_UNQUALIFIED_CACHE_TIMEOUT = 300000L;
+    /**
+     * This is the hold off time for pending queries, i.e. the time during which subsequent queries
+     * are squelched.
+     */
+    private static final long ANQP_HOLDOFF_TIME = 10000L;
+
+    /**
+     * Max value for the retry counter for unanswered queries. This limits the maximum time-out to
+     * ANQP_HOLDOFF_TIME * 2^MAX_RETRY. With current values this results in 640s.
+     */
+    private static final int MAX_RETRY = 6;
+
+    private final NetworkDetail mNetwork;
+    private final Map<Constants.ANQPElementType, ANQPElement> mANQPElements;
+    private final long mCtime;
+    private final long mExpiry;
+    private final int mRetry;
+
+    public ANQPData(NetworkDetail network,
+                    Map<Constants.ANQPElementType, ANQPElement> anqpElements) {
+
+        mNetwork = network;
+        mANQPElements = anqpElements != null ? Collections.unmodifiableMap(anqpElements) : null;
+        mCtime = System.currentTimeMillis();
+        mRetry = 0;
+        if (anqpElements == null) {
+            mExpiry = mCtime + ANQP_HOLDOFF_TIME;
+        }
+        else if (network.getAnqpDomainID() == 0) {
+            mExpiry = mCtime + ANQP_UNQUALIFIED_CACHE_TIMEOUT;
+        }
+        else {
+            mExpiry = mCtime + ANQP_QUALIFIED_CACHE_TIMEOUT;
+        }
+    }
+
+    public ANQPData(NetworkDetail network, ANQPData existing) {
+        mNetwork = network;
+        mANQPElements = null;
+        mCtime = System.currentTimeMillis();
+        if (existing == null) {
+            mRetry = 0;
+            mExpiry = mCtime + ANQP_HOLDOFF_TIME;
+        }
+        else {
+            mRetry = Math.max(existing.getRetry() + 1, MAX_RETRY);
+            mExpiry = ANQP_HOLDOFF_TIME * (1<<mRetry);
+        }
+    }
+
+    public Map<Constants.ANQPElementType, ANQPElement> getANQPElements() {
+        return Collections.unmodifiableMap(mANQPElements);
+    }
+
+    public NetworkDetail getNetwork() {
+        return mNetwork;
+    }
+
+    public boolean expired() {
+        return expired(System.currentTimeMillis());
+    }
+
+    public boolean expired(long at) {
+        return mExpiry <= at;
+    }
+
+    protected boolean isValid(NetworkDetail nwk) {
+        return mANQPElements != null &&
+                nwk.getAnqpDomainID() == mNetwork.getAnqpDomainID() &&
+                mExpiry > System.currentTimeMillis();
+    }
+
+    private int getRetry() {
+        return mRetry;
+    }
+
+    public String toString(boolean brief) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(mNetwork.toKeyString()).append(", domid ").append(mNetwork.getAnqpDomainID());
+        if (mANQPElements == null) {
+            sb.append(", unresolved, ");
+        }
+        else {
+            sb.append(", ").append(mANQPElements.size()).append(" elements, ");
+        }
+        long now = System.currentTimeMillis();
+        sb.append(Utils.toHMS(now-mCtime)).append(" old, expires in ").
+                append(Utils.toHMS(mExpiry-now)).append(' ');
+        if (brief) {
+            sb.append(expired(now) ? 'x' : '-');
+            sb.append(mANQPElements == null ? 'u' : '-');
+        }
+        else if (mANQPElements != null) {
+            sb.append(" data=").append(mANQPElements);
+        }
+        return sb.toString();
+    }
+
+    @Override
+    public String toString() {
+        return toString(true);
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/AlarmHandler.java b/service/java/com/android/server/wifi/hotspot2/AlarmHandler.java
new file mode 100644
index 0000000..5fae519
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/AlarmHandler.java
@@ -0,0 +1,5 @@
+package com.android.server.wifi.hotspot2;
+
+public interface AlarmHandler {
+    public void wake(Object token);
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/AnqpCache.java b/service/java/com/android/server/wifi/hotspot2/AnqpCache.java
new file mode 100644
index 0000000..5bc60ae
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/AnqpCache.java
@@ -0,0 +1,190 @@
+package com.android.server.wifi.hotspot2;
+
+import android.util.Log;
+
+import com.android.server.wifi.anqp.ANQPElement;
+import com.android.server.wifi.anqp.Constants;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class AnqpCache {
+    private static final long CACHE_RECHECK = 60000L;
+    private static final boolean STANDARD_ESS = true;  // Regular AP keying; see CacheKey below.
+    private long mLastSweep;
+
+    private final HashMap<CacheKey, ANQPData> mANQPCache;
+
+    public AnqpCache() {
+        mANQPCache = new HashMap<>();
+        mLastSweep = System.currentTimeMillis();
+    }
+
+    private static class CacheKey {
+        private final String mSSID;
+        private final long mBSSID;
+        private final long mHESSID;
+
+        private CacheKey(String ssid, long bssid, long hessid) {
+            mSSID = ssid;
+            mBSSID = bssid;
+            mHESSID = hessid;
+        }
+
+        /**
+         * Build an ANQP cache key suitable for the granularity of the key space as follows:
+         *
+         * HESSID   domainID    standardESS     Key content Rationale
+         * -------- ----------- --------------- ----------- --------------------
+         * n/a      zero        n/a             SSID/BSSID  Domain ID indicates unique AP info
+         * not set  set         false           SSID/BSSID  Strict per AP keying override
+         * not set  set         true            SSID        Standard definition of an ESS
+         * set      set         n/a             HESSID      The ESS is defined by the HESSID
+         *
+         * @param network The network to build the key for.
+         * @param standardESS If this parameter is set the "standard" paradigm for an ESS is used
+         *                    for the cache, i.e. all APs with identical SSID is considered an ESS,
+         *                    otherwise caching is performed per AP.
+         * @return A CacheKey.
+         */
+        private static CacheKey buildKey(NetworkDetail network, boolean standardESS) {
+            String ssid;
+            long bssid;
+            long hessid;
+            if (network.getAnqpDomainID() == 0L || (network.getHESSID() == 0L && !standardESS)) {
+                ssid = network.getSSID();
+                bssid = network.getBSSID();
+                hessid = 0L;
+            }
+            else if (network.getHESSID() != 0L && network.getAnqpDomainID() > 0) {
+                ssid = null;
+                bssid = 0L;
+                hessid = network.getHESSID();
+            }
+            else {
+                ssid = network.getSSID();
+                bssid = 0L;
+                hessid = 0L;
+            }
+
+            return new CacheKey(ssid, bssid, hessid);
+        }
+
+        @Override
+        public int hashCode() {
+            if (mHESSID != 0) {
+                return (int)((mHESSID >>> 32) * 31 + mHESSID);
+            }
+            else if (mBSSID != 0) {
+                return (int)((mSSID.hashCode() * 31 + (mBSSID >>> 32)) * 31 + mBSSID);
+            }
+            else {
+                return mSSID.hashCode();
+            }
+        }
+
+        @Override
+        public boolean equals(Object thatObject) {
+            if (thatObject == this) {
+                return true;
+            }
+            else if (thatObject == null || thatObject.getClass() != CacheKey.class) {
+                return false;
+            }
+            CacheKey that = (CacheKey) thatObject;
+            return Utils.compare(that.mSSID, mSSID) == 0 &&
+                    that.mBSSID == mBSSID &&
+                    that.mHESSID == mHESSID;
+        }
+
+        @Override
+        public String toString() {
+            if (mHESSID != 0L) {
+                return "HESSID:" + NetworkDetail.toMACString(mHESSID);
+            }
+            else if (mBSSID != 0L) {
+                return NetworkDetail.toMACString(mBSSID) +
+                        ":<" + Utils.toUnicodeEscapedString(mSSID) + ">";
+            }
+            else {
+                return '<' + Utils.toUnicodeEscapedString(mSSID) + '>';
+            }
+        }
+    }
+
+    public boolean initiate(NetworkDetail network) {
+        CacheKey key = CacheKey.buildKey(network, STANDARD_ESS);
+
+        synchronized (mANQPCache) {
+            ANQPData data = mANQPCache.get(key);
+            if (data == null || data.expired()) {
+                mANQPCache.put(key, new ANQPData(network, data));
+                return true;
+            }
+            else {
+                Log.d(Utils.hs2LogTag(getClass()),
+                      String.format("BSSID %012x already in cache: %s", network.getBSSID(), data));
+                return false;
+            }
+        }
+    }
+
+    public void update(NetworkDetail network,
+                       Map<Constants.ANQPElementType, ANQPElement> anqpElements) {
+
+        CacheKey key = CacheKey.buildKey(network, STANDARD_ESS);
+
+        // Networks with a 0 ANQP Domain ID are still cached, but with a very short expiry, just
+        // long enough to prevent excessive re-querying.
+        synchronized (mANQPCache) {
+            ANQPData data = new ANQPData(network, anqpElements);
+            mANQPCache.put(key, data);
+        }
+    }
+
+    public ANQPData getEntry(NetworkDetail network) {
+        ANQPData data;
+
+        CacheKey key = CacheKey.buildKey(network, STANDARD_ESS);
+        synchronized (mANQPCache) {
+            data = mANQPCache.get(key);
+        }
+
+        return data != null && data.isValid(network) ? data : null;
+    }
+
+    public void clear(boolean all, boolean debug) {
+        long now = System.currentTimeMillis();
+        synchronized (mANQPCache) {
+            if (all) {
+                mANQPCache.clear();
+                mLastSweep = now;
+            }
+            else if (now > mLastSweep + CACHE_RECHECK) {
+                List<CacheKey> retirees = new ArrayList<>();
+                for (Map.Entry<CacheKey, ANQPData> entry : mANQPCache.entrySet()) {
+                    if (entry.getValue().expired(now)) {
+                        retirees.add(entry.getKey());
+                    }
+                }
+                for (CacheKey key : retirees) {
+                    mANQPCache.remove(key);
+                    if (debug) {
+                        Log.d(Utils.hs2LogTag(getClass()), "Retired " + key);
+                    }
+                }
+                mLastSweep = now;
+            }
+        }
+    }
+
+    public void dump(PrintWriter out) {
+        out.println("Last sweep " + Utils.toHMS(System.currentTimeMillis() - mLastSweep) + " ago.");
+        for (ANQPData anqpData : mANQPCache.values()) {
+            out.println(anqpData.toString(false));
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/AuthMatch.java b/service/java/com/android/server/wifi/hotspot2/AuthMatch.java
new file mode 100644
index 0000000..cd988b5
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/AuthMatch.java
@@ -0,0 +1,40 @@
+package com.android.server.wifi.hotspot2;
+
+/**
+ * Match score for EAP credentials:
+ * None means that there is a distinct mismatch, i.e. realm, method or parameter is defined
+ * and mismatches that of the credential.
+ * Indeterminate means that there is no ANQP information to match against.
+ * Note: The numeric values given to the constants are used for preference comparison and
+ * must be maintained accordingly.
+ */
+public abstract class AuthMatch {
+    public static final int None = -1;
+    public static final int Indeterminate = 0;
+    public static final int Realm = 0x04;
+    public static final int Method = 0x02;
+    public static final int Param = 0x01;
+    public static final int MethodParam = Method | Param;
+    public static final int Exact = Realm | Method | Param;
+
+    public static String toString(int match) {
+        if (match < 0) {
+            return "None";
+        }
+        else if (match == 0) {
+            return "Indeterminate";
+        }
+
+        StringBuilder sb = new StringBuilder();
+        if ((match & Realm) != 0) {
+            sb.append("Realm");
+        }
+        if ((match & Method) != 0) {
+            sb.append("Method");
+        }
+        if ((match & Param) != 0) {
+            sb.append("Param");
+        }
+        return sb.toString();
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/Chronograph.java b/service/java/com/android/server/wifi/hotspot2/Chronograph.java
new file mode 100644
index 0000000..92bc95f
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/Chronograph.java
@@ -0,0 +1,154 @@
+package com.android.server.wifi.hotspot2;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+public class Chronograph extends Thread {
+    private final Map<Long, Set<AlarmEntry>> mAlarmEntryMap = new TreeMap<Long, Set<AlarmEntry>>();
+    private boolean mRecalculate;
+
+    private static class AlarmEntry {
+        private final long mAt;
+        private final AlarmHandler mAlarmHandler;
+        private final Object mToken;
+
+        private AlarmEntry(long at, AlarmHandler alarmHandler, Object token) {
+            mAt = at;
+            mAlarmHandler = alarmHandler;
+            mToken = token;
+        }
+
+        private void callout() {
+            mAlarmHandler.wake(mToken);
+        }
+    }
+
+    public Chronograph()
+    {
+        setName("Chronograph");
+        setDaemon(true);
+    }
+
+    public Object addAlarm(long interval, AlarmHandler handler, Object token) {
+        long at = System.currentTimeMillis() + interval;
+        synchronized (mAlarmEntryMap) {
+            AlarmEntry alarmEntry = new AlarmEntry(at, handler, token);
+            Set<AlarmEntry> entries = mAlarmEntryMap.get(at);
+            if (entries == null) {
+                entries = new HashSet<AlarmEntry>(1);
+                mAlarmEntryMap.put(at, entries);
+            }
+            entries.add(alarmEntry);
+            mRecalculate = true;
+            mAlarmEntryMap.notifyAll();
+            return alarmEntry;
+        }
+    }
+
+    public boolean cancelAlarm(Object key) {
+        if (key == null || key.getClass() != AlarmEntry.class) {
+            throw new IllegalArgumentException("Not an alarm key");
+        }
+
+        AlarmEntry alarmEntry = (AlarmEntry)key;
+
+        synchronized (mAlarmEntryMap) {
+            Set<AlarmEntry> entries = mAlarmEntryMap.get(alarmEntry.mAt);
+            if (entries == null) {
+                return false;
+            }
+            if (entries.remove(alarmEntry)) {
+                mRecalculate = true;
+                mAlarmEntryMap.notifyAll();
+                return true;
+            }
+            return false;
+        }
+    }
+
+    @Override
+    public void run() {
+
+        for(;;) {
+
+            long now = System.currentTimeMillis();
+            List<Set<AlarmEntry>> pending = new ArrayList<Set<AlarmEntry>>();
+
+            long nextExpiration = 0;
+
+            synchronized (mAlarmEntryMap) {
+
+                Iterator<Map.Entry<Long,Set<AlarmEntry>>> entries =
+                        mAlarmEntryMap.entrySet().iterator();
+
+                while (entries.hasNext()) {
+                    Map.Entry<Long,Set<AlarmEntry>> entry = entries.next();
+                    if (entry.getKey() <= now) {
+                        pending.add(entry.getValue());
+                        entries.remove();
+                    }
+                    else {
+                        nextExpiration = entry.getKey();
+                        break;
+                    }
+                }
+            }
+
+            for (Set<AlarmEntry> alarmEntries : pending) {
+                for (AlarmEntry alarmEntry : alarmEntries) {
+                    alarmEntry.callout();
+                }
+            }
+
+            now = System.currentTimeMillis();
+
+            synchronized (mAlarmEntryMap) {
+                long sleep = nextExpiration - now;
+                while (sleep > 0 && !mRecalculate) {
+                    try {
+                        mAlarmEntryMap.wait(sleep);
+                    }
+                    catch (InterruptedException ie) {
+                        /**/
+                    }
+                    sleep = nextExpiration - System.currentTimeMillis();
+                }
+            }
+        }
+    }
+
+    public static void main(String[] args) throws InterruptedException{
+        Chronograph chronograph = new Chronograph();
+        chronograph.start();
+
+        chronograph.addAlarm(3000L, new AlarmHandler() {
+            @Override
+            public void wake(Object token) {
+                System.out.println("3: " + token);
+            }
+        }, "3s" );
+
+        Object key = chronograph.addAlarm(7500L, new AlarmHandler() {
+            @Override
+            public void wake(Object token) {
+                System.out.println("7: " + token);
+            }
+        }, "7.5s" );
+
+        chronograph.addAlarm(10000L, new AlarmHandler() {
+            @Override
+            public void wake(Object token) {
+                System.out.println("10: " + token);
+            }
+        }, "10.00s" );
+
+        System.out.println(chronograph.cancelAlarm(key));
+
+        chronograph.join();
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/NetworkDetail.java b/service/java/com/android/server/wifi/hotspot2/NetworkDetail.java
new file mode 100644
index 0000000..900cbeb
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/NetworkDetail.java
@@ -0,0 +1,635 @@
+package com.android.server.wifi.hotspot2;
+
+import android.net.wifi.ScanResult;
+import android.util.Log;
+
+import com.android.server.wifi.anqp.ANQPElement;
+import com.android.server.wifi.anqp.Constants;
+import com.android.server.wifi.anqp.VenueNameElement;
+
+import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+
+import static com.android.server.wifi.anqp.Constants.BYTES_IN_EUI48;
+import static com.android.server.wifi.anqp.Constants.BYTE_MASK;
+import static com.android.server.wifi.anqp.Constants.getInteger;
+
+public class NetworkDetail {
+
+    private static final int EID_SSID = 0;
+    private static final int EID_BSSLoad = 11;
+    private static final int EID_HT_OPERATION = 61;
+    private static final int EID_VHT_OPERATION = 192;
+    private static final int EID_Interworking = 107;
+    private static final int EID_RoamingConsortium = 111;
+    private static final int EID_ExtendedCaps = 127;
+    private static final int EID_VSA = 221;
+
+    private static final int ANQP_DOMID_BIT = 0x04;
+    private static final int RTT_RESP_ENABLE_BIT = 70;
+
+    private static final long SSID_UTF8_BIT = 0x0001000000000000L;
+    //turn off when SHIP
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false;
+    
+    private static final String TAG = "NetworkDetail:";
+
+    public enum Ant {
+        Private,
+        PrivateWithGuest,
+        ChargeablePublic,
+        FreePublic,
+        Personal,
+        EmergencyOnly,
+        Resvd6,
+        Resvd7,
+        Resvd8,
+        Resvd9,
+        Resvd10,
+        Resvd11,
+        Resvd12,
+        Resvd13,
+        TestOrExperimental,
+        Wildcard
+    }
+
+    public enum HSRelease {
+        R1,
+        R2,
+        Unknown
+    }
+
+    // General identifiers:
+    private final String mSSID;
+    private final long mHESSID;
+    private final long mBSSID;
+
+    // BSS Load element:
+    private final int mStationCount;
+    private final int mChannelUtilization;
+    private final int mCapacity;
+
+    //channel detailed information
+   /*
+    * 0 -- 20 MHz
+    * 1 -- 40 MHz
+    * 2 -- 80 MHz
+    * 3 -- 160 MHz
+    * 4 -- 80 + 80 MHz
+    */
+    private final int mChannelWidth;
+    private final int mPrimaryFreq;
+    private final int mCenterfreq0;
+    private final int mCenterfreq1;
+    private final boolean m80211McRTTResponder;
+    /*
+     * From Interworking element:
+     * mAnt non null indicates the presence of Interworking, i.e. 802.11u
+     * mVenueGroup and mVenueType may be null if not present in the Interworking element.
+     */
+    private final Ant mAnt;
+    private final boolean mInternet;
+    private final VenueNameElement.VenueGroup mVenueGroup;
+    private final VenueNameElement.VenueType mVenueType;
+
+    /*
+     * From HS20 Indication element:
+     * mHSRelease is null only if the HS20 Indication element was not present.
+     * mAnqpDomainID is set to -1 if not present in the element.
+     */
+    private final HSRelease mHSRelease;
+    private final int mAnqpDomainID;
+
+    /*
+     * From beacon:
+     * mAnqpOICount is how many additional OIs are available through ANQP.
+     * mRoamingConsortiums is either null, if the element was not present, or is an array of
+     * 1, 2 or 3 longs in which the roaming consortium values occupy the LSBs.
+     */
+    private final int mAnqpOICount;
+    private final long[] mRoamingConsortiums;
+
+    private final Long mExtendedCapabilities;
+
+    private final Map<Constants.ANQPElementType, ANQPElement> mANQPElements;
+
+    public NetworkDetail(String bssid, String infoElements, List<String> anqpLines, int freq) {
+
+        if (infoElements == null) {
+            throw new IllegalArgumentException("Null information element string");
+        }
+        int separator = infoElements.indexOf('=');
+        if (separator<0) {
+            throw new IllegalArgumentException("No element separator");
+        }
+
+        mBSSID = Utils.parseMac(bssid);
+
+        ByteBuffer data = ByteBuffer.wrap(Utils.hexToBytes(infoElements.substring(separator + 1)))
+                .order(ByteOrder.LITTLE_ENDIAN);
+
+        String ssid = null;
+        byte[] ssidOctets = null;
+        int stationCount = 0;
+        int channelUtilization = 0;
+        int capacity = 0;
+
+        Ant ant = null;
+        boolean internet = false;
+        VenueNameElement.VenueGroup venueGroup = null;
+        VenueNameElement.VenueType venueType = null;
+        long hessid = 0L;
+
+        int anqpOICount = 0;
+        long[] roamingConsortiums = null;
+
+        HSRelease hsRelease = null;
+        int anqpDomainID = 0;       // No domain ID treated the same as a 0; unique info per AP.
+
+        Long extendedCapabilities = null;
+
+        int secondChanelOffset = 0;
+        int channelMode = 0;
+        int centerFreqIndex1 = 0;
+        int centerFreqIndex2 = 0;
+        boolean RTTResponder = false;
+
+        RuntimeException exception = null;
+
+        try {
+            while (data.remaining() > 1) {
+                int eid = data.get() & Constants.BYTE_MASK;
+                int elementLength = data.get() & Constants.BYTE_MASK;
+
+                if (elementLength > data.remaining()) {
+                    throw new IllegalArgumentException("Element length " + elementLength +
+                            " exceeds payload length " + data.remaining() +
+                            " @ " + data.position());
+                }
+                if (eid == 0 && elementLength == 0 && ssidOctets != null) {
+                    // Don't overwrite SSID (eid 0) with trailing zero garbage
+                    continue;
+                }
+
+                ByteBuffer element;
+
+                switch (eid) {
+                    case EID_SSID:
+                        ssidOctets = new byte[elementLength];
+                        data.get(ssidOctets);
+                        break;
+                    case EID_BSSLoad:
+                        if (elementLength != 5) {
+                            throw new IllegalArgumentException("BSS Load element length is not 5: " +
+                                    elementLength);
+                        }
+                        stationCount = data.getShort() & Constants.SHORT_MASK;
+                        channelUtilization = data.get() & Constants.BYTE_MASK;
+                        capacity = data.getShort() & Constants.SHORT_MASK;
+                        break;
+                    case EID_HT_OPERATION:
+                        element = getAndAdvancePayload(data, elementLength);
+                        int primary_channel = element.get();
+                        secondChanelOffset = element.get() & 0x3;
+                        break;
+                    case EID_VHT_OPERATION:
+                        element = getAndAdvancePayload(data, elementLength);
+                        channelMode = element.get() & Constants.BYTE_MASK;
+                        centerFreqIndex1 = element.get() & Constants.BYTE_MASK;
+                        centerFreqIndex2 = element.get() & Constants.BYTE_MASK;
+                        break;
+                    case EID_Interworking:
+                        int anOptions = data.get() & Constants.BYTE_MASK;
+                        ant = Ant.values()[anOptions & 0x0f];
+                        internet = (anOptions & 0x10) != 0;
+                        // Len 1 none, 3 venue-info, 7 HESSID, 9 venue-info & HESSID
+                        if (elementLength == 3 || elementLength == 9) {
+                            try {
+                                ByteBuffer vinfo = data.duplicate();
+                                vinfo.limit(vinfo.position() + 2);
+                                VenueNameElement vne =
+                                        new VenueNameElement(Constants.ANQPElementType.ANQPVenueName,
+                                                vinfo);
+                                venueGroup = vne.getGroup();
+                                venueType = vne.getType();
+                                data.getShort();
+                            } catch (ProtocolException pe) {
+                                /*Cannot happen*/
+                            }
+                        } else if (elementLength != 1 && elementLength != 7) {
+                            throw new IllegalArgumentException("Bad Interworking element length: " +
+                                    elementLength);
+                        }
+                        if (elementLength == 7 || elementLength == 9) {
+                            hessid = getInteger(data, ByteOrder.BIG_ENDIAN, 6);
+                        }
+                        break;
+                    case EID_RoamingConsortium:
+                        anqpOICount = data.get() & Constants.BYTE_MASK;
+
+                        int oi12Length = data.get() & Constants.BYTE_MASK;
+                        int oi1Length = oi12Length & Constants.NIBBLE_MASK;
+                        int oi2Length = (oi12Length >>> 4) & Constants.NIBBLE_MASK;
+                        int oi3Length = elementLength - 2 - oi1Length - oi2Length;
+                        int oiCount = 0;
+                        if (oi1Length > 0) {
+                            oiCount++;
+                            if (oi2Length > 0) {
+                                oiCount++;
+                                if (oi3Length > 0) {
+                                    oiCount++;
+                                }
+                            }
+                        }
+                        roamingConsortiums = new long[oiCount];
+                        if (oi1Length > 0 && roamingConsortiums.length > 0) {
+                            roamingConsortiums[0] =
+                                    getInteger(data, ByteOrder.BIG_ENDIAN, oi1Length);
+                        }
+                        if (oi2Length > 0 && roamingConsortiums.length > 1) {
+                            roamingConsortiums[1] =
+                                    getInteger(data, ByteOrder.BIG_ENDIAN, oi2Length);
+                        }
+                        if (oi3Length > 0 && roamingConsortiums.length > 2) {
+                            roamingConsortiums[2] =
+                                    getInteger(data, ByteOrder.BIG_ENDIAN, oi3Length);
+                        }
+                        break;
+                    case EID_VSA:
+                        element = getAndAdvancePayload(data, elementLength);
+                        if (elementLength >= 5 && element.getInt() == Constants.HS20_FRAME_PREFIX) {
+                            int hsConf = element.get() & Constants.BYTE_MASK;
+                            switch ((hsConf >> 4) & Constants.NIBBLE_MASK) {
+                                case 0:
+                                    hsRelease = HSRelease.R1;
+                                    break;
+                                case 1:
+                                    hsRelease = HSRelease.R2;
+                                    break;
+                                default:
+                                    hsRelease = HSRelease.Unknown;
+                                    break;
+                            }
+                            if ((hsConf & ANQP_DOMID_BIT) != 0) {
+                                if (elementLength < 7) {
+                                    throw new IllegalArgumentException(
+                                            "HS20 indication element too short: " + elementLength);
+                                }
+                                anqpDomainID = element.getShort() & Constants.SHORT_MASK;
+                            }
+                        }
+                        break;
+                    case EID_ExtendedCaps:
+                        element = data.duplicate();
+                        extendedCapabilities =
+                                Constants.getInteger(data, ByteOrder.LITTLE_ENDIAN, elementLength);
+
+                        int index = RTT_RESP_ENABLE_BIT / 8;
+                        byte offset = RTT_RESP_ENABLE_BIT % 8;
+
+                        if (elementLength < index + 1) {
+                            RTTResponder = false;
+                            element.position(element.position() + elementLength);
+                            break;
+                        }
+
+                        element.position(element.position() + index);
+
+                        RTTResponder = (element.get() & (0x1 << offset)) != 0;
+                        break;
+                    default:
+                        data.position(data.position() + elementLength);
+                        break;
+                }
+            }
+        }
+        catch (IllegalArgumentException | BufferUnderflowException | ArrayIndexOutOfBoundsException e) {
+            Log.d(Utils.hs2LogTag(getClass()), "Caught " + e);
+            if (ssidOctets == null) {
+                throw new IllegalArgumentException("Malformed IE string (no SSID)", e);
+            }
+            exception = e;
+        }
+
+        if (ssidOctets != null) {
+            boolean strictUTF8 = extendedCapabilities != null &&
+                    ( extendedCapabilities & SSID_UTF8_BIT ) != 0;
+
+            /*
+             * Strict use of the "UTF-8 SSID" bit by APs appears to be spotty at best even if the
+             * encoding truly is in UTF-8. An unconditional attempt to decode the SSID as UTF-8 is
+             * therefore always made with a fall back to 8859-1 under normal circumstances.
+             * If, however, a previous exception was detected and the UTF-8 bit is set, failure to
+             * decode the SSID will be used as an indication that the whole frame is malformed and
+             * an exception will be triggered.
+             */
+            CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
+            try {
+                CharBuffer decoded = decoder.decode(ByteBuffer.wrap(ssidOctets));
+                ssid = decoded.toString();
+            }
+            catch (CharacterCodingException cce) {
+                ssid = null;
+            }
+
+            if (ssid == null) {
+                if (strictUTF8 && exception != null) {
+                    throw new IllegalArgumentException("Failed to decode SSID in dubious IE string");
+                }
+                else {
+                    ssid = new String(ssidOctets, StandardCharsets.ISO_8859_1);
+                }
+            }
+        }
+
+        mSSID = ssid;
+        mHESSID = hessid;
+        mStationCount = stationCount;
+        mChannelUtilization = channelUtilization;
+        mCapacity = capacity;
+        mAnt = ant;
+        mInternet = internet;
+        mVenueGroup = venueGroup;
+        mVenueType = venueType;
+        mHSRelease = hsRelease;
+        mAnqpDomainID = anqpDomainID;
+        mAnqpOICount = anqpOICount;
+        mRoamingConsortiums = roamingConsortiums;
+        mExtendedCapabilities = extendedCapabilities;
+        mANQPElements = SupplicantBridge.parseANQPLines(anqpLines);
+        //set up channel info
+        mPrimaryFreq = freq;
+
+        if (channelMode != 0) {
+            // 80 or 160 MHz
+            mChannelWidth = channelMode + 1;
+            mCenterfreq0 = (centerFreqIndex1 - 36) * 5 + 5180;
+            if(channelMode > 1) { //160MHz
+                mCenterfreq1 = (centerFreqIndex2 - 36) * 5 + 5180;
+            } else {
+                mCenterfreq1 = 0;
+            }
+        } else {
+            //20 or 40 MHz
+            if (secondChanelOffset != 0) {//40MHz
+                mChannelWidth = 1;
+                if (secondChanelOffset == 1) {
+                    mCenterfreq0 = mPrimaryFreq + 20;
+                } else if (secondChanelOffset == 3) {
+                    mCenterfreq0 = mPrimaryFreq - 20;
+                } else {
+                    mCenterfreq0 = 0;
+                    Log.e(TAG,"Error on secondChanelOffset");
+                }
+            } else {
+                mCenterfreq0 = 0;
+                mChannelWidth = 0;
+            }
+            mCenterfreq1 = 0;
+        }
+        m80211McRTTResponder = RTTResponder;
+        if (VDBG) {
+            Log.d(TAG, mSSID + "ChannelWidth is: " + mChannelWidth + " PrimaryFreq: " + mPrimaryFreq +
+                    " mCenterfreq0: " + mCenterfreq0 + " mCenterfreq1: " + mCenterfreq1 +
+                    (m80211McRTTResponder ? "Support RTT reponder" : "Do not support RTT responder"));
+        }
+    }
+
+    private static ByteBuffer getAndAdvancePayload(ByteBuffer data, int plLength) {
+        ByteBuffer payload = data.duplicate().order(data.order());
+        payload.limit(payload.position() + plLength);
+        data.position(data.position() + plLength);
+        return payload;
+    }
+
+    private NetworkDetail(NetworkDetail base, Map<Constants.ANQPElementType, ANQPElement> anqpElements) {
+        mSSID = base.mSSID;
+        mBSSID = base.mBSSID;
+        mHESSID = base.mHESSID;
+        mStationCount = base.mStationCount;
+        mChannelUtilization = base.mChannelUtilization;
+        mCapacity = base.mCapacity;
+        mAnt = base.mAnt;
+        mInternet = base.mInternet;
+        mVenueGroup = base.mVenueGroup;
+        mVenueType = base.mVenueType;
+        mHSRelease = base.mHSRelease;
+        mAnqpDomainID = base.mAnqpDomainID;
+        mAnqpOICount = base.mAnqpOICount;
+        mRoamingConsortiums = base.mRoamingConsortiums;
+        mExtendedCapabilities = base.mExtendedCapabilities;
+        mANQPElements = anqpElements;
+        mChannelWidth = base.mChannelWidth;
+        mPrimaryFreq = base.mPrimaryFreq;
+        mCenterfreq0 = base.mCenterfreq0;
+        mCenterfreq1 = base.mCenterfreq1;
+        m80211McRTTResponder = base.m80211McRTTResponder;
+    }
+
+    public NetworkDetail complete(Map<Constants.ANQPElementType, ANQPElement> anqpElements) {
+        return new NetworkDetail(this, anqpElements);
+    }
+
+    private static long parseMac(String s) {
+
+        long mac = 0;
+        int count = 0;
+        for (int n = 0; n < s.length(); n++) {
+            int nibble = Utils.fromHex(s.charAt(n), true);
+            if (nibble >= 0) {
+                mac = (mac << 4) | nibble;
+                count++;
+            }
+        }
+        if (count < 12 || (count&1) == 1) {
+            throw new IllegalArgumentException("Bad MAC address: '" + s + "'");
+        }
+        return mac;
+    }
+
+    public boolean has80211uInfo() {
+        return mAnt != null || mRoamingConsortiums != null || mHSRelease != null;
+    }
+
+    public boolean hasInterworking() {
+        return mAnt != null;
+    }
+
+    public String getSSID() {
+        return mSSID;
+    }
+
+    public String getTrimmedSSID() {
+        for (int n = 0; n < mSSID.length(); n++) {
+            if (mSSID.charAt(n) != 0) {
+                return mSSID;
+            }
+        }
+        return "";
+    }
+
+    public long getHESSID() {
+        return mHESSID;
+    }
+
+    public long getBSSID() {
+        return mBSSID;
+    }
+
+    public int getStationCount() {
+        return mStationCount;
+    }
+
+    public int getChannelUtilization() {
+        return mChannelUtilization;
+    }
+
+    public int getCapacity() {
+        return mCapacity;
+    }
+
+    public boolean isInterworking() {
+        return mAnt != null;
+    }
+
+    public Ant getAnt() {
+        return mAnt;
+    }
+
+    public boolean isInternet() {
+        return mInternet;
+    }
+
+    public VenueNameElement.VenueGroup getVenueGroup() {
+        return mVenueGroup;
+    }
+
+    public VenueNameElement.VenueType getVenueType() {
+        return mVenueType;
+    }
+
+    public HSRelease getHSRelease() {
+        return mHSRelease;
+    }
+
+    public int getAnqpDomainID() {
+        return mAnqpDomainID;
+    }
+
+    public int getAnqpOICount() {
+        return mAnqpOICount;
+    }
+
+    public long[] getRoamingConsortiums() {
+        return mRoamingConsortiums;
+    }
+
+    public Long getExtendedCapabilities() {
+        return mExtendedCapabilities;
+    }
+
+    public Map<Constants.ANQPElementType, ANQPElement> getANQPElements() {
+        return mANQPElements;
+    }
+
+    public int getChannelWidth() {
+        return mChannelWidth;
+    }
+
+    public int getCenterfreq0() {
+        return mCenterfreq0;
+    }
+
+    public int getCenterfreq1() {
+        return mCenterfreq1;
+    }
+
+    public boolean is80211McResponderSupport() {
+        return m80211McRTTResponder;
+    }
+
+    public boolean isSSID_UTF8() {
+        return mExtendedCapabilities != null && (mExtendedCapabilities & SSID_UTF8_BIT) != 0;
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        if (thatObject == null || getClass() != thatObject.getClass()) {
+            return false;
+        }
+
+        NetworkDetail that = (NetworkDetail)thatObject;
+
+        return getSSID().equals(that.getSSID()) && getBSSID() == that.getBSSID();
+    }
+
+    @Override
+    public int hashCode() {
+        return ((mSSID.hashCode() * 31) + (int)(mBSSID >>> 32)) * 31 + (int)mBSSID;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("NetworkInfo{mSSID='%s', mHESSID=%x, mBSSID=%x, mStationCount=%d, " +
+                "mChannelUtilization=%d, mCapacity=%d, mAnt=%s, mInternet=%s, " +
+                "mVenueGroup=%s, mVenueType=%s, mHSRelease=%s, mAnqpDomainID=%d, " +
+                "mAnqpOICount=%d, mRoamingConsortiums=%s}",
+                mSSID, mHESSID, mBSSID, mStationCount,
+                mChannelUtilization, mCapacity, mAnt, mInternet,
+                mVenueGroup, mVenueType, mHSRelease, mAnqpDomainID,
+                mAnqpOICount, Utils.roamingConsortiumsToString(mRoamingConsortiums));
+    }
+
+    public String toKeyString() {
+        return mHESSID != 0 ?
+            String.format("'%s':%012x (%012x)", mSSID, mBSSID, mHESSID) :
+            String.format("'%s':%012x", mSSID, mBSSID);
+    }
+
+    public String getBSSIDString() {
+        return toMACString(mBSSID);
+    }
+
+    public static String toMACString(long mac) {
+        StringBuilder sb = new StringBuilder();
+        boolean first = true;
+        for (int n = BYTES_IN_EUI48 - 1; n >= 0; n--) {
+            if (first) {
+                first = false;
+            } else {
+                sb.append(':');
+            }
+            sb.append(String.format("%02x", (mac >>> (n * Byte.SIZE)) & BYTE_MASK));
+        }
+        return sb.toString();
+    }
+
+    private static final String IE = "ie=" +
+            "000477696e67" +                // SSID wing
+            "0b052a00cf611e" +              // BSS Load 42:207:7777
+            "6b091e0a01610408621205" +      // internet:Experimental:Vehicular:Auto:hessid
+            "6f0a0e530111112222222229" +    // 14:111111:2222222229
+            "dd07506f9a10143a01";           // r2:314
+
+    private static final String IE2 = "ie=000f4578616d706c65204e6574776f726b010882848b960c1218240301012a010432043048606c30140100000fac040100000fac040100000fac0100007f04000000806b091e07010203040506076c027f006f1001531122331020304050010203040506dd05506f9a1000";
+
+    public static void main(String[] args) {
+        ScanResult scanResult = new ScanResult();
+        scanResult.SSID = "wing";
+        scanResult.BSSID = "610408";
+        NetworkDetail nwkDetail = new NetworkDetail(scanResult.BSSID, IE2, null, 0);
+        System.out.println(nwkDetail);
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointMatch.java b/service/java/com/android/server/wifi/hotspot2/PasspointMatch.java
new file mode 100644
index 0000000..8825dad
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointMatch.java
@@ -0,0 +1,9 @@
+package com.android.server.wifi.hotspot2;
+
+public enum PasspointMatch {
+    HomeProvider,
+    RoamingProvider,
+    Incomplete,
+    None,
+    Declined
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointMatchInfo.java b/service/java/com/android/server/wifi/hotspot2/PasspointMatchInfo.java
new file mode 100644
index 0000000..deb60ed
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointMatchInfo.java
@@ -0,0 +1,246 @@
+package com.android.server.wifi.hotspot2;
+
+import com.android.server.wifi.ScanDetail;
+import com.android.server.wifi.anqp.ANQPElement;
+import com.android.server.wifi.anqp.HSConnectionCapabilityElement;
+import com.android.server.wifi.anqp.HSWanMetricsElement;
+import com.android.server.wifi.anqp.IPAddressTypeAvailabilityElement;
+import com.android.server.wifi.hotspot2.pps.HomeSP;
+
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.android.server.wifi.anqp.Constants.ANQPElementType;
+import static com.android.server.wifi.anqp.IPAddressTypeAvailabilityElement.IPv4Availability;
+import static com.android.server.wifi.anqp.IPAddressTypeAvailabilityElement.IPv6Availability;
+
+public class PasspointMatchInfo implements Comparable<PasspointMatchInfo> {
+    private final PasspointMatch mPasspointMatch;
+    private final ScanDetail mScanDetail;
+    private final HomeSP mHomeSP;
+    private final int mScore;
+
+    private static final Map<IPv4Availability, Integer> sIP4Scores =
+            new EnumMap<>(IPv4Availability.class);
+    private static final Map<IPv6Availability, Integer> sIP6Scores =
+            new EnumMap<>(IPv6Availability.class);
+
+    private static final Map<Integer, Map<Integer, Integer>> sPortScores = new HashMap<>();
+
+    private static final int IPPROTO_ICMP = 1;
+    private static final int IPPROTO_TCP = 6;
+    private static final int IPPROTO_UDP = 17;
+    private static final int IPPROTO_ESP = 50;
+    private static final Map<NetworkDetail.Ant, Integer> sAntScores = new HashMap<>();
+
+    static {
+        // These are all arbitrarily chosen scores, subject to tuning.
+
+        sAntScores.put(NetworkDetail.Ant.FreePublic, 4);
+        sAntScores.put(NetworkDetail.Ant.ChargeablePublic, 4);
+        sAntScores.put(NetworkDetail.Ant.PrivateWithGuest, 4);
+        sAntScores.put(NetworkDetail.Ant.Private, 4);
+        sAntScores.put(NetworkDetail.Ant.Personal, 2);
+        sAntScores.put(NetworkDetail.Ant.EmergencyOnly, 2);
+        sAntScores.put(NetworkDetail.Ant.Wildcard, 1);
+        sAntScores.put(NetworkDetail.Ant.TestOrExperimental, 0);
+
+        sIP4Scores.put(IPv4Availability.NotAvailable, 0);
+        sIP4Scores.put(IPv4Availability.PortRestricted, 1);
+        sIP4Scores.put(IPv4Availability.PortRestrictedAndSingleNAT, 1);
+        sIP4Scores.put(IPv4Availability.PortRestrictedAndDoubleNAT, 1);
+        sIP4Scores.put(IPv4Availability.Unknown, 1);
+        sIP4Scores.put(IPv4Availability.Public, 2);
+        sIP4Scores.put(IPv4Availability.SingleNAT, 2);
+        sIP4Scores.put(IPv4Availability.DoubleNAT, 2);
+
+        sIP6Scores.put(IPv6Availability.NotAvailable, 0);
+        sIP6Scores.put(IPv6Availability.Reserved, 1);
+        sIP6Scores.put(IPv6Availability.Unknown, 1);
+        sIP6Scores.put(IPv6Availability.Available, 2);
+
+        Map<Integer, Integer> tcpMap = new HashMap<>();
+        tcpMap.put(20, 1);
+        tcpMap.put(21, 1);
+        tcpMap.put(22, 3);
+        tcpMap.put(23, 2);
+        tcpMap.put(25, 8);
+        tcpMap.put(26, 8);
+        tcpMap.put(53, 3);
+        tcpMap.put(80, 10);
+        tcpMap.put(110, 6);
+        tcpMap.put(143, 6);
+        tcpMap.put(443, 10);
+        tcpMap.put(993, 6);
+        tcpMap.put(1723, 7);
+
+        Map<Integer, Integer> udpMap = new HashMap<>();
+        udpMap.put(53, 10);
+        udpMap.put(500, 7);
+        udpMap.put(5060, 10);
+        udpMap.put(4500, 4);
+
+        sPortScores.put(IPPROTO_TCP, tcpMap);
+        sPortScores.put(IPPROTO_UDP, udpMap);
+    }
+
+
+    public PasspointMatchInfo(PasspointMatch passpointMatch,
+                              ScanDetail scanDetail, HomeSP homeSP) {
+        mPasspointMatch = passpointMatch;
+        mScanDetail = scanDetail;
+        mHomeSP = homeSP;
+
+        int score;
+        if (passpointMatch == PasspointMatch.HomeProvider) {
+            score = 100;
+        }
+        else if (passpointMatch == PasspointMatch.RoamingProvider) {
+            score = 0;
+        }
+        else {
+            score = -1000;  // Don't expect to see anything not home or roaming.
+        }
+
+        if (getNetworkDetail().getHSRelease() != null) {
+            score += getNetworkDetail().getHSRelease() != NetworkDetail.HSRelease.Unknown ? 50 : 0;
+        }
+
+        if (getNetworkDetail().hasInterworking()) {
+            score += getNetworkDetail().isInternet() ? 20 : -20;
+        }
+
+        score += (Math.max(200-getNetworkDetail().getStationCount(), 0) *
+                (255-getNetworkDetail().getChannelUtilization()) *
+                getNetworkDetail().getCapacity()) >>> 26;
+                // Gives a value of 23 max capped at 200 stations and max cap 31250
+
+        if (getNetworkDetail().hasInterworking()) {
+            score += sAntScores.get(getNetworkDetail().getAnt());
+        }
+
+        Map<ANQPElementType, ANQPElement> anqp = getNetworkDetail().getANQPElements();
+
+        if (anqp != null) {
+            HSWanMetricsElement wm = (HSWanMetricsElement) anqp.get(ANQPElementType.HSWANMetrics);
+
+            if (wm != null) {
+                if (wm.getStatus() != HSWanMetricsElement.LinkStatus.Up || wm.isCapped()) {
+                    score -= 1000;
+                } else {
+                    long scaledSpeed =
+                            wm.getDlSpeed() * (255 - wm.getDlLoad()) * 8 +
+                                    wm.getUlSpeed() * (255 - wm.getUlLoad()) * 2;
+                    score += Math.min(scaledSpeed, 255000000L) >>> 23;
+                    // Max value is 30 capped at 100Mb/s
+                }
+            }
+
+            IPAddressTypeAvailabilityElement ipa =
+                    (IPAddressTypeAvailabilityElement) anqp.get(ANQPElementType.ANQPIPAddrAvailability);
+
+            if (ipa != null) {
+                Integer as14 = sIP4Scores.get(ipa.getV4Availability());
+                Integer as16 = sIP6Scores.get(ipa.getV6Availability());
+                as14 = as14 != null ? as14 : 1;
+                as16 = as16 != null ? as16 : 1;
+                // Is IPv4 twice as important as IPv6???
+                score += as14 * 2 + as16;
+            }
+
+            HSConnectionCapabilityElement cce =
+                    (HSConnectionCapabilityElement) anqp.get(ANQPElementType.HSConnCapability);
+
+            if (cce != null) {
+                score = Math.min(Math.max(protoScore(cce) >> 3, -10), 10);
+            }
+        }
+
+        mScore = score;
+    }
+
+    public PasspointMatch getPasspointMatch() {
+        return mPasspointMatch;
+    }
+
+    public ScanDetail getScanDetail() {
+        return mScanDetail; 
+    }
+
+    public NetworkDetail getNetworkDetail() {
+        return mScanDetail.getNetworkDetail(); 
+    }
+
+
+    public HomeSP getHomeSP() {
+        return mHomeSP;
+    }
+
+    public int getScore() {
+        return mScore;
+    }
+
+    @Override
+    public int compareTo(PasspointMatchInfo that) {
+        return getScore() - that.getScore();
+    }
+
+    private static int protoScore(HSConnectionCapabilityElement cce) {
+        int score = 0;
+        for (HSConnectionCapabilityElement.ProtocolTuple tuple : cce.getStatusList()) {
+            int sign = tuple.getStatus() == HSConnectionCapabilityElement.ProtoStatus.Open ?
+                    1 : -1;
+
+            int elementScore = 1;
+            if (tuple.getProtocol() == IPPROTO_ICMP) {
+                elementScore = 1;
+            }
+            else if (tuple.getProtocol() == IPPROTO_ESP) {
+                elementScore = 5;
+            }
+            else {
+                Map<Integer, Integer> protoMap = sPortScores.get(tuple.getProtocol());
+                if (protoMap != null) {
+                    Integer portScore = protoMap.get(tuple.getPort());
+                    elementScore = portScore != null ? portScore : 0;
+                }
+            }
+            score += elementScore * sign;
+        }
+        return score;
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        if (thatObject == null || getClass() != thatObject.getClass()) {
+            return false;
+        }
+
+        PasspointMatchInfo that = (PasspointMatchInfo)thatObject;
+
+        return getNetworkDetail().equals(that.getNetworkDetail()) &&
+                getHomeSP().equals(that.getHomeSP()) &&
+                getPasspointMatch().equals(that.getPasspointMatch());
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mPasspointMatch != null ? mPasspointMatch.hashCode() : 0;
+        result = 31 * result + getNetworkDetail().hashCode();
+        result = 31 * result + (mHomeSP != null ? mHomeSP.hashCode() : 0);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "PasspointMatchInfo{" +
+                ", mPasspointMatch=" + mPasspointMatch +
+                ", mNetworkInfo=" + getNetworkDetail().getSSID() +
+                ", mHomeSP=" + mHomeSP.getFQDN() +
+                '}';
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/SupplicantBridge.java b/service/java/com/android/server/wifi/hotspot2/SupplicantBridge.java
new file mode 100644
index 0000000..40b6223
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/SupplicantBridge.java
@@ -0,0 +1,457 @@
+package com.android.server.wifi.hotspot2;
+
+import android.util.Log;
+
+import com.android.server.wifi.ScanDetail;
+import com.android.server.wifi.WifiConfigStore;
+import com.android.server.wifi.WifiNative;
+import com.android.server.wifi.anqp.ANQPElement;
+import com.android.server.wifi.anqp.ANQPFactory;
+import com.android.server.wifi.anqp.Constants;
+import com.android.server.wifi.anqp.eap.AuthParam;
+import com.android.server.wifi.anqp.eap.EAP;
+import com.android.server.wifi.anqp.eap.EAPMethod;
+import com.android.server.wifi.hotspot2.pps.Credential;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class SupplicantBridge {
+    private final WifiNative mSupplicantHook;
+    private final WifiConfigStore mConfigStore;
+    private final Map<Long, ScanDetail> mRequestMap = new HashMap<>();
+
+    private static final Map<String, Constants.ANQPElementType> sWpsNames = new HashMap<>();
+
+    static {
+        sWpsNames.put("anqp_venue_name", Constants.ANQPElementType.ANQPVenueName);
+        sWpsNames.put("anqp_network_auth_type", Constants.ANQPElementType.ANQPNwkAuthType);
+        sWpsNames.put("anqp_roaming_consortium", Constants.ANQPElementType.ANQPRoamingConsortium);
+        sWpsNames.put("anqp_ip_addr_type_availability",
+                Constants.ANQPElementType.ANQPIPAddrAvailability);
+        sWpsNames.put("anqp_nai_realm", Constants.ANQPElementType.ANQPNAIRealm);
+        sWpsNames.put("anqp_3gpp", Constants.ANQPElementType.ANQP3GPPNetwork);
+        sWpsNames.put("anqp_domain_name", Constants.ANQPElementType.ANQPDomName);
+        sWpsNames.put("hs20_operator_friendly_name", Constants.ANQPElementType.HSFriendlyName);
+        sWpsNames.put("hs20_wan_metrics", Constants.ANQPElementType.HSWANMetrics);
+        sWpsNames.put("hs20_connection_capability", Constants.ANQPElementType.HSConnCapability);
+        sWpsNames.put("hs20_operating_class", Constants.ANQPElementType.HSOperatingclass);
+        sWpsNames.put("hs20_osu_providers_list", Constants.ANQPElementType.HSOSUProviders);
+    }
+
+    public static boolean isAnqpAttribute(String line) {
+        int split = line.indexOf('=');
+        return split >= 0 && sWpsNames.containsKey(line.substring(0, split));
+    }
+
+    public SupplicantBridge(WifiNative supplicantHook, WifiConfigStore configStore) {
+        mSupplicantHook = supplicantHook;
+        mConfigStore = configStore;
+    }
+
+    public static Map<Constants.ANQPElementType, ANQPElement> parseANQPLines(List<String> lines) {
+        if (lines == null) {
+            return null;
+        }
+        Map<Constants.ANQPElementType, ANQPElement> elements = new HashMap<>(lines.size());
+        for (String line : lines) {
+            try {
+                ANQPElement element = buildElement(line);
+                if (element != null) {
+                    elements.put(element.getID(), element);
+                }
+            }
+            catch (ProtocolException pe) {
+                Log.e(Utils.hs2LogTag(SupplicantBridge.class), "Failed to parse ANQP: " + pe);
+            }
+        }
+        return elements;
+    }
+
+    public void startANQP(ScanDetail scanDetail) {
+        String anqpGet = buildWPSQueryRequest(scanDetail.getNetworkDetail());
+        synchronized (mRequestMap) {
+            mRequestMap.put(scanDetail.getNetworkDetail().getBSSID(), scanDetail);
+        }
+        String result = mSupplicantHook.doCustomCommand(anqpGet);
+        if (result != null && result.startsWith("OK")) {
+            Log.d(Utils.hs2LogTag(getClass()), "ANQP initiated on " + scanDetail);
+        }
+        else {
+            Log.d(Utils.hs2LogTag(getClass()), "ANQP failed on " +
+                    scanDetail + ": " + result);
+        }
+    }
+
+    public void notifyANQPDone(Long bssid, boolean success) {
+        ScanDetail scanDetail;
+        synchronized (mRequestMap) {
+            scanDetail = mRequestMap.remove(bssid);
+        }
+        if (scanDetail == null) {
+            Log.d(Utils.hs2LogTag(getClass()), String.format("Spurious %s ANQP response for %012x",
+                            success ? "successful" : "failed", bssid));
+            return;
+        }
+
+        String bssData = mSupplicantHook.scanResult(scanDetail.getBSSIDString());
+        try {
+            Map<Constants.ANQPElementType, ANQPElement> elements = parseWPSData(bssData);
+            Log.d(Utils.hs2LogTag(getClass()), String.format("%s ANQP response for %012x: %s",
+                    success ? "successful" : "failed", bssid, elements));
+            mConfigStore.notifyANQPResponse(scanDetail, success ? elements : null);
+        }
+        catch (IOException ioe) {
+            Log.e(Utils.hs2LogTag(getClass()), "Failed to parse ANQP: " +
+                    ioe.toString() + ": " + bssData);
+        }
+        catch (RuntimeException rte) {
+            Log.e(Utils.hs2LogTag(getClass()), "Failed to parse ANQP: " +
+                    rte.toString() + ": " + bssData, rte);
+        }
+        mConfigStore.notifyANQPResponse(scanDetail, null);
+    }
+
+    /*
+    public boolean addCredential(HomeSP homeSP, NetworkDetail networkDetail) {
+        Credential credential = homeSP.getCredential();
+        if (credential == null)
+            return false;
+
+        String nwkID = null;
+        if (mLastSSID != null) {
+            String nwkList = mSupplicantHook.doCustomCommand("LIST_NETWORKS");
+
+            BufferedReader reader = new BufferedReader(new StringReader(nwkList));
+            String line;
+            try {
+                while ((line = reader.readLine()) != null) {
+                    String[] tokens = line.split("\\t");
+                    if (tokens.length < 2 || ! Utils.isDecimal(tokens[0])) {
+                        continue;
+                    }
+                    if (unescapeSSID(tokens[1]).equals(mLastSSID)) {
+                        nwkID = tokens[0];
+                        Log.d("HS2J", "Network " + tokens[0] +
+                                " matches last SSID '" + mLastSSID + "'");
+                        break;
+                    }
+                }
+            }
+            catch (IOException ioe) {
+                //
+            }
+        }
+
+        if (nwkID == null) {
+            nwkID = mSupplicantHook.doCustomCommand("ADD_NETWORK");
+            Log.d("HS2J", "add_network: '" + nwkID + "'");
+            if (! Utils.isDecimal(nwkID)) {
+                return false;
+            }
+        }
+
+        List<String> credCommand = getWPSNetCommands(nwkID, networkDetail, credential);
+        for (String command : credCommand) {
+            String status = mSupplicantHook.doCustomCommand(command);
+            Log.d("HS2J", "Status of '" + command + "': '" + status + "'");
+        }
+
+        if (! networkDetail.getSSID().equals(mLastSSID)) {
+            mLastSSID = networkDetail.getSSID();
+            PrintWriter out = null;
+            try {
+                out = new PrintWriter(new OutputStreamWriter(
+                        new FileOutputStream(mLastSSIDFile, false), StandardCharsets.UTF_8));
+                out.println(mLastSSID);
+            } catch (IOException ioe) {
+            //
+            } finally {
+                if (out != null) {
+                    out.close();
+                }
+            }
+        }
+
+        return true;
+    }
+    */
+
+    private static String escapeSSID(NetworkDetail networkDetail) {
+        return escapeString(networkDetail.getSSID(), networkDetail.isSSID_UTF8());
+    }
+
+    private static String escapeString(String s, boolean utf8) {
+        boolean asciiOnly = true;
+        for (int n = 0; n < s.length(); n++) {
+            char ch = s.charAt(n);
+            if (ch > 127) {
+                asciiOnly = false;
+                break;
+            }
+        }
+
+        if (asciiOnly) {
+            return '"' + s + '"';
+        }
+        else {
+            byte[] octets = s.getBytes(utf8 ? StandardCharsets.UTF_8 : StandardCharsets.ISO_8859_1);
+
+            StringBuilder sb = new StringBuilder();
+            for (byte octet : octets) {
+                sb.append(String.format("%02x", octet & Constants.BYTE_MASK));
+            }
+            return sb.toString();
+        }
+    }
+
+    private static String buildWPSQueryRequest(NetworkDetail networkDetail) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("ANQP_GET ").append(networkDetail.getBSSIDString()).append(' ');
+
+        boolean first = true;
+        for (Constants.ANQPElementType elementType : ANQPFactory.getBaseANQPSet()) {
+            if (networkDetail.getAnqpOICount() == 0 &&
+                    elementType == Constants.ANQPElementType.ANQPRoamingConsortium) {
+                continue;
+            }
+            if (first) {
+                first = false;
+            }
+            else {
+                sb.append(',');
+            }
+            sb.append(Constants.getANQPElementID(elementType));
+        }
+        if (networkDetail.getHSRelease() != null) {
+            for (Constants.ANQPElementType elementType : ANQPFactory.getHS20ANQPSet()) {
+                sb.append(",hs20:").append(Constants.getHS20ElementID(elementType));
+            }
+        }
+        return sb.toString();
+    }
+
+    private static List<String> getWPSNetCommands(String netID, NetworkDetail networkDetail,
+                                                 Credential credential) {
+
+        List<String> commands = new ArrayList<String>();
+
+        EAPMethod eapMethod = credential.getEAPMethod();
+        commands.add(String.format("SET_NETWORK %s key_mgmt WPA-EAP", netID));
+        commands.add(String.format("SET_NETWORK %s ssid %s", netID, escapeSSID(networkDetail)));
+        commands.add(String.format("SET_NETWORK %s bssid %s",
+                netID, networkDetail.getBSSIDString()));
+        commands.add(String.format("SET_NETWORK %s eap %s",
+                netID, mapEAPMethodName(eapMethod.getEAPMethodID())));
+
+        AuthParam authParam = credential.getEAPMethod().getAuthParam();
+        if (authParam == null) {
+            return null;            // TLS or SIM/AKA
+        }
+        switch (authParam.getAuthInfoID()) {
+            case NonEAPInnerAuthType:
+            case InnerAuthEAPMethodType:
+                commands.add(String.format("SET_NETWORK %s identity %s",
+                        netID, escapeString(credential.getUserName(), true)));
+                commands.add(String.format("SET_NETWORK %s password %s",
+                        netID, escapeString(credential.getPassword(), true)));
+                commands.add(String.format("SET_NETWORK %s anonymous_identity \"anonymous\"",
+                        netID));
+                break;
+            default:                // !!! Needs work.
+                return null;
+        }
+        commands.add(String.format("SET_NETWORK %s priority 0", netID));
+        commands.add(String.format("ENABLE_NETWORK %s", netID));
+        commands.add(String.format("SAVE_CONFIG"));
+        return commands;
+    }
+
+    private static Map<Constants.ANQPElementType, ANQPElement> parseWPSData(String bssInfo)
+            throws IOException {
+        Map<Constants.ANQPElementType, ANQPElement> elements = new HashMap<>();
+        if (bssInfo == null) {
+            return elements;
+        }
+        BufferedReader lineReader = new BufferedReader(new StringReader(bssInfo));
+        String line;
+        while ((line=lineReader.readLine()) != null) {
+            ANQPElement element = buildElement(line);
+            if (element != null) {
+                elements.put(element.getID(), element);
+            }
+        }
+        return elements;
+    }
+
+    private static ANQPElement buildElement(String text) throws ProtocolException {
+        int separator = text.indexOf('=');
+        if (separator < 0) {
+            return null;
+        }
+
+        String elementName = text.substring(0, separator);
+        Constants.ANQPElementType elementType = sWpsNames.get(elementName);
+        if (elementType == null) {
+            return null;
+        }
+
+        byte[] payload;
+        try {
+            payload = Utils.hexToBytes(text.substring(separator + 1));
+        }
+        catch (NumberFormatException nfe) {
+            Log.e(Utils.hs2LogTag(SupplicantBridge.class), "Failed to parse hex string");
+            return null;
+        }
+        return Constants.getANQPElementID(elementType) != null ?
+                ANQPFactory.buildElement(ByteBuffer.wrap(payload), elementType, payload.length) :
+                ANQPFactory.buildHS20Element(elementType,
+                        ByteBuffer.wrap(payload).order(ByteOrder.LITTLE_ENDIAN));
+    }
+
+    private static String mapEAPMethodName(EAP.EAPMethodID eapMethodID) {
+        switch (eapMethodID) {
+            case EAP_AKA:
+                return "AKA";
+            case EAP_AKAPrim:
+                return "AKA'";  // eap.c:1514
+            case EAP_SIM:
+                return "SIM";
+            case EAP_TLS:
+                return "TLS";
+            case EAP_TTLS:
+                return "TTLS";
+            default:
+                throw new IllegalArgumentException("No mapping for " + eapMethodID);
+        }
+    }
+
+    private static final Map<Character,Integer> sMappings = new HashMap<Character, Integer>();
+
+    static {
+        sMappings.put('\\', (int)'\\');
+        sMappings.put('"', (int)'"');
+        sMappings.put('e', 0x1b);
+        sMappings.put('n', (int)'\n');
+        sMappings.put('r', (int)'\n');
+        sMappings.put('t', (int)'\t');
+    }
+
+    public static String unescapeSSID(String ssid) {
+
+        CharIterator chars = new CharIterator(ssid);
+        byte[] octets = new byte[ssid.length()];
+        int bo = 0;
+
+        while (chars.hasNext()) {
+            char ch = chars.next();
+            if (ch != '\\' || ! chars.hasNext()) {
+                octets[bo++] = (byte)ch;
+            }
+            else {
+                char suffix = chars.next();
+                Integer mapped = sMappings.get(suffix);
+                if (mapped != null) {
+                    octets[bo++] = mapped.byteValue();
+                }
+                else if (suffix == 'x' && chars.hasDoubleHex()) {
+                    octets[bo++] = (byte)chars.nextDoubleHex();
+                }
+                else {
+                    octets[bo++] = '\\';
+                    octets[bo++] = (byte)suffix;
+                }
+            }
+        }
+
+        boolean asciiOnly = true;
+        for (byte b : octets) {
+            if ((b&0x80) != 0) {
+                asciiOnly = false;
+                break;
+            }
+        }
+        if (asciiOnly) {
+            return new String(octets, 0, bo, StandardCharsets.UTF_8);
+        } else {
+            try {
+                // If UTF-8 decoding is successful it is almost certainly UTF-8
+                CharBuffer cb = StandardCharsets.UTF_8.newDecoder().decode(
+                        ByteBuffer.wrap(octets, 0, bo));
+                return cb.toString();
+            } catch (CharacterCodingException cce) {
+                return new String(octets, 0, bo, StandardCharsets.ISO_8859_1);
+            }
+        }
+    }
+
+    private static class CharIterator {
+        private final String mString;
+        private int mPosition;
+        private int mHex;
+
+        private CharIterator(String s) {
+            mString = s;
+        }
+
+        private boolean hasNext() {
+            return mPosition < mString.length();
+        }
+
+        private char next() {
+            return mString.charAt(mPosition++);
+        }
+
+        private boolean hasDoubleHex() {
+            if (mString.length() - mPosition < 2) {
+                return false;
+            }
+            int nh = Utils.fromHex(mString.charAt(mPosition), true);
+            if (nh < 0) {
+                return false;
+            }
+            int nl = Utils.fromHex(mString.charAt(mPosition + 1), true);
+            if (nl < 0) {
+                return false;
+            }
+            mPosition += 2;
+            mHex = (nh << 4) | nl;
+            return true;
+        }
+
+        private int nextDoubleHex() {
+            return mHex;
+        }
+    }
+
+    private static final String[] TestStrings = {
+            "test-ssid",
+            "test\\nss\\tid",
+            "test\\x2d\\x5f\\nss\\tid",
+            "test\\x2d\\x5f\\nss\\tid\\\\",
+            "test\\x2d\\x5f\\nss\\tid\\n",
+            "test\\x2d\\x5f\\nss\\tid\\x4a",
+            "another\\",
+            "an\\other",
+            "another\\x2"
+    };
+
+    public static void main(String[] args) {
+        for (String string : TestStrings) {
+            System.out.println(unescapeSSID(string));
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/Utils.java b/service/java/com/android/server/wifi/hotspot2/Utils.java
new file mode 100644
index 0000000..aa84853
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/Utils.java
@@ -0,0 +1,267 @@
+package com.android.server.wifi.hotspot2;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.TimeZone;
+
+import static com.android.server.wifi.anqp.Constants.BYTE_MASK;
+import static com.android.server.wifi.anqp.Constants.NIBBLE_MASK;
+
+public abstract class Utils {
+
+    public static final long UNSET_TIME = -1;
+
+    private static final String[] PLMNText = {"org", "3gppnetwork", "mcc*", "mnc*", "wlan" };
+
+    public static String hs2LogTag(Class c) {
+        return "HS20";
+    }
+
+    public static List<String> splitDomain(String domain) {
+
+        if (domain.endsWith("."))
+            domain = domain.substring(0, domain.length() - 1);
+        int at = domain.indexOf('@');
+        if (at >= 0)
+            domain = domain.substring(at + 1);
+
+        String[] labels = domain.toLowerCase().split("\\.");
+        LinkedList<String> labelList = new LinkedList<String>();
+        for (String label : labels) {
+            labelList.addFirst(label);
+        }
+
+        return labelList;
+    }
+
+    public static long parseMac(String s) {
+
+        long mac = 0;
+        int count = 0;
+        for (int n = 0; n < s.length(); n++) {
+            int nibble = Utils.fromHex(s.charAt(n), true);  // Set lenient to not blow up on ':'
+            if (nibble >= 0) {                              // ... and use only legit hex.
+                mac = (mac << 4) | nibble;
+                count++;
+            }
+        }
+        if (count < 12 || (count&1) == 1) {
+            throw new IllegalArgumentException("Bad MAC address: '" + s + "'");
+        }
+        return mac;
+    }
+
+    public static String getMccMnc(List<String> domain) {
+        if (domain.size() != PLMNText.length) {
+            return null;
+        }
+
+        for (int n = 0; n < PLMNText.length; n++ ) {
+            String expect = PLMNText[n];
+            int len = expect.endsWith("*") ? expect.length() - 1 : expect.length();
+            if (!domain.get(n).regionMatches(0, expect, 0, len)) {
+                return null;
+            }
+        }
+
+        String prefix = domain.get(2).substring(3) + domain.get(3).substring(3);
+        for (int n = 0; n < prefix.length(); n++) {
+            char ch = prefix.charAt(n);
+            if (ch < '0' || ch > '9') {
+                return null;
+            }
+        }
+        return prefix;
+    }
+
+    public static String roamingConsortiumsToString(long[] ois) {
+        if (ois == null) {
+            return "null";
+        }
+        List<Long> list = new ArrayList<Long>(ois.length);
+        for (long oi : ois) {
+            list.add(oi);
+        }
+        return roamingConsortiumsToString(list);
+    }
+
+    public static String roamingConsortiumsToString(Collection<Long> ois) {
+        StringBuilder sb = new StringBuilder();
+        boolean first = true;
+        for (long oi : ois) {
+            if (first) {
+                first = false;
+            } else {
+                sb.append(", ");
+            }
+            if (Long.numberOfLeadingZeros(oi) > 40) {
+                sb.append(String.format("%06x", oi));
+            } else {
+                sb.append(String.format("%010x", oi));
+            }
+        }
+        return sb.toString();
+    }
+
+    public static String toUnicodeEscapedString(String s) {
+        StringBuilder sb = new StringBuilder(s.length());
+        for (int n = 0; n < s.length(); n++) {
+            char ch = s.charAt(n);
+            if (ch>= ' ' && ch < 127) {
+                sb.append(ch);
+            }
+            else {
+                sb.append("\\u").append(String.format("%04x", (int)ch));
+            }
+        }
+        return sb.toString();
+    }
+
+    public static String toHexString(byte[] data) {
+        if (data == null) {
+            return "null";
+        }
+        StringBuilder sb = new StringBuilder(data.length * 3);
+
+        boolean first = true;
+        for (byte b : data) {
+            if (first) {
+                first = false;
+            } else {
+                sb.append(' ');
+            }
+            sb.append(String.format("%02x", b & BYTE_MASK));
+        }
+        return sb.toString();
+    }
+
+    public static String toHex(byte[] octets) {
+        StringBuilder sb = new StringBuilder(octets.length * 2);
+        for (byte o : octets) {
+            sb.append(String.format("%02x", o & BYTE_MASK));
+        }
+        return sb.toString();
+    }
+
+    public static byte[] hexToBytes(String text) {
+        if ((text.length() & 1) == 1) {
+            throw new NumberFormatException("Odd length hex string: " + text.length());
+        }
+        byte[] data = new byte[text.length() >> 1];
+        int position = 0;
+        for (int n = 0; n < text.length(); n += 2) {
+            data[position] =
+                    (byte) (((fromHex(text.charAt(n), false) & NIBBLE_MASK) << 4) |
+                            (fromHex(text.charAt(n + 1), false) & NIBBLE_MASK));
+            position++;
+        }
+        return data;
+    }
+
+    public static int fromHex(char ch, boolean lenient) throws NumberFormatException {
+        if (ch <= '9' && ch >= '0') {
+            return ch - '0';
+        } else if (ch >= 'a' && ch <= 'f') {
+            return ch + 10 - 'a';
+        } else if (ch <= 'F' && ch >= 'A') {
+            return ch + 10 - 'A';
+        } else if (lenient) {
+            return -1;
+        } else {
+            throw new NumberFormatException("Bad hex-character: " + ch);
+        }
+    }
+
+    private static char toAscii(int b) {
+        return b >= ' ' && b < 0x7f ? (char) b : '.';
+    }
+
+    static boolean isDecimal(String s) {
+        for (int n = 0; n < s.length(); n++) {
+            char ch = s.charAt(n);
+            if (ch < '0' || ch > '9') {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public static <T extends Comparable> int compare(Comparable<T> c1, T c2) {
+        if (c1 == null) {
+            return c2 == null ? 0 : -1;
+        }
+        else if (c2 == null) {
+            return 1;
+        }
+        else {
+            return c1.compareTo(c2);
+        }
+    }
+
+    public static String bytesToBingoCard(ByteBuffer data, int len) {
+        ByteBuffer dup = data.duplicate();
+        dup.limit(dup.position() + len);
+        return bytesToBingoCard(dup);
+    }
+
+    public static String bytesToBingoCard(ByteBuffer data) {
+        ByteBuffer dup = data.duplicate();
+        StringBuilder sbx = new StringBuilder();
+        while (dup.hasRemaining()) {
+            sbx.append(String.format("%02x ", dup.get() & BYTE_MASK));
+        }
+        dup = data.duplicate();
+        sbx.append(' ');
+        while (dup.hasRemaining()) {
+            sbx.append(String.format("%c", toAscii(dup.get() & BYTE_MASK)));
+        }
+        return sbx.toString();
+    }
+
+    public static String toHMS(long millis) {
+        long time = millis >= 0 ? millis : -millis;
+        long tmp = time / 1000L;
+        long ms = time - tmp * 1000L;
+
+        time = tmp;
+        tmp /= 60L;
+        long s = time - tmp * 60L;
+
+        time = tmp;
+        tmp /= 60L;
+        long m = time - tmp * 60L;
+
+        return String.format("%s%d:%02d:%02d.%03d", millis < 0 ? "-" : "", tmp, m, s, ms);
+    }
+
+    public static String toUTCString(long ms) {
+        if (ms < 0) {
+            return "unset";
+        }
+        Calendar c = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+        c.setTimeInMillis(ms);
+        return String.format("%4d/%02d/%02d %2d:%02d:%02dZ",
+                c.get(Calendar.YEAR),
+                c.get(Calendar.MONTH) + 1,
+                c.get(Calendar.DAY_OF_MONTH),
+                c.get(Calendar.HOUR_OF_DAY),
+                c.get(Calendar.MINUTE),
+                c.get(Calendar.SECOND));
+    }
+
+    public static String unquote(String s) {
+        if (s == null) {
+            return null;
+        }
+        else if (s.startsWith("\"") && s.endsWith("\"")) {
+            return s.substring(1, s.length()-1);
+        }
+        else {
+            return s;
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/omadm/MOManager.java b/service/java/com/android/server/wifi/hotspot2/omadm/MOManager.java
new file mode 100644
index 0000000..0ea75ae
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/omadm/MOManager.java
@@ -0,0 +1,711 @@
+package com.android.server.wifi.hotspot2.omadm;
+
+import android.util.Base64;
+import android.util.Log;
+
+import com.android.server.wifi.IMSIParameter;
+import com.android.server.wifi.anqp.eap.EAP;
+import com.android.server.wifi.anqp.eap.EAPMethod;
+import com.android.server.wifi.anqp.eap.ExpandedEAPMethod;
+import com.android.server.wifi.anqp.eap.InnerAuthEAP;
+import com.android.server.wifi.anqp.eap.NonEAPInnerAuth;
+import com.android.server.wifi.hotspot2.Utils;
+import com.android.server.wifi.hotspot2.pps.Credential;
+import com.android.server.wifi.hotspot2.pps.HomeSP;
+
+import org.xml.sax.SAXException;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TimeZone;
+
+/**
+ * Handles provisioning of PerProviderSubscription data.
+ */
+public class MOManager {
+
+    public static final String TAG_AAAServerTrustRoot = "AAAServerTrustRoot";
+    public static final String TAG_AbleToShare = "AbleToShare";
+    public static final String TAG_CertificateType = "CertificateType";
+    public static final String TAG_CertSHA256Fingerprint = "CertSHA256Fingerprint";
+    public static final String TAG_CertURL = "CertURL";
+    public static final String TAG_CheckAAAServerCertStatus = "CheckAAAServerCertStatus";
+    public static final String TAG_Country = "Country";
+    public static final String TAG_CreationDate = "CreationDate";
+    public static final String TAG_Credential = "Credential";
+    public static final String TAG_CredentialPriority = "CredentialPriority";
+    public static final String TAG_DataLimit = "DataLimit";
+    public static final String TAG_DigitalCertificate = "DigitalCertificate";
+    public static final String TAG_DLBandwidth = "DLBandwidth";
+    public static final String TAG_EAPMethod = "EAPMethod";
+    public static final String TAG_EAPType = "EAPType";
+    public static final String TAG_ExpirationDate = "ExpirationDate";
+    public static final String TAG_Extension = "Extension";
+    public static final String TAG_FQDN = "FQDN";
+    public static final String TAG_FQDN_Match = "FQDN_Match";
+    public static final String TAG_FriendlyName = "FriendlyName";
+    public static final String TAG_HESSID = "HESSID";
+    public static final String TAG_HomeOI = "HomeOI";
+    public static final String TAG_HomeOIList = "HomeOIList";
+    public static final String TAG_HomeOIRequired = "HomeOIRequired";
+    public static final String TAG_HomeSP = "HomeSP";
+    public static final String TAG_IconURL = "IconURL";
+    public static final String TAG_IMSI = "IMSI";
+    public static final String TAG_InnerEAPType = "InnerEAPType";
+    public static final String TAG_InnerMethod = "InnerMethod";
+    public static final String TAG_InnerVendorID = "InnerVendorID";
+    public static final String TAG_InnerVendorType = "InnerVendorType";
+    public static final String TAG_IPProtocol = "IPProtocol";
+    public static final String TAG_MachineManaged = "MachineManaged";
+    public static final String TAG_MaximumBSSLoadValue = "MaximumBSSLoadValue";
+    public static final String TAG_MinBackhaulThreshold = "MinBackhaulThreshold";
+    public static final String TAG_NetworkID = "NetworkID";
+    public static final String TAG_NetworkType = "NetworkType";
+    public static final String TAG_Other = "Other";
+    public static final String TAG_OtherHomePartners = "OtherHomePartners";
+    public static final String TAG_Password = "Password";
+    public static final String TAG_PerProviderSubscription = "PerProviderSubscription";
+    public static final String TAG_Policy = "Policy";
+    public static final String TAG_PolicyUpdate = "PolicyUpdate";
+    public static final String TAG_PortNumber = "PortNumber";
+    public static final String TAG_PreferredRoamingPartnerList = "PreferredRoamingPartnerList";
+    public static final String TAG_Priority = "Priority";
+    public static final String TAG_Realm = "Realm";
+    public static final String TAG_RequiredProtoPortTuple = "RequiredProtoPortTuple";
+    public static final String TAG_Restriction = "Restriction";
+    public static final String TAG_RoamingConsortiumOI = "RoamingConsortiumOI";
+    public static final String TAG_SIM = "SIM";
+    public static final String TAG_SoftTokenApp = "SoftTokenApp";
+    public static final String TAG_SPExclusionList = "SPExclusionList";
+    public static final String TAG_SSID = "SSID";
+    public static final String TAG_StartDate = "StartDate";
+    public static final String TAG_SubscriptionParameters = "SubscriptionParameters";
+    public static final String TAG_SubscriptionUpdate = "SubscriptionUpdate";
+    public static final String TAG_TimeLimit = "TimeLimit";
+    public static final String TAG_TrustRoot = "TrustRoot";
+    public static final String TAG_TypeOfSubscription = "TypeOfSubscription";
+    public static final String TAG_ULBandwidth = "ULBandwidth";
+    public static final String TAG_UpdateIdentifier = "UpdateIdentifier";
+    public static final String TAG_UpdateInterval = "UpdateInterval";
+    public static final String TAG_UpdateMethod = "UpdateMethod";
+    public static final String TAG_URI = "URI";
+    public static final String TAG_UsageLimits = "UsageLimits";
+    public static final String TAG_UsageTimePeriod = "UsageTimePeriod";
+    public static final String TAG_Username = "Username";
+    public static final String TAG_UsernamePassword = "UsernamePassword";
+    public static final String TAG_VendorId = "VendorId";
+    public static final String TAG_VendorType = "VendorType";
+
+    private static final DateFormat DTFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+
+    static {
+        DTFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+    }
+
+    private final File mPpsFile;
+    private final boolean mEnabled;
+    private final Map<String, HomeSP> mSPs;
+
+    public MOManager(File ppsFile, boolean hs2enabled) {
+        mPpsFile = ppsFile;
+        mEnabled = hs2enabled;
+        mSPs = new HashMap<>();
+    }
+
+    public File getPpsFile() {
+        return mPpsFile;
+    }
+
+    public boolean isEnabled() {
+        return mEnabled;
+    }
+
+    public boolean isConfigured() {
+        return mEnabled && !mSPs.isEmpty();
+    }
+
+    public Map<String, HomeSP> getLoadedSPs() {
+        return Collections.unmodifiableMap(mSPs);
+    }
+
+    public List<HomeSP> loadAllSPs() throws IOException {
+
+        if (!mEnabled || !mPpsFile.exists()) {
+            return Collections.emptyList();
+        }
+
+        try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(mPpsFile))) {
+            MOTree moTree = MOTree.unmarshal(in);
+            mSPs.clear();
+            if (moTree == null) {
+                return Collections.emptyList();     // Empty file
+            }
+
+            List<HomeSP> sps = buildSPs(moTree);
+            if (sps != null) {
+                for (HomeSP sp : sps) {
+                    if (mSPs.put(sp.getFQDN(), sp) != null) {
+                        throw new OMAException("Multiple SPs for FQDN '" + sp.getFQDN() + "'");
+                    } else {
+                        Log.d(Utils.hs2LogTag(getClass()), "retrieved " + sp.getFQDN() + " from PPS");
+                    }
+                }
+                return sps;
+
+            } else {
+                throw new OMAException("Failed to build HomeSP");
+            }
+        }
+    }
+
+    public static HomeSP buildSP(String xml) throws IOException, SAXException {
+        OMAParser omaParser = new OMAParser();
+        MOTree tree = omaParser.parse(xml, OMAConstants.LOC_PPS + ":1.0");
+        List<HomeSP> spList = buildSPs(tree);
+        if (spList.size() != 1) {
+            throw new OMAException("Expected exactly one HomeSP, got " + spList.size());
+        }
+        return spList.iterator().next();
+    }
+
+    public HomeSP addSP(String xml) throws IOException, SAXException {
+        OMAParser omaParser = new OMAParser();
+        MOTree tree = omaParser.parse(xml, OMAConstants.LOC_PPS + ":1.0");
+        List<HomeSP> spList = buildSPs(tree);
+        if (spList.size() != 1) {
+            throw new OMAException("Expected exactly one HomeSP, got " + spList.size());
+        }
+        HomeSP sp = spList.iterator().next();
+        String fqdn = sp.getFQDN();
+        if (mSPs.put(fqdn, sp) != null) {
+            throw new OMAException("SP " + fqdn + " already exists");
+        }
+
+        BufferedOutputStream out = null;
+        try {
+            out = new BufferedOutputStream(new FileOutputStream(mPpsFile, true));
+            tree.marshal(out);
+            out.flush();
+        } finally {
+            if (out != null) {
+                try {
+                    out.close();
+                } catch (IOException ioe) {
+                    /**/
+                }
+            }
+        }
+
+        return sp;
+    }
+
+    public HomeSP getHomeSP(String fqdn) {
+        return mSPs.get(fqdn);
+    }
+
+    public void addSP(HomeSP homeSP) throws IOException {
+        if (!mEnabled) {
+            throw new IOException("HS2.0 not enabled on this device");
+        }
+        if (mSPs.containsKey(homeSP.getFQDN())) {
+            Log.d(Utils.hs2LogTag(getClass()), "HS20 profile for " +
+                    homeSP.getFQDN() + " already exists");
+            return;
+        }
+        Log.d(Utils.hs2LogTag(getClass()), "Adding new HS20 profile for " + homeSP.getFQDN());
+        mSPs.put(homeSP.getFQDN(), homeSP);
+        writeMO(mSPs.values(), mPpsFile);
+    }
+
+    public void removeSP(String fqdn) throws IOException {
+        if (mSPs.remove(fqdn) == null) {
+            Log.d(Utils.hs2LogTag(getClass()), "No HS20 profile to delete for " + fqdn);
+            return;
+        }
+        Log.d(Utils.hs2LogTag(getClass()), "Deleting HS20 profile for " + fqdn);
+        writeMO(mSPs.values(), mPpsFile);
+    }
+
+    public void updateAndSaveAllSps(Collection<HomeSP> homeSPs) throws IOException {
+
+        boolean dirty = false;
+        List<HomeSP> newSet = new ArrayList<>(homeSPs.size());
+
+        Map<String, HomeSP> spClone = new HashMap<>(mSPs);
+        for (HomeSP homeSP : homeSPs) {
+            Log.d(Utils.hs2LogTag(getClass()), "Passed HomeSP: " + homeSP);
+            HomeSP existing = spClone.remove(homeSP.getFQDN());
+            if (existing == null) {
+                dirty = true;
+                newSet.add(homeSP);
+                Log.d(Utils.hs2LogTag(getClass()), "New HomeSP");
+            }
+            else if (!homeSP.deepEquals(existing)) {
+                dirty = true;
+                newSet.add(homeSP.getClone(existing.getCredential().getPassword()));
+                Log.d(Utils.hs2LogTag(getClass()), "Non-equal HomeSP: " + existing);
+            }
+            else {
+                newSet.add(existing);
+                Log.d(Utils.hs2LogTag(getClass()), "Keeping HomeSP: " + existing);
+            }
+        }
+
+        Log.d(Utils.hs2LogTag(getClass()),
+                String.format("Saving all SPs (%s): current %s (%d), new %s (%d)",
+                dirty ? "dirty" : "clean",
+                fqdnList(mSPs.values()), mSPs.size(),
+                fqdnList(newSet), newSet.size()));
+
+        if (!dirty && spClone.isEmpty()) {
+            Log.d(Utils.hs2LogTag(getClass()), "Not persisting");
+            return;
+        }
+
+        rewriteMO(newSet, mSPs, mPpsFile);
+    }
+
+    private static void rewriteMO(Collection<HomeSP> homeSPs, Map<String, HomeSP> current, File f)
+            throws IOException {
+
+        current.clear();
+
+        OMAConstructed ppsNode = new OMAConstructed(null, TAG_PerProviderSubscription, null);
+        int instance = 0;
+        for (HomeSP homeSP : homeSPs) {
+            buildHomeSPTree(homeSP, ppsNode, instance++);
+            current.put(homeSP.getFQDN(), homeSP);
+        }
+
+        MOTree tree = new MOTree(OMAConstants.LOC_PPS + ":1.0", "1.2", ppsNode);
+        try (BufferedOutputStream out =
+                     new BufferedOutputStream(new FileOutputStream(f, false))) {
+            tree.marshal(out);
+            out.flush();
+        }
+    }
+
+    private static void writeMO(Collection<HomeSP> homeSPs, File f) throws IOException {
+
+        OMAConstructed ppsNode = new OMAConstructed(null, TAG_PerProviderSubscription, null);
+        int instance = 0;
+        for (HomeSP homeSP : homeSPs) {
+            buildHomeSPTree(homeSP, ppsNode, instance++);
+        }
+
+        MOTree tree = new MOTree(OMAConstants.LOC_PPS + ":1.0", "1.2", ppsNode);
+        try (BufferedOutputStream out =
+                     new BufferedOutputStream(new FileOutputStream(f, false))) {
+            tree.marshal(out);
+            out.flush();
+        }
+    }
+
+    private static String fqdnList(Collection<HomeSP> sps) {
+        StringBuilder sb = new StringBuilder();
+        boolean first = true;
+        for (HomeSP sp : sps) {
+            if (first) {
+                first = false;
+            }
+            else {
+                sb.append(", ");
+            }
+            sb.append(sp.getFQDN());
+        }
+        return sb.toString();
+    }
+
+    private static void buildHomeSPTree(HomeSP homeSP, OMAConstructed root, int spInstance)
+            throws IOException {
+        OMANode providerSubNode = root.addChild(getInstanceString(spInstance), null, null, null);
+
+        // The HomeSP:
+        OMANode homeSpNode = providerSubNode.addChild(TAG_HomeSP, null, null, null);
+        if (!homeSP.getSSIDs().isEmpty()) {
+            OMAConstructed nwkIDNode =
+                    (OMAConstructed) homeSpNode.addChild(TAG_NetworkID, null, null, null);
+            int instance = 0;
+            for (Map.Entry<String, Long> entry : homeSP.getSSIDs().entrySet()) {
+                OMAConstructed inode =
+                        (OMAConstructed) nwkIDNode.addChild(getInstanceString(instance++), null, null, null);
+                inode.addChild(TAG_SSID, null, entry.getKey(), null);
+                if (entry.getValue() != null) {
+                    inode.addChild(TAG_HESSID, null, String.format("%012x", entry.getValue()), null);
+                }
+            }
+        }
+
+        homeSpNode.addChild(TAG_FriendlyName, null, homeSP.getFriendlyName(), null);
+
+        if (homeSP.getIconURL() != null) {
+            homeSpNode.addChild(TAG_IconURL, null, homeSP.getIconURL(), null);
+        }
+
+        homeSpNode.addChild(TAG_FQDN, null, homeSP.getFQDN(), null);
+
+        if (!homeSP.getMatchAllOIs().isEmpty() || !homeSP.getMatchAnyOIs().isEmpty()) {
+            OMAConstructed homeOIList =
+                    (OMAConstructed) homeSpNode.addChild(TAG_HomeOIList, null, null, null);
+
+            int instance = 0;
+            for (Long oi : homeSP.getMatchAllOIs()) {
+                OMAConstructed inode =
+                        (OMAConstructed) homeOIList.addChild(getInstanceString(instance++),
+                                null, null, null);
+                inode.addChild(TAG_HomeOI, null, String.format("%x", oi), null);
+                inode.addChild(TAG_HomeOIRequired, null, "TRUE", null);
+            }
+            for (Long oi : homeSP.getMatchAnyOIs()) {
+                OMAConstructed inode =
+                        (OMAConstructed) homeOIList.addChild(getInstanceString(instance++),
+                                null, null, null);
+                inode.addChild(TAG_HomeOI, null, String.format("%x", oi), null);
+                inode.addChild(TAG_HomeOIRequired, null, "FALSE", null);
+            }
+        }
+
+        if (!homeSP.getOtherHomePartners().isEmpty()) {
+            OMAConstructed otherPartners =
+                    (OMAConstructed) homeSpNode.addChild(TAG_OtherHomePartners, null, null, null);
+            int instance = 0;
+            for (String fqdn : homeSP.getOtherHomePartners()) {
+                OMAConstructed inode =
+                        (OMAConstructed) otherPartners.addChild(getInstanceString(instance++),
+                                null, null, null);
+                inode.addChild(TAG_FQDN, null, fqdn, null);
+            }
+        }
+
+        if (!homeSP.getRoamingConsortiums().isEmpty()) {
+            homeSpNode.addChild(TAG_RoamingConsortiumOI, null, getRCList(homeSP.getRoamingConsortiums()), null);
+        }
+
+        // The Credential:
+        OMANode credentialNode = providerSubNode.addChild(TAG_Credential, null, null, null);
+        Credential cred = homeSP.getCredential();
+        EAPMethod method = cred.getEAPMethod();
+
+        if (cred.getCtime() > 0) {
+            credentialNode.addChild(TAG_CreationDate,
+                    null, DTFormat.format(new Date(cred.getCtime())), null);
+        }
+        if (cred.getExpTime() > 0) {
+            credentialNode.addChild(TAG_ExpirationDate,
+                    null, DTFormat.format(new Date(cred.getExpTime())), null);
+        }
+
+        if (method.getEAPMethodID() == EAP.EAPMethodID.EAP_SIM
+                || method.getEAPMethodID() == EAP.EAPMethodID.EAP_AKA
+                || method.getEAPMethodID() == EAP.EAPMethodID.EAP_AKAPrim) {
+
+            OMANode simNode = credentialNode.addChild(TAG_SIM, null, null, null);
+            simNode.addChild(TAG_IMSI, null, cred.getImsi().toString(), null);
+            simNode.addChild(TAG_EAPType, null,
+                    Integer.toString(EAP.mapEAPMethod(method.getEAPMethodID())), null);
+
+        } else if (method.getEAPMethodID() == EAP.EAPMethodID.EAP_TTLS) {
+
+            OMANode unpNode = credentialNode.addChild(TAG_UsernamePassword, null, null, null);
+            unpNode.addChild(TAG_Username, null, cred.getUserName(), null);
+            unpNode.addChild(TAG_Password, null,
+                    Base64.encodeToString(cred.getPassword().getBytes(StandardCharsets.UTF_8),
+                            Base64.DEFAULT), null);
+            OMANode eapNode = unpNode.addChild(TAG_EAPMethod, null, null, null);
+            eapNode.addChild(TAG_EAPType, null,
+                    Integer.toString(EAP.mapEAPMethod(method.getEAPMethodID())), null);
+            eapNode.addChild(TAG_InnerMethod, null,
+                    ((NonEAPInnerAuth) method.getAuthParam()).getOMAtype(), null);
+
+        } else if (method.getEAPMethodID() == EAP.EAPMethodID.EAP_TLS) {
+
+            OMANode certNode = credentialNode.addChild(TAG_DigitalCertificate, null, null, null);
+            certNode.addChild(TAG_CertificateType, null, Credential.CertTypeX509, null);
+            certNode.addChild(TAG_CertSHA256Fingerprint, null,
+                    Utils.toHex(cred.getFingerPrint()), null);
+
+        } else {
+            throw new OMAException("Invalid credential on " + homeSP.getFQDN());
+        }
+
+        credentialNode.addChild(TAG_Realm, null, cred.getRealm(), null);
+
+        // !!! Note: This node defines CRL checking through OSCP, I suspect we won't be able
+        // to do that so it is commented out:
+        //credentialNode.addChild(TAG_CheckAAAServerCertStatus, null, "TRUE", null);
+    }
+
+    private static String getInstanceString(int instance) {
+        return "i" + instance;
+    }
+
+    private static String getRCList(Collection<Long> rcs) {
+        StringBuilder builder = new StringBuilder();
+        boolean first = true;
+        for (Long roamingConsortium : rcs) {
+            if (first) {
+                first = false;
+            }
+            else {
+                builder.append(',');
+            }
+            builder.append(String.format("%x", roamingConsortium));
+        }
+        return builder.toString();
+    }
+
+    private static List<HomeSP> buildSPs(MOTree moTree) throws OMAException {
+        OMAConstructed spList;
+        if (moTree.getRoot().getName().equals(TAG_PerProviderSubscription)) {
+            // The PPS file is rooted at PPS instead of MgmtTree to conserve space
+            spList = moTree.getRoot();
+        }
+        else {
+            List<String> spPath = Arrays.asList(TAG_PerProviderSubscription);
+            spList = moTree.getRoot().getListValue(spPath.iterator());
+        }
+
+        List<HomeSP> homeSPs = new ArrayList<>();
+
+        if (spList == null) {
+            return homeSPs;
+        }
+        for (OMANode spRoot : spList.getChildren()) {
+            homeSPs.add(buildHomeSP(spRoot));
+        }
+
+        return homeSPs;
+    }
+
+    private static HomeSP buildHomeSP(OMANode ppsRoot) throws OMAException {
+        OMANode spRoot = ppsRoot.getChild(TAG_HomeSP);
+
+        String fqdn = spRoot.getScalarValue(Arrays.asList(TAG_FQDN).iterator());
+        String friendlyName = spRoot.getScalarValue(Arrays.asList(TAG_FriendlyName).iterator());
+        String iconURL = spRoot.getScalarValue(Arrays.asList(TAG_IconURL).iterator());
+
+        HashSet<Long> roamingConsortiums = new HashSet<>();
+        String oiString = spRoot.getScalarValue(Arrays.asList(TAG_RoamingConsortiumOI).iterator());
+        if (oiString != null) {
+            for (String oi : oiString.split(",")) {
+                roamingConsortiums.add(Long.parseLong(oi.trim(), 16));
+            }
+        }
+
+        Map<String, Long> ssids = new HashMap<>();
+
+        OMANode ssidListNode = spRoot.getListValue(Arrays.asList(TAG_NetworkID).iterator());
+        if (ssidListNode != null) {
+            for (OMANode ssidRoot : ssidListNode.getChildren()) {
+                OMANode hessidNode = ssidRoot.getChild(TAG_HESSID);
+                ssids.put(ssidRoot.getChild(TAG_SSID).getValue(), getMac(hessidNode));
+            }
+        }
+
+        Set<Long> matchAnyOIs = new HashSet<>();
+        List<Long> matchAllOIs = new ArrayList<>();
+        OMANode homeOIListNode = spRoot.getListValue(Arrays.asList(TAG_HomeOIList).iterator());
+        if (homeOIListNode != null) {
+            for (OMANode homeOIRoot : homeOIListNode.getChildren()) {
+                String homeOI = homeOIRoot.getChild(TAG_HomeOI).getValue();
+                if (Boolean.parseBoolean(homeOIRoot.getChild(TAG_HomeOIRequired).getValue())) {
+                    matchAllOIs.add(Long.parseLong(homeOI, 16));
+                } else {
+                    matchAnyOIs.add(Long.parseLong(homeOI, 16));
+                }
+            }
+        }
+
+        Set<String> otherHomePartners = new HashSet<>();
+        OMANode otherListNode =
+                spRoot.getListValue(Arrays.asList(TAG_OtherHomePartners).iterator());
+        if (otherListNode != null) {
+            for (OMANode fqdnNode : otherListNode.getChildren()) {
+                otherHomePartners.add(fqdnNode.getChild(TAG_FQDN).getValue());
+            }
+        }
+
+        Credential credential = buildCredential(ppsRoot.getChild(TAG_Credential));
+
+        return new HomeSP(ssids, fqdn, roamingConsortiums, otherHomePartners,
+                matchAnyOIs, matchAllOIs, friendlyName, iconURL, credential);
+    }
+
+    private static Credential buildCredential(OMANode credNode) throws OMAException {
+        long ctime = getTime(credNode.getChild(TAG_CreationDate));
+        long expTime = getTime(credNode.getChild(TAG_ExpirationDate));
+        String realm = getString(credNode.getChild(TAG_Realm));
+        boolean checkAAACert = getBoolean(credNode.getChild(TAG_CheckAAAServerCertStatus));
+
+        OMANode unNode = credNode.getChild(TAG_UsernamePassword);
+        OMANode certNode = credNode.getChild(TAG_DigitalCertificate);
+        OMANode simNode = credNode.getChild(TAG_SIM);
+
+        int alternatives = 0;
+        alternatives += unNode != null ? 1 : 0;
+        alternatives += certNode != null ? 1 : 0;
+        alternatives += simNode != null ? 1 : 0;
+        if (alternatives != 1) {
+            throw new OMAException("Expected exactly one credential type, got " + alternatives);
+        }
+
+        if (unNode != null) {
+            String userName = getString(unNode.getChild(TAG_Username));
+            String password = getString(unNode.getChild(TAG_Password));
+            boolean machineManaged = getBoolean(unNode.getChild(TAG_MachineManaged));
+            String softTokenApp = getString(unNode.getChild(TAG_SoftTokenApp));
+            boolean ableToShare = getBoolean(unNode.getChild(TAG_AbleToShare));
+
+            OMANode eapMethodNode = unNode.getChild(TAG_EAPMethod);
+            int eapID = getInteger(eapMethodNode.getChild(TAG_EAPType));
+
+            EAP.EAPMethodID eapMethodID = EAP.mapEAPMethod(eapID);
+            if (eapMethodID == null) {
+                throw new OMAException("Unknown EAP method: " + eapID);
+            }
+
+            Long vid = getOptionalInteger(eapMethodNode.getChild(TAG_VendorId));
+            Long vtype = getOptionalInteger(eapMethodNode.getChild(TAG_VendorType));
+            Long innerEAPType = getOptionalInteger(eapMethodNode.getChild(TAG_InnerEAPType));
+            EAP.EAPMethodID innerEAPMethod = null;
+            if (innerEAPType != null) {
+                innerEAPMethod = EAP.mapEAPMethod(innerEAPType.intValue());
+                if (innerEAPMethod == null) {
+                    throw new OMAException("Bad inner EAP method: " + innerEAPType);
+                }
+            }
+
+            Long innerVid = getOptionalInteger(eapMethodNode.getChild(TAG_InnerVendorID));
+            Long innerVtype = getOptionalInteger(eapMethodNode.getChild(TAG_InnerVendorType));
+            String innerNonEAPMethod = getString(eapMethodNode.getChild(TAG_InnerMethod));
+
+            EAPMethod eapMethod;
+            if (innerEAPMethod != null) {
+                eapMethod = new EAPMethod(eapMethodID, new InnerAuthEAP(innerEAPMethod));
+            } else if (vid != null) {
+                eapMethod = new EAPMethod(eapMethodID,
+                        new ExpandedEAPMethod(EAP.AuthInfoID.ExpandedEAPMethod,
+                                vid.intValue(), vtype));
+            } else if (innerVid != null) {
+                eapMethod =
+                        new EAPMethod(eapMethodID, new ExpandedEAPMethod(EAP.AuthInfoID
+                                .ExpandedInnerEAPMethod, innerVid.intValue(), innerVtype));
+            } else if (innerNonEAPMethod != null) {
+                eapMethod = new EAPMethod(eapMethodID, new NonEAPInnerAuth(innerNonEAPMethod));
+            } else {
+                throw new OMAException("Incomplete set of EAP parameters");
+            }
+
+            return new Credential(ctime, expTime, realm, checkAAACert, eapMethod, userName,
+                    password, machineManaged, softTokenApp, ableToShare);
+        }
+        if (certNode != null) {
+            try {
+                String certTypeString = getString(certNode.getChild(TAG_CertificateType));
+                byte[] fingerPrint = getOctets(certNode.getChild(TAG_CertSHA256Fingerprint));
+
+                EAPMethod eapMethod = new EAPMethod(EAP.EAPMethodID.EAP_TLS, null);
+
+                return new Credential(ctime, expTime, realm, checkAAACert, eapMethod,
+                        Credential.mapCertType(certTypeString), fingerPrint);
+            }
+            catch (NumberFormatException nfe) {
+                throw new OMAException("Bad hex string: " + nfe.toString());
+            }
+        }
+        if (simNode != null) {
+            try {
+                IMSIParameter imsi = new IMSIParameter(getString(simNode.getChild(TAG_IMSI)));
+
+                EAPMethod eapMethod =
+                        new EAPMethod(EAP.mapEAPMethod(getInteger(simNode.getChild(TAG_EAPType))),
+                                null);
+
+                return new Credential(ctime, expTime, realm, checkAAACert, eapMethod, imsi);
+            }
+            catch (IOException ioe) {
+                throw new OMAException("Failed to parse IMSI: " + ioe);
+            }
+        }
+        throw new OMAException("Missing credential parameters");
+    }
+
+    private static boolean getBoolean(OMANode boolNode) {
+        return boolNode != null && Boolean.parseBoolean(boolNode.getValue());
+    }
+
+    private static String getString(OMANode stringNode) {
+        return stringNode != null ? stringNode.getValue() : null;
+    }
+
+    private static int getInteger(OMANode intNode) throws OMAException {
+        if (intNode == null) {
+            throw new OMAException("Missing integer value");
+        }
+        try {
+            return Integer.parseInt(intNode.getValue());
+        } catch (NumberFormatException nfe) {
+            throw new OMAException("Invalid integer: " + intNode.getValue());
+        }
+    }
+
+    private static Long getMac(OMANode macNode) throws OMAException {
+        if (macNode == null) {
+            return null;
+        }
+        try {
+            return Long.parseLong(macNode.getValue(), 16);
+        } catch (NumberFormatException nfe) {
+            throw new OMAException("Invalid MAC: " + macNode.getValue());
+        }
+    }
+
+    private static Long getOptionalInteger(OMANode intNode) throws OMAException {
+        if (intNode == null) {
+            return null;
+        }
+        try {
+            return Long.parseLong(intNode.getValue());
+        } catch (NumberFormatException nfe) {
+            throw new OMAException("Invalid integer: " + intNode.getValue());
+        }
+    }
+
+    private static long getTime(OMANode timeNode) throws OMAException {
+        if (timeNode == null) {
+            return Utils.UNSET_TIME;
+        }
+        String timeText = timeNode.getValue();
+        try {
+            Date date = DTFormat.parse(timeText);
+            return date.getTime();
+        } catch (ParseException pe) {
+            throw new OMAException("Badly formatted time: " + timeText);
+        }
+    }
+
+    private static byte[] getOctets(OMANode octetNode) throws OMAException {
+        if (octetNode == null) {
+            throw new OMAException("Missing byte value");
+        }
+        return Utils.hexToBytes(octetNode.getValue());
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/omadm/MOTree.java b/service/java/com/android/server/wifi/hotspot2/omadm/MOTree.java
new file mode 100644
index 0000000..96df05f
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/omadm/MOTree.java
@@ -0,0 +1,224 @@
+package com.android.server.wifi.hotspot2.omadm;
+
+import android.util.Log;
+
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+
+public class MOTree {
+    public static final String MgmtTreeTag = "MgmtTree";
+
+    private static final String NodeTag = "Node";
+    private static final String NodeNameTag = "NodeName";
+    private static final String PathTag = "Path";
+    private static final String ValueTag = "Value";
+    private static final String RTPropTag = "RTProperties";
+    private static final String TypeTag = "Type";
+    private static final String DDFNameTag = "DDFName";
+
+    private final String mUrn;
+    private final String mDtdRev;
+    private final OMAConstructed mRoot;
+
+    public MOTree(XMLNode node, String urn) throws IOException, SAXException {
+        Iterator<XMLNode> children = node.getChildren().iterator();
+
+        String dtdRev = null;
+
+        while (children.hasNext()) {
+            XMLNode child = children.next();
+            if (child.getTag().equals(OMAConstants.SyncMLVersionTag)) {
+                dtdRev = child.getText();
+                children.remove();
+                break;
+            }
+        }
+
+        mUrn = urn;
+        mDtdRev = dtdRev;
+
+        mRoot = new OMAConstructed(null, MgmtTreeTag, null);
+
+        for (XMLNode child : node.getChildren()) {
+            buildNode(mRoot, child);
+        }
+    }
+
+    public MOTree(String urn, String rev, OMAConstructed root) {
+        mUrn = urn;
+        mDtdRev = rev;
+        mRoot = root;
+    }
+
+    private static class NodeData {
+        private final String mName;
+        private String mPath;
+        private String mValue;
+
+        private NodeData(String name) {
+            mName = name;
+        }
+
+        private void setPath(String path) {
+            mPath = path;
+        }
+
+        private void setValue(String value) {
+            mValue = value;
+        }
+
+        public String getName() {
+            return mName;
+        }
+
+        public String getPath() {
+            return mPath;
+        }
+
+        public String getValue() {
+            return mValue;
+        }
+    }
+
+    private static void buildNode(OMANode parent, XMLNode node) throws IOException {
+        if (!node.getTag().equals(NodeTag))
+            throw new IOException("Node is a '" + node.getTag() + "' instead of a 'Node'");
+
+        Map<String, XMLNode> checkMap = new HashMap<String, XMLNode>(3);
+        String context = null;
+        List<NodeData> values = new ArrayList<NodeData>();
+        List<XMLNode> children = new ArrayList<XMLNode>();
+
+        NodeData curValue = null;
+
+        for (XMLNode child : node.getChildren()) {
+            XMLNode old = checkMap.put(child.getTag(), child);
+
+            if (child.getTag().equals(NodeNameTag)) {
+                if (curValue != null)
+                    throw new IOException(NodeNameTag + " not expected");
+                curValue = new NodeData(child.getText());
+
+            } else if (child.getTag().equals(PathTag)) {
+                if (curValue == null || curValue.getPath() != null)
+                    throw new IOException(PathTag + " not expected");
+                curValue.setPath(child.getText());
+
+            } else if (child.getTag().equals(ValueTag)) {
+                if (!children.isEmpty())
+                    throw new IOException(ValueTag + " in constructed node");
+                if (curValue == null || curValue.getValue() != null)
+                    throw new IOException(ValueTag + " not expected");
+                curValue.setValue(child.getText());
+                values.add(curValue);
+                curValue = null;
+
+            } else if (child.getTag().equals(RTPropTag)) {
+                if (old != null)
+                    throw new IOException("Duplicate " + RTPropTag);
+                XMLNode typeNode = getNextNode(child, TypeTag);
+                XMLNode ddfName = getNextNode(typeNode, DDFNameTag);
+                context = ddfName.getText();
+                if (context == null)
+                    throw new IOException("No text in " + DDFNameTag);
+
+            } else if (child.getTag().equals(NodeTag)) {
+                if (!values.isEmpty())
+                    throw new IOException("Scalar node " + node.getText() + " has Node child");
+                children.add(child);
+
+            }
+        }
+
+        if (values.isEmpty()) {
+            if (curValue == null)
+                throw new IOException("Missing name");
+
+            OMANode subNode = parent.addChild(curValue.getName(),
+                    context, null, curValue.getPath());
+
+            for (XMLNode child : children) {
+                buildNode(subNode, child);
+            }
+        } else {
+            if (!children.isEmpty())
+                throw new IOException("Got both sub nodes and value(s)");
+
+            for (NodeData nodeData : values) {
+                parent.addChild(nodeData.getName(), context,
+                        nodeData.getValue(), nodeData.getPath());
+            }
+        }
+    }
+
+    private static XMLNode getNextNode(XMLNode node, String tag) throws IOException {
+        if (node == null)
+            throw new IOException("No node for " + tag);
+        if (node.getChildren().size() != 1)
+            throw new IOException("Expected " + node.getTag() + " to have exactly one child");
+        XMLNode child = node.getChildren().iterator().next();
+        if (!child.getTag().equals(tag))
+            throw new IOException("Expected " + node.getTag() + " to have child '" + tag +
+                    "' instead of '" + child.getTag() + "'");
+        return child;
+    }
+
+    public String getUrn() {
+        return mUrn;
+    }
+
+    public String getDtdRev() {
+        return mDtdRev;
+    }
+
+    public OMAConstructed getRoot() {
+        return mRoot;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("MO Tree v").append(mDtdRev).append(", urn ").append(mUrn).append(")\n");
+        sb.append(mRoot);
+
+        return sb.toString();
+    }
+
+    public void marshal(OutputStream out) throws IOException {
+        out.write("tree ".getBytes(StandardCharsets.UTF_8));
+        OMAConstants.serializeString(mDtdRev, out);
+        out.write(String.format("(%s)\n", mUrn).getBytes(StandardCharsets.UTF_8));
+        mRoot.marshal(out, 0);
+    }
+
+    public static MOTree unmarshal(InputStream in) throws IOException {
+        boolean strip = true;
+        StringBuilder tree = new StringBuilder();
+        for (; ; ) {
+            int octet = in.read();
+            if (octet < 0) {
+                return null;
+            } else if (octet > ' ') {
+                tree.append((char) octet);
+                strip = false;
+            } else if (!strip) {
+                break;
+            }
+        }
+        if (!tree.toString().equals("tree")) {
+            throw new IOException("Not a tree: " + tree);
+        }
+
+        String version = OMAConstants.deserializeString(in);
+        String urn = OMAConstants.readURN(in);
+
+        OMAConstructed root = OMANode.unmarshal(in);
+
+        return new MOTree(urn, version, root);
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/omadm/NodeAttribute.java b/service/java/com/android/server/wifi/hotspot2/omadm/NodeAttribute.java
new file mode 100644
index 0000000..1fc1adf
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/omadm/NodeAttribute.java
@@ -0,0 +1,30 @@
+package com.android.server.wifi.hotspot2.omadm;
+
+public class NodeAttribute {
+    private final String mName;
+    private final String mType;
+    private final String mValue;
+
+    public NodeAttribute(String name, String type, String value) {
+        mName = name;
+        mType = type;
+        mValue = value;
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public String getValue() {
+        return mValue;
+    }
+
+    public String getType() {
+        return mType;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("%s (%s) = '%s'", mName, mType, mValue);
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/omadm/OMAConstants.java b/service/java/com/android/server/wifi/hotspot2/omadm/OMAConstants.java
new file mode 100644
index 0000000..1a42341
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/omadm/OMAConstants.java
@@ -0,0 +1,96 @@
+package com.android.server.wifi.hotspot2.omadm;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class OMAConstants {
+    private OMAConstants() {
+    }
+
+    public static final String TAG_PostDevData = "spp:sppPostDevData";
+    public static final String TAG_SupportedVersions = "spp:supportedSPPVersions";
+    public static final String TAG_SupportedMOs = "spp:supportedMOList";
+
+    public static final String TAG_MO_Add = "spp:addMO";
+    public static final String TAG_MO_Container = "spp:moContainer";
+
+    public static final String ATTR_URN = "spp:moURN";
+
+    // Following strings excludes the trailing version number (e.g. :1.0)
+    public static final String LOC_PPS = "urn:wfa:mo:hotspot2dot0-perprovidersubscription";
+    public static final String LOC_DEVINFO =
+            "urn:oma:mo:oma-dm-devinfo:1.0 urn:oma:mo:oma-dm-devdetail";
+    public static final String LOC_DEVDETAIL = "urn:wfa:mo-ext:hotspot2dot0-devdetail-ext";
+
+    public static final String SyncMLVersionTag = "VerDTD";
+    public static final String RequiredSyncMLVersion = "1.2";
+
+    private static final Set<String> sMOContainers = new HashSet<String>();
+
+    static {
+        sMOContainers.add(TAG_MO_Add);
+        sMOContainers.add(TAG_MO_Container);
+    }
+
+    public static boolean isMOContainer(String tag) {
+        return sMOContainers.contains(tag);
+    }
+
+    private static final byte[] INDENT = new byte[1024];
+
+    static {
+        Arrays.fill(INDENT, (byte) ' ');
+    }
+
+    public static void serializeString(String s, OutputStream out) throws IOException {
+        byte[] octets = s.getBytes(StandardCharsets.UTF_8);
+        byte[] prefix = String.format("%x:", octets.length).getBytes(StandardCharsets.UTF_8);
+        out.write(prefix);
+        out.write(octets);
+    }
+
+    public static void indent(int level, OutputStream out) throws IOException {
+        out.write(INDENT, 0, level);
+    }
+
+    public static String deserializeString(InputStream in) throws IOException {
+        StringBuilder prefix = new StringBuilder();
+        for (; ; ) {
+            byte b = (byte) in.read();
+            if (b == '.')
+                return null;
+            else if (b == ':')
+                break;
+            else if (b > ' ')
+                prefix.append((char) b);
+        }
+        int length = Integer.parseInt(prefix.toString(), 16);
+        byte[] octets = new byte[length];
+        int offset = 0;
+        while (offset < octets.length) {
+            int amount = in.read(octets, offset, octets.length - offset);
+            if (amount <= 0)
+                throw new EOFException();
+            offset += amount;
+        }
+        return new String(octets, StandardCharsets.UTF_8);
+    }
+
+    public static String readURN(InputStream in) throws IOException {
+        StringBuilder urn = new StringBuilder();
+
+        for (; ; ) {
+            byte b = (byte) in.read();
+            if (b == ')')
+                break;
+            urn.append((char) b);
+        }
+        return urn.toString();
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/omadm/OMAConstructed.java b/service/java/com/android/server/wifi/hotspot2/omadm/OMAConstructed.java
new file mode 100644
index 0000000..c0fee8f
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/omadm/OMAConstructed.java
@@ -0,0 +1,118 @@
+package com.android.server.wifi.hotspot2.omadm;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+public class OMAConstructed extends OMANode {
+    private final Map<String, OMANode> mChildren;
+
+    public OMAConstructed(OMANode parent, String name, String context) {
+        super(parent, name, context);
+        mChildren = new HashMap<>();
+    }
+
+    @Override
+    public OMANode addChild(String name, String context, String value, String pathString) throws IOException {
+        if (pathString == null) {
+            OMANode child = value != null ?
+                    new OMAScalar(this, name, context, value) :
+                    new OMAConstructed(this, name, context);
+            mChildren.put(name.toLowerCase(), child);
+            return child;
+        } else {
+            OMANode target = this;
+            while (target.getParent() != null)
+                target = target.getParent();
+
+            for (String element : pathString.split("/")) {
+                target = target.getChild(element);
+                if (target == null)
+                    throw new IOException("No child node '" + element + "' in " + getPathString());
+                else if (target.isLeaf())
+                    throw new IOException("Cannot add child to leaf node: " + getPathString());
+            }
+            return target.addChild(name, context, value, null);
+        }
+    }
+
+    public String getScalarValue(Iterator<String> path) throws OMAException {
+        if (!path.hasNext()) {
+            throw new OMAException("Path too short for " + getPathString());
+        }
+        String tag = path.next();
+        OMANode child = mChildren.get(tag.toLowerCase());
+        if (child != null) {
+            return child.getScalarValue(path);
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public OMAConstructed getListValue(Iterator<String> path) throws OMAException {
+        if (!path.hasNext()) {
+            return this;
+        }
+        String tag = path.next();
+        OMANode child = mChildren.get(tag.toLowerCase());
+        if (child != null) {
+            return child.getListValue(path);
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public boolean isLeaf() {
+        return false;
+    }
+
+    @Override
+    public Collection<OMANode> getChildren() {
+        return Collections.unmodifiableCollection(mChildren.values());
+    }
+
+    public OMANode getChild(String name) {
+        return mChildren.get(name.toLowerCase());
+    }
+
+    @Override
+    public String getValue() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void toString(StringBuilder sb, int level) {
+        sb.append(getPathString());
+        if (getContext() != null) {
+            sb.append(" (").append(getContext()).append(')');
+        }
+        sb.append('\n');
+
+        for (OMANode node : mChildren.values()) {
+            node.toString(sb, level + 1);
+        }
+    }
+
+    @Override
+    public void marshal(OutputStream out, int level) throws IOException {
+        OMAConstants.indent(level, out);
+        OMAConstants.serializeString(getName(), out);
+        if (getContext() != null) {
+            out.write(String.format("(%s)", getContext()).getBytes(StandardCharsets.UTF_8));
+        }
+        out.write(new byte[] { '+', '\n' });
+
+        for (OMANode child : mChildren.values()) {
+            child.marshal(out, level + 1);
+        }
+        OMAConstants.indent(level, out);
+        out.write(".\n".getBytes(StandardCharsets.UTF_8));
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/omadm/OMAException.java b/service/java/com/android/server/wifi/hotspot2/omadm/OMAException.java
new file mode 100644
index 0000000..5f8cd11
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/omadm/OMAException.java
@@ -0,0 +1,9 @@
+package com.android.server.wifi.hotspot2.omadm;
+
+import java.io.IOException;
+
+public class OMAException extends IOException {
+    public OMAException(String message) {
+        super(message);
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/omadm/OMANode.java b/service/java/com/android/server/wifi/hotspot2/omadm/OMANode.java
new file mode 100644
index 0000000..5eab73d
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/omadm/OMANode.java
@@ -0,0 +1,124 @@
+package com.android.server.wifi.hotspot2.omadm;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+public abstract class OMANode {
+    private final OMANode mParent;
+    private final String mName;
+    private final String mContext;
+
+    protected OMANode(OMANode parent, String name, String context) {
+        mParent = parent;
+        mName = name;
+        mContext = context;
+    }
+
+    public OMANode getParent() {
+        return mParent;
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public String getContext() {
+        return mContext;
+    }
+
+    public List<String> getPath() {
+        LinkedList<String> path = new LinkedList<>();
+        for (OMANode node = this; node != null; node = node.getParent()) {
+            path.addFirst(node.getName());
+        }
+        return path;
+    }
+
+    public String getPathString() {
+        StringBuilder sb = new StringBuilder();
+        for (String element : getPath()) {
+            sb.append('/').append(element);
+        }
+        return sb.toString();
+    }
+
+    public abstract String getScalarValue(Iterator<String> path) throws OMAException;
+
+    public abstract OMAConstructed getListValue(Iterator<String> path) throws OMAException;
+
+    public abstract boolean isLeaf();
+
+    public abstract Collection<OMANode> getChildren();
+
+    public abstract OMANode getChild(String name);
+
+    public abstract String getValue();
+
+    public abstract OMANode addChild(String name, String context, String value, String path)
+            throws IOException;
+
+    public abstract void marshal(OutputStream out, int level) throws IOException;
+
+    public abstract void toString(StringBuilder sb, int level);
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        toString(sb, 0);
+        return sb.toString();
+    }
+
+    public static OMAConstructed unmarshal(InputStream in) throws IOException {
+        OMANode node = buildNode(in, null);
+        if (node == null || node.isLeaf()) {
+            throw new IOException("Bad OMA tree");
+        }
+        unmarshal(in, (OMAConstructed) node);
+        return (OMAConstructed) node;
+    }
+
+    private static void unmarshal(InputStream in, OMAConstructed parent) throws IOException {
+        for (; ; ) {
+            OMANode node = buildNode(in, parent);
+            if (node == null) {
+                return;
+            }
+            else if (!node.isLeaf()) {
+                unmarshal(in, (OMAConstructed) node);
+            }
+        }
+    }
+
+    private static OMANode buildNode(InputStream in, OMAConstructed parent) throws IOException {
+        String name = OMAConstants.deserializeString(in);
+        if (name == null) {
+            return null;
+        }
+
+        String urn = null;
+        int next = in.read();
+        if (next == '(') {
+            urn = OMAConstants.readURN(in);
+            next = in.read();
+        }
+
+        if (next == '=') {
+            String value = OMAConstants.deserializeString(in);
+            return parent.addChild(name, urn, value, null);
+        } else if (next == '+') {
+            if (parent != null) {
+                return parent.addChild(name, urn, null, null);
+            } else {
+                return new OMAConstructed(null, name, urn);
+            }
+        }
+        else {
+            throw new IOException("Parse error: expected = or + after node name");
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/omadm/OMAParser.java b/service/java/com/android/server/wifi/hotspot2/omadm/OMAParser.java
new file mode 100644
index 0000000..99bcbb9
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/omadm/OMAParser.java
@@ -0,0 +1,63 @@
+package com.android.server.wifi.hotspot2.omadm;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import java.io.*;
+
+/**
+ * Parses an OMA-DM XML tree.
+ */
+public class OMAParser extends DefaultHandler {
+    private XMLNode mRoot;
+    private XMLNode mCurrent;
+
+    public MOTree parse(String text, String urn) throws IOException, SAXException {
+        try {
+            SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
+            parser.parse(new InputSource(new StringReader(text)), this);
+            return new MOTree(mRoot, urn);
+        } catch (ParserConfigurationException pce) {
+            throw new SAXException(pce);
+        }
+    }
+
+    @Override
+    public void startElement(String uri, String localName, String qName, Attributes attributes)
+            throws SAXException {
+        XMLNode parent = mCurrent;
+
+        mCurrent = new XMLNode(mCurrent, qName, attributes);
+
+        if (mRoot == null)
+            mRoot = mCurrent;
+        else
+            parent.addChild(mCurrent);
+    }
+
+    @Override
+    public void endElement(String uri, String localName, String qName) throws SAXException {
+        if (!qName.equals(mCurrent.getTag()))
+            throw new SAXException("End tag '" + qName + "' doesn't match current node: " +
+                    mCurrent);
+
+        try {
+            mCurrent.close();
+        } catch (IOException ioe) {
+            throw new SAXException("Failed to close element", ioe);
+        }
+
+        mCurrent = mCurrent.getParent();
+    }
+
+    @Override
+    public void characters(char[] ch, int start, int length) throws SAXException {
+        mCurrent.addText(ch, start, length);
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/omadm/OMAScalar.java b/service/java/com/android/server/wifi/hotspot2/omadm/OMAScalar.java
new file mode 100644
index 0000000..fa93478
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/omadm/OMAScalar.java
@@ -0,0 +1,70 @@
+package com.android.server.wifi.hotspot2.omadm;
+
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Collection;
+import java.util.Iterator;
+
+public class OMAScalar extends OMANode {
+    private final String mValue;
+
+    public OMAScalar(OMANode parent, String name, String context, String value) {
+        super(parent, name, context);
+        mValue = value;
+    }
+
+    public String getScalarValue(Iterator<String> path) throws OMAException {
+        return mValue;
+    }
+
+    @Override
+    public OMAConstructed getListValue(Iterator<String> path) throws OMAException {
+        throw new OMAException("Scalar encountered in list path: " + getPathString());
+    }
+
+    @Override
+    public boolean isLeaf() {
+        return true;
+    }
+
+    @Override
+    public Collection<OMANode> getChildren() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getValue() {
+        return mValue;
+    }
+
+    @Override
+    public OMANode getChild(String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public OMANode addChild(String name, String context, String value, String path)
+            throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void toString(StringBuilder sb, int level) {
+        sb.append(getPathString()).append('=').append(mValue);
+        if (getContext() != null) {
+            sb.append(" (").append(getContext()).append(')');
+        }
+        sb.append('\n');
+    }
+
+    @Override
+    public void marshal(OutputStream out, int level) throws IOException {
+        OMAConstants.indent(level, out);
+        OMAConstants.serializeString(getName(), out);
+        out.write((byte) '=');
+        OMAConstants.serializeString(getValue(), out);
+        out.write((byte) '\n');
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/omadm/RequestDetail.java b/service/java/com/android/server/wifi/hotspot2/omadm/RequestDetail.java
new file mode 100644
index 0000000..11d5c5b
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/omadm/RequestDetail.java
@@ -0,0 +1,60 @@
+package com.android.server.wifi.hotspot2.omadm;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
+
+public class RequestDetail {
+    private final String mSppversion;
+    private final String mRedirectURI;
+    private final String mRequestReason;
+    private final String mSessionID;
+    private final String[] mSupportedVersions;
+    private final String[] mSupportedMOs;
+    private final Collection<MOTree> m_MOs;
+
+    public enum RequestFields {
+        SPPVersion,
+        RedirectURI,
+        RequestReason,
+        SessionID,
+        SupportedVersions,
+        SupportedMOs
+    }
+
+    public RequestDetail(Map<RequestFields, String> values, Collection<MOTree> mos) {
+        mSppversion = values.get(RequestFields.SPPVersion);
+        mRedirectURI = values.get(RequestFields.RedirectURI);
+        mRequestReason = values.get(RequestFields.RequestReason);
+        mSessionID = values.get(RequestFields.SessionID);
+        mSupportedVersions = split(values.get(RequestFields.SupportedVersions));
+        mSupportedMOs = split(values.get(RequestFields.SupportedMOs));
+        m_MOs = mos;
+    }
+
+    public Collection<MOTree> getMOs() {
+        return m_MOs;
+    }
+
+    private static String[] split(String list) {
+        return list != null ? list.split("[ \n\r]+") : null;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append("SPPVersion").append(" = '").append(mSppversion).append("'\n");
+        sb.append("RedirectURI").append(" = '").append(mRedirectURI).append("'\n");
+        sb.append("RequestReason").append(" = '").append(mRequestReason).append("'\n");
+        sb.append("SessionID").append(" = '").append(mSessionID).append("'\n");
+        sb.append("SupportedVersions").append(" = ").append(Arrays.toString(mSupportedVersions))
+                .append('\n');
+        sb.append("SupportedMOs").append(" = ").append(Arrays.toString(mSupportedMOs)).append('\n');
+        sb.append("MOs:\n");
+        for (MOTree mo : m_MOs)
+            sb.append(mo);
+
+        return sb.toString();
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/omadm/SOAPParser.java b/service/java/com/android/server/wifi/hotspot2/omadm/SOAPParser.java
new file mode 100644
index 0000000..f13c851
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/omadm/SOAPParser.java
@@ -0,0 +1,153 @@
+package com.android.server.wifi.hotspot2.omadm;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import java.io.*;
+import java.util.*;
+
+import static com.android.server.wifi.hotspot2.omadm.RequestDetail.RequestFields.*;
+
+/**
+ * This is an incomplete SOAP-XML parser for OSU data needing enhancements for r2.
+ */
+
+public class SOAPParser extends DefaultHandler {
+    private XMLNode mRoot;
+    private XMLNode mCurrent;
+
+    private static String[] TagOnly = new String[0];
+    private static final Map<RequestDetail.RequestFields, String> sSoapMappings =
+            new EnumMap<RequestDetail.RequestFields, String>(RequestDetail.RequestFields.class);
+    private static final Map<String, RequestDetail.RequestFields> sRevMappings =
+            new HashMap<String, RequestDetail.RequestFields>();
+    private static final Map<String, String[]> sSoapAttributes =
+            new HashMap<String, String[]>();
+
+    static {
+        sSoapMappings.put(SPPVersion, "spp:sppVersion");
+        sSoapMappings.put(RedirectURI, "redirectURI");
+        sSoapMappings.put(RequestReason, "requestReason");
+        sSoapMappings.put(SessionID, "spp:sessionID");
+        sSoapMappings.put(SupportedVersions, "spp:supportedSPPVersions");
+        sSoapMappings.put(SupportedMOs, "spp:supportedMOList");
+
+        for (Map.Entry<RequestDetail.RequestFields, String> entry : sSoapMappings.entrySet()) {
+            sRevMappings.put(entry.getValue(), entry.getKey());
+        }
+
+        // !!! Really: The first element inside the body
+        sSoapAttributes.put("spp:sppPostDevDataResponse", new String[]{
+                sSoapMappings.get(SPPVersion),
+                sSoapMappings.get(RedirectURI),
+                sSoapMappings.get(RequestReason),
+                sSoapMappings.get(SessionID)});
+
+        sSoapAttributes.put(sSoapMappings.get(SupportedVersions), TagOnly);
+        sSoapAttributes.put(sSoapMappings.get(SupportedMOs), TagOnly);
+    }
+
+    public XMLNode parse(File file) throws IOException, ParserConfigurationException, SAXException {
+        SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
+
+        BufferedInputStream in = new BufferedInputStream(new FileInputStream(file));
+        try {
+            parser.parse(in, this);
+        } finally {
+            in.close();
+        }
+        return mRoot;
+    }
+
+    @Override
+    public void startElement(String uri, String localName, String qName, Attributes attributes)
+            throws SAXException {
+        XMLNode parent = mCurrent;
+
+        mCurrent = new XMLNode(mCurrent, qName, attributes);
+        System.out.println("Added " + mCurrent.getTag() + ", atts " + mCurrent.getAttributes());
+
+        if (mRoot == null)
+            mRoot = mCurrent;
+        else
+            parent.addChild(mCurrent);
+    }
+
+    @Override
+    public void endElement(String uri, String localName, String qName) throws SAXException {
+        if (!qName.equals(mCurrent.getTag()))
+            throw new SAXException("End tag '" + qName + "' doesn't match current node: " +
+                    mCurrent);
+
+        try {
+            mCurrent.close();
+        } catch (IOException ioe) {
+            throw new SAXException("Failed to close element", ioe);
+        }
+
+        mCurrent = mCurrent.getParent();
+    }
+
+    @Override
+    public void characters(char[] ch, int start, int length) throws SAXException {
+        mCurrent.addText(ch, start, length);
+    }
+
+    public RequestDetail getRequestDetail() {
+        Map<RequestDetail.RequestFields, String> values =
+                new EnumMap<RequestDetail.RequestFields, String>(RequestDetail.RequestFields.class);
+        List<MOTree> mos = new ArrayList<MOTree>();
+        extractFields(mRoot, values, mos);
+        return new RequestDetail(values, mos);
+    }
+
+    private static void extractFields(XMLNode node, Map<RequestDetail.RequestFields,
+            String> values, Collection<MOTree> mos) {
+        String[] attributes = sSoapAttributes.get(node.getTag());
+
+        if (attributes != null) {
+            if (attributes.length == 0) {
+                RequestDetail.RequestFields field = sRevMappings.get(node.getTag());
+                values.put(field, node.getText());
+            } else {
+                for (String attribute : attributes) {
+                    RequestDetail.RequestFields field = sRevMappings.get(attribute);
+                    if (field != null) {
+                        String value = node.getAttributeValue(attribute);
+
+                        if (value != null)
+                            values.put(field, value);
+                    }
+                }
+            }
+        }
+
+        if (node.getMOTree() != null)
+            mos.add(node.getMOTree());
+
+        for (XMLNode child : node.getChildren()) {
+            extractFields(child, values, mos);
+        }
+    }
+
+    public static void main(String[] args) throws Exception {
+        SOAPParser soapParser = new SOAPParser();
+        XMLNode root = soapParser.parse(new File(args[0]));
+        //System.out.println( root );
+        System.out.println(soapParser.getRequestDetail());
+        System.out.println("Marshalled: ");
+        for (MOTree mo : soapParser.getRequestDetail().getMOs()) {
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+            mo.marshal(out);
+            System.out.println(out.toString());
+            MOTree back = MOTree.unmarshal(new ByteArrayInputStream(out.toByteArray()));
+            System.out.println(back);
+        }
+        System.out.println("---");
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/omadm/XMLNode.java b/service/java/com/android/server/wifi/hotspot2/omadm/XMLNode.java
new file mode 100644
index 0000000..d400e2f
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/omadm/XMLNode.java
@@ -0,0 +1,128 @@
+package com.android.server.wifi.hotspot2.omadm;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class XMLNode {
+    private final String mTag;
+    private final Map<String, NodeAttribute> mAttributes;
+    private final List<XMLNode> mChildren;
+    private final XMLNode mParent;
+    private MOTree mMO;
+    private StringBuilder mTextBuilder;
+    private String mText;
+
+    public XMLNode(XMLNode parent, String tag, Attributes attributes) throws SAXException {
+        mTag = tag;
+
+        mAttributes = new HashMap<String, NodeAttribute>();
+
+        if (attributes.getLength() > 0) {
+            for (int n = 0; n < attributes.getLength(); n++)
+                mAttributes.put(attributes.getQName(n), new NodeAttribute(attributes.getQName(n),
+                        attributes.getType(n), attributes.getValue(n)));
+        }
+
+        mParent = parent;
+        mChildren = new ArrayList<XMLNode>();
+
+        mTextBuilder = new StringBuilder();
+    }
+
+    public void addText(char[] chs, int start, int length) {
+        String s = new String(chs, start, length);
+        String trimmed = s.trim();
+        if (trimmed.isEmpty())
+            return;
+
+        if (s.charAt(0) != trimmed.charAt(0))
+            mTextBuilder.append(' ');
+        mTextBuilder.append(trimmed);
+        if (s.charAt(s.length() - 1) != trimmed.charAt(trimmed.length() - 1))
+            mTextBuilder.append(' ');
+    }
+
+    public void addChild(XMLNode child) {
+        mChildren.add(child);
+    }
+
+    public void close() throws IOException, SAXException {
+        String text = mTextBuilder.toString().trim();
+        StringBuilder filtered = new StringBuilder(text.length());
+        for (int n = 0; n < text.length(); n++) {
+            char ch = text.charAt(n);
+            if (ch >= ' ')
+                filtered.append(ch);
+        }
+
+        mText = filtered.toString();
+        mTextBuilder = null;
+
+        if (OMAConstants.isMOContainer(mTag)) {
+            NodeAttribute urn = mAttributes.get(OMAConstants.ATTR_URN);
+            OMAParser omaParser = new OMAParser();
+            mMO = omaParser.parse(mText, urn.getValue());
+        }
+    }
+
+    public String getTag() {
+        return mTag;
+    }
+
+    public XMLNode getParent() {
+        return mParent;
+    }
+
+    public String getText() {
+        return mText;
+    }
+
+    public Map<String, NodeAttribute> getAttributes() {
+        return Collections.unmodifiableMap(mAttributes);
+    }
+
+    public String getAttributeValue(String name) {
+        NodeAttribute nodeAttribute = mAttributes.get(name);
+        return nodeAttribute != null ? nodeAttribute.getValue() : null;
+    }
+
+    public List<XMLNode> getChildren() {
+        return mChildren;
+    }
+
+    public MOTree getMOTree() {
+        return mMO;
+    }
+
+    private void toString(char[] indent, StringBuilder sb) {
+        Arrays.fill(indent, ' ');
+
+        sb.append(indent).append('<').append(mTag).append("> ").append(mAttributes.values());
+
+        if (mMO != null)
+            sb.append('\n').append(mMO);
+        else if (!mText.isEmpty())
+            sb.append(", text: ").append(mText);
+
+        sb.append('\n');
+
+        char[] subIndent = Arrays.copyOf(indent, indent.length + 2);
+        for (XMLNode child : mChildren)
+            child.toString(subIndent, sb);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        toString(new char[0], sb);
+        return sb.toString();
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/pps/Credential.java b/service/java/com/android/server/wifi/hotspot2/pps/Credential.java
new file mode 100644
index 0000000..4a06021
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/pps/Credential.java
@@ -0,0 +1,357 @@
+package com.android.server.wifi.hotspot2.pps;
+
+import android.net.wifi.WifiEnterpriseConfig;
+import android.security.Credentials;
+import android.security.KeyStore;
+import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
+
+import com.android.server.wifi.IMSIParameter;
+import com.android.server.wifi.anqp.eap.EAP;
+import com.android.server.wifi.anqp.eap.EAPMethod;
+import com.android.server.wifi.anqp.eap.NonEAPInnerAuth;
+import com.android.server.wifi.hotspot2.Utils;
+import com.android.server.wifi.hotspot2.omadm.OMAException;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.util.Arrays;
+
+public class Credential {
+    public enum CertType {IEEE, x509v3}
+
+    public static final String CertTypeX509 = "x509v3";
+    public static final String CertTypeIEEE = "802.1ar";
+
+    private final long mCtime;
+    private final long mExpTime;
+    private final String mRealm;
+    private final boolean mCheckAAACert;
+
+    private final String mUserName;
+    private final String mPassword;
+    private final boolean mDisregardPassword;
+    private final boolean mMachineManaged;
+    private final String mSTokenApp;
+    private final boolean mShare;
+    private final EAPMethod mEAPMethod;
+
+    private final CertType mCertType;
+    private final byte[] mFingerPrint;
+
+    private final IMSIParameter mImsi;
+
+    public Credential(long ctime, long expTime, String realm, boolean checkAAACert,
+                      EAPMethod eapMethod, String userName, String password,
+                      boolean machineManaged, String stApp, boolean share) {
+        mCtime = ctime;
+        mExpTime = expTime;
+        mRealm = realm;
+        mCheckAAACert = checkAAACert;
+        mEAPMethod = eapMethod;
+        mUserName = userName;
+
+        if (!TextUtils.isEmpty(password)) {
+            byte[] pwOctets = Base64.decode(password, Base64.DEFAULT);
+            mPassword = new String(pwOctets, StandardCharsets.UTF_8);
+        } else {
+            mPassword = null;
+        }
+        mDisregardPassword = false;
+
+        mMachineManaged = machineManaged;
+        mSTokenApp = stApp;
+        mShare = share;
+
+        mCertType = null;
+        mFingerPrint = null;
+
+        mImsi = null;
+    }
+
+    public Credential(long ctime, long expTime, String realm, boolean checkAAACert,
+                      EAPMethod eapMethod, Credential.CertType certType, byte[] fingerPrint) {
+        mCtime = ctime;
+        mExpTime = expTime;
+        mRealm = realm;
+        mCheckAAACert = checkAAACert;
+        mEAPMethod = eapMethod;
+        mCertType = certType;
+        mFingerPrint = fingerPrint;
+
+        mUserName = null;
+        mPassword = null;
+        mDisregardPassword = false;
+        mMachineManaged = false;
+        mSTokenApp = null;
+        mShare = false;
+
+        mImsi = null;
+    }
+
+    public Credential(long ctime, long expTime, String realm, boolean checkAAACert,
+                      EAPMethod eapMethod, IMSIParameter imsi) {
+        mCtime = ctime;
+        mExpTime = expTime;
+        mRealm = realm;
+        mCheckAAACert = checkAAACert;
+        mEAPMethod = eapMethod;
+        mImsi = imsi;
+
+        mCertType = null;
+        mFingerPrint = null;
+
+        mUserName = null;
+        mPassword = null;
+        mDisregardPassword = false;
+        mMachineManaged = false;
+        mSTokenApp = null;
+        mShare = false;
+    }
+
+    public Credential(Credential other, String password) {
+        mCtime = other.mCtime;
+        mExpTime = other.mExpTime;
+        mRealm = other.mRealm;
+        mCheckAAACert = other.mCheckAAACert;
+        mUserName = other.mUserName;
+        mPassword = password;
+        mDisregardPassword = other.mDisregardPassword;
+        mMachineManaged = other.mMachineManaged;
+        mSTokenApp = other.mSTokenApp;
+        mShare = other.mShare;
+        mEAPMethod = other.mEAPMethod;
+        mCertType = other.mCertType;
+        mFingerPrint = other.mFingerPrint;
+        mImsi = other.mImsi;
+    }
+
+    public Credential(WifiEnterpriseConfig enterpriseConfig, KeyStore keyStore, boolean update)
+            throws IOException {
+        mCtime = Utils.UNSET_TIME;
+        mExpTime = Utils.UNSET_TIME;
+        mRealm = enterpriseConfig.getRealm();
+        mCheckAAACert = false;
+        mEAPMethod = mapEapMethod(enterpriseConfig.getEapMethod(),
+                enterpriseConfig.getPhase2Method());
+        mCertType = mEAPMethod.getEAPMethodID() == EAP.EAPMethodID.EAP_TLS ? CertType.x509v3 : null;
+        byte[] fingerPrint;
+
+        if (enterpriseConfig.getClientCertificate() != null) {
+            // !!! Not sure this will be true in any practical instances:
+            try {
+                MessageDigest digester = MessageDigest.getInstance("SHA-256");
+                fingerPrint = digester.digest(enterpriseConfig.getClientCertificate().getEncoded());
+            } catch (GeneralSecurityException gse) {
+                Log.e(Utils.hs2LogTag(getClass()),
+                        "Failed to generate certificate fingerprint: " + gse);
+                fingerPrint = null;
+            }
+        } else if (enterpriseConfig.getClientCertificateAlias() != null) {
+            String alias = enterpriseConfig.getClientCertificateAlias();
+            byte[] octets = keyStore.get(Credentials.USER_CERTIFICATE + alias);
+            if (octets != null) {
+                try {
+                    MessageDigest digester = MessageDigest.getInstance("SHA-256");
+                    fingerPrint = digester.digest(octets);
+                } catch (GeneralSecurityException gse) {
+                    Log.e(Utils.hs2LogTag(getClass()), "Failed to construct digest: " + gse);
+                    fingerPrint = null;
+                }
+            } else // !!! The current alias is *not* derived from the fingerprint...
+            {
+                try {
+                    fingerPrint = Base64.decode(enterpriseConfig.getClientCertificateAlias(),
+                            Base64.DEFAULT);
+                } catch (IllegalArgumentException ie) {
+                    Log.e(Utils.hs2LogTag(getClass()), "Bad base 64 alias");
+                    fingerPrint = null;
+                }
+            }
+        } else {
+            fingerPrint = null;
+        }
+        mFingerPrint = fingerPrint;
+        String imsi = enterpriseConfig.getPlmn();
+        mImsi = imsi == null || imsi.length() == 0 ? null : new IMSIParameter(imsi);
+        mUserName = enterpriseConfig.getIdentity();
+        mPassword = enterpriseConfig.getPassword();
+        mDisregardPassword = update && mPassword.length() < 2;
+        mMachineManaged = false;
+        mSTokenApp = null;
+        mShare = false;
+    }
+
+    public static CertType mapCertType(String certType) throws OMAException {
+        if (certType.equalsIgnoreCase(CertTypeX509)) {
+            return CertType.x509v3;
+        } else if (certType.equalsIgnoreCase(CertTypeIEEE)) {
+            return CertType.IEEE;
+        } else {
+            throw new OMAException("Invalid cert type: '" + certType + "'");
+        }
+    }
+
+    private static EAPMethod mapEapMethod(int eapMethod, int phase2Method) throws IOException {
+        switch (eapMethod) {
+            case WifiEnterpriseConfig.Eap.TLS:
+                return new EAPMethod(EAP.EAPMethodID.EAP_TLS, null);
+            case WifiEnterpriseConfig.Eap.TTLS:
+            /* keep this table in sync with WifiEnterpriseConfig.Phase2 enum */
+                NonEAPInnerAuth inner;
+                switch (phase2Method) {
+                    case WifiEnterpriseConfig.Phase2.PAP:
+                        inner = new NonEAPInnerAuth(NonEAPInnerAuth.NonEAPType.PAP);
+                        break;
+                    case WifiEnterpriseConfig.Phase2.MSCHAP:
+                        inner = new NonEAPInnerAuth(NonEAPInnerAuth.NonEAPType.MSCHAP);
+                        break;
+                    case WifiEnterpriseConfig.Phase2.MSCHAPV2:
+                        inner = new NonEAPInnerAuth(NonEAPInnerAuth.NonEAPType.MSCHAPv2);
+                        break;
+                    default:
+                        throw new IOException("TTLS phase2 method " +
+                                phase2Method + " not valid for Passpoint");
+                }
+                return new EAPMethod(EAP.EAPMethodID.EAP_TTLS, inner);
+            case WifiEnterpriseConfig.Eap.SIM:
+                return new EAPMethod(EAP.EAPMethodID.EAP_SIM, null);
+            case WifiEnterpriseConfig.Eap.AKA:
+                return new EAPMethod(EAP.EAPMethodID.EAP_AKA, null);
+            case WifiEnterpriseConfig.Eap.AKA_PRIME:
+                return new EAPMethod(EAP.EAPMethodID.EAP_AKAPrim, null);
+            default:
+                String methodName;
+                if (eapMethod >= 0 && eapMethod < WifiEnterpriseConfig.Eap.strings.length) {
+                    methodName = WifiEnterpriseConfig.Eap.strings[eapMethod];
+                } else {
+                    methodName = Integer.toString(eapMethod);
+                }
+                throw new IOException("EAP method id " + methodName + " is not valid for Passpoint");
+        }
+    }
+
+    public EAPMethod getEAPMethod() {
+        return mEAPMethod;
+    }
+
+    public String getRealm() {
+        return mRealm;
+    }
+
+    public IMSIParameter getImsi() {
+        return mImsi;
+    }
+
+    public String getUserName() {
+        return mUserName;
+    }
+
+    public String getPassword() {
+        return mPassword;
+    }
+
+    public boolean hasDisregardPassword() {
+        return mDisregardPassword;
+    }
+
+    public CertType getCertType() {
+        return mCertType;
+    }
+
+    public byte[] getFingerPrint() {
+        return mFingerPrint;
+    }
+
+    public long getCtime() {
+        return mCtime;
+    }
+
+    public long getExpTime() {
+        return mExpTime;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        Credential that = (Credential) o;
+
+        if (mCheckAAACert != that.mCheckAAACert) return false;
+        if (mCtime != that.mCtime) return false;
+        if (mExpTime != that.mExpTime) return false;
+        if (mMachineManaged != that.mMachineManaged) return false;
+        if (mShare != that.mShare) return false;
+        if (mCertType != that.mCertType) return false;
+        if (!mEAPMethod.equals(that.mEAPMethod)) return false;
+        if (!Arrays.equals(mFingerPrint, that.mFingerPrint)) return false;
+        if (!safeEquals(mImsi, that.mImsi)) {
+            return false;
+        }
+
+        if (!mDisregardPassword && !safeEquals(mPassword, that.mPassword)) {
+            return false;
+        }
+
+        if (!mRealm.equals(that.mRealm)) return false;
+        if (!safeEquals(mSTokenApp, that.mSTokenApp)) {
+            return false;
+        }
+        if (!safeEquals(mUserName, that.mUserName)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    private static boolean safeEquals(Object s1, Object s2) {
+        if (s1 == null) {
+            return s2 == null;
+        }
+        else {
+            return s2 != null && s1.equals(s2);
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        int result = (int) (mCtime ^ (mCtime >>> 32));
+        result = 31 * result + (int) (mExpTime ^ (mExpTime >>> 32));
+        result = 31 * result + mRealm.hashCode();
+        result = 31 * result + (mCheckAAACert ? 1 : 0);
+        result = 31 * result + (mUserName != null ? mUserName.hashCode() : 0);
+        result = 31 * result + (mPassword != null ? mPassword.hashCode() : 0);
+        result = 31 * result + (mMachineManaged ? 1 : 0);
+        result = 31 * result + (mSTokenApp != null ? mSTokenApp.hashCode() : 0);
+        result = 31 * result + (mShare ? 1 : 0);
+        result = 31 * result + mEAPMethod.hashCode();
+        result = 31 * result + (mCertType != null ? mCertType.hashCode() : 0);
+        result = 31 * result + (mFingerPrint != null ? Arrays.hashCode(mFingerPrint) : 0);
+        result = 31 * result + (mImsi != null ? mImsi.hashCode() : 0);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "Credential{" +
+                "mCtime=" + Utils.toUTCString(mCtime) +
+                ", mExpTime=" + Utils.toUTCString(mExpTime) +
+                ", mRealm='" + mRealm + '\'' +
+                ", mCheckAAACert=" + mCheckAAACert +
+                ", mUserName='" + mUserName + '\'' +
+                ", mPassword='" + mPassword + '\'' +
+                ", mDisregardPassword=" + mDisregardPassword +
+                ", mMachineManaged=" + mMachineManaged +
+                ", mSTokenApp='" + mSTokenApp + '\'' +
+                ", mShare=" + mShare +
+                ", mEAPMethod=" + mEAPMethod +
+                ", mCertType=" + mCertType +
+                ", mFingerPrint=" + Utils.toHexString(mFingerPrint) +
+                ", mImsi='" + mImsi + '\'' +
+                '}';
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/pps/DomainMatcher.java b/service/java/com/android/server/wifi/hotspot2/pps/DomainMatcher.java
new file mode 100644
index 0000000..3f6e22a
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/pps/DomainMatcher.java
@@ -0,0 +1,151 @@
+package com.android.server.wifi.hotspot2.pps;
+
+import android.util.Log;
+
+import com.android.server.wifi.hotspot2.Utils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+
+public class DomainMatcher {
+
+    public enum Match {None, Primary, Secondary}
+
+    private final Label mRoot;
+
+    private static class Label {
+        private final Map<String, Label> mSubDomains;
+        private final Match mMatch;
+
+        private Label(Match match) {
+            mMatch = match;
+            mSubDomains = match == Match.None ? new HashMap<String, Label>() : null;
+        }
+
+        private void addDomain(Iterator<String> labels, Match match) {
+            String labelName = labels.next();
+            if (labels.hasNext()) {
+                Label subLabel = new Label(Match.None);
+                mSubDomains.put(labelName, subLabel);
+                subLabel.addDomain(labels, match);
+            } else {
+                mSubDomains.put(labelName, new Label(match));
+            }
+        }
+
+        private Label getSubLabel(String labelString) {
+            return mSubDomains.get(labelString);
+        }
+
+        public Match getMatch() {
+            return mMatch;
+        }
+
+        private void toString(StringBuilder sb) {
+            if (mSubDomains != null) {
+                sb.append(".{");
+                for (Map.Entry<String, Label> entry : mSubDomains.entrySet()) {
+                    sb.append(entry.getKey());
+                    entry.getValue().toString(sb);
+                }
+                sb.append('}');
+            } else {
+                sb.append('=').append(mMatch);
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            toString(sb);
+            return sb.toString();
+        }
+    }
+
+    public DomainMatcher(List<String> primary, List<List<String>> secondary) {
+        mRoot = new Label(Match.None);
+        for (List<String> secondaryLabel : secondary) {
+            mRoot.addDomain(secondaryLabel.iterator(), Match.Secondary);
+        }
+        // Primary overwrites secondary.
+        mRoot.addDomain(primary.iterator(), Match.Primary);
+    }
+
+    /**
+     * Check if domain is either a the same or a sub-domain of any of the domains in the domain tree
+     * in this matcher, i.e. all or or a sub-set of the labels in domain matches a path in the tree.
+     * @param domain Domain to be checked.
+     * @return None if domain is not a sub-domain, Primary if it matched one of the primary domains
+     * or Secondary if it matched on of the secondary domains.
+     */
+    public Match isSubDomain(List<String> domain) {
+
+        Label label = mRoot;
+        for (String labelString : domain) {
+            label = label.getSubLabel(labelString);
+            if (label == null) {
+                return Match.None;
+            } else if (label.getMatch() != Match.None) {
+                return label.getMatch();
+            }
+        }
+        return Match.None;  // Domain is a super domain
+    }
+
+    public static boolean arg2SubdomainOfArg1(List<String> arg1, List<String> arg2) {
+        if (arg2.size() < arg1.size()) {
+            return false;
+        }
+
+        Iterator<String> l1 = arg1.iterator();
+        Iterator<String> l2 = arg2.iterator();
+
+        while(l1.hasNext()) {
+            if (!l1.next().equals(l2.next())) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "Domain matcher " + mRoot;
+    }
+
+    private static final String[] TestDomains = {
+            "garbage.apple.com",
+            "apple.com",
+            "com",
+            "jan.android.google.com.",
+            "jan.android.google.com",
+            "android.google.com",
+            "google.com",
+            "jan.android.google.net.",
+            "jan.android.google.net",
+            "android.google.net",
+            "google.net",
+            "net.",
+            "."
+    };
+
+    public static void main(String[] args) {
+        DomainMatcher dm1 = new DomainMatcher(Utils.splitDomain("android.google.com"),
+                Collections.<List<String>>emptyList());
+        for (String domain : TestDomains) {
+            System.out.println(domain + ": " + dm1.isSubDomain(Utils.splitDomain(domain)));
+        }
+        List<List<String>> secondaries = new ArrayList<List<String>>();
+        secondaries.add(Utils.splitDomain("apple.com"));
+        secondaries.add(Utils.splitDomain("net"));
+        DomainMatcher dm2 = new DomainMatcher(Utils.splitDomain("android.google.com"), secondaries);
+        for (String domain : TestDomains) {
+            System.out.println(domain + ": " + dm2.isSubDomain(Utils.splitDomain(domain)));
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/pps/HomeSP.java b/service/java/com/android/server/wifi/hotspot2/pps/HomeSP.java
new file mode 100644
index 0000000..c8a3da2
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/pps/HomeSP.java
@@ -0,0 +1,313 @@
+package com.android.server.wifi.hotspot2.pps;
+
+import android.util.Log;
+
+import com.android.server.wifi.SIMAccessor;
+import com.android.server.wifi.anqp.ANQPElement;
+import com.android.server.wifi.anqp.CellularNetwork;
+import com.android.server.wifi.anqp.DomainNameElement;
+import com.android.server.wifi.anqp.NAIRealmElement;
+import com.android.server.wifi.anqp.RoamingConsortiumElement;
+import com.android.server.wifi.anqp.ThreeGPPNetworkElement;
+import com.android.server.wifi.hotspot2.AuthMatch;
+import com.android.server.wifi.hotspot2.NetworkDetail;
+import com.android.server.wifi.hotspot2.PasspointMatch;
+import com.android.server.wifi.hotspot2.Utils;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static com.android.server.wifi.anqp.Constants.ANQPElementType;
+
+public class HomeSP {
+    private final Map<String, Long> mSSIDs;        // SSID, HESSID, [0,N]
+    private final String mFQDN;
+    private final DomainMatcher mDomainMatcher;
+    private final Set<String> mOtherHomePartners;
+    private final HashSet<Long> mRoamingConsortiums;    // [0,N]
+    private final Set<Long> mMatchAnyOIs;           // [0,N]
+    private final List<Long> mMatchAllOIs;          // [0,N]
+
+    private final Credential mCredential;
+
+    // Informational:
+    private final String mFriendlyName;             // [1]
+    private final String mIconURL;                  // [0,1]
+
+    public HomeSP(Map<String, Long> ssidMap,
+                   /*@NotNull*/ String fqdn,
+                   /*@NotNull*/ HashSet<Long> roamingConsortiums,
+                   /*@NotNull*/ Set<String> otherHomePartners,
+                   /*@NotNull*/ Set<Long> matchAnyOIs,
+                   /*@NotNull*/ List<Long> matchAllOIs,
+                   String friendlyName,
+                   String iconURL,
+                   Credential credential) {
+
+        mSSIDs = ssidMap;
+        List<List<String>> otherPartners = new ArrayList<>(otherHomePartners.size());
+        for (String otherPartner : otherHomePartners) {
+            otherPartners.add(Utils.splitDomain(otherPartner));
+        }
+        mOtherHomePartners = otherHomePartners;
+        mFQDN = fqdn;
+        mDomainMatcher = new DomainMatcher(Utils.splitDomain(fqdn), otherPartners);
+        mRoamingConsortiums = roamingConsortiums;
+        mMatchAnyOIs = matchAnyOIs;
+        mMatchAllOIs = matchAllOIs;
+        mFriendlyName = friendlyName;
+        mIconURL = iconURL;
+        mCredential = credential;
+    }
+
+    public HomeSP getClone(String password) {
+        if (getCredential().hasDisregardPassword()) {
+            return new HomeSP(mSSIDs,
+                    mFQDN,
+                    mRoamingConsortiums,
+                    mOtherHomePartners,
+                    mMatchAnyOIs,
+                    mMatchAllOIs,
+                    mFriendlyName,
+                    mIconURL,
+                    new Credential(mCredential, password));
+        }
+        else {
+            return this;
+        }
+    }
+
+    public PasspointMatch match(NetworkDetail networkDetail,
+                                Map<ANQPElementType, ANQPElement> anqpElementMap,
+                                SIMAccessor simAccessor) {
+
+        List<String> imsis = simAccessor.getMatchingImsis(mCredential.getImsi());
+
+        PasspointMatch spMatch = matchSP(networkDetail, anqpElementMap, imsis);
+
+        if (spMatch == PasspointMatch.Incomplete || spMatch == PasspointMatch.Declined) {
+            return spMatch;
+        }
+
+        if (imsiMatch(imsis, (ThreeGPPNetworkElement)
+                anqpElementMap.get(ANQPElementType.ANQP3GPPNetwork)) != null) {
+            // PLMN match, promote sp match to roaming if necessary.
+            return spMatch == PasspointMatch.None ? PasspointMatch.RoamingProvider : spMatch;
+        }
+
+        NAIRealmElement naiRealmElement =
+                (NAIRealmElement) anqpElementMap.get(ANQPElementType.ANQPNAIRealm);
+
+        int authMatch = naiRealmElement != null ?
+                naiRealmElement.match(mCredential) :
+                AuthMatch.Indeterminate;
+        
+        Log.d(Utils.hs2LogTag(getClass()), networkDetail.toKeyString() + " match on " + mFQDN +
+                ": " + spMatch + ", auth " + AuthMatch.toString(authMatch));
+
+        if (authMatch == AuthMatch.None) {
+            // Distinct auth mismatch, demote authentication.
+            return PasspointMatch.None;
+        }
+        else if ((authMatch & AuthMatch.Realm) == 0) {
+            // No realm match, return sp match as is.
+            return spMatch;
+        }
+        else {
+            // Realm match, promote sp match to roaming if necessary.
+            return spMatch == PasspointMatch.None ? PasspointMatch.RoamingProvider : spMatch;
+        }
+    }
+
+    public PasspointMatch matchSP(NetworkDetail networkDetail,
+                                Map<ANQPElementType, ANQPElement> anqpElementMap,
+                                List<String> imsis) {
+
+        if (mSSIDs.containsKey(networkDetail.getSSID())) {
+            Long hessid = mSSIDs.get(networkDetail.getSSID());
+            if (hessid == null || networkDetail.getHESSID() == hessid) {
+                Log.d(Utils.hs2LogTag(getClass()), "match SSID");
+                return PasspointMatch.HomeProvider;
+            }
+        }
+
+        Set<Long> anOIs = new HashSet<>();
+
+        if (networkDetail.getRoamingConsortiums() != null) {
+            for (long oi : networkDetail.getRoamingConsortiums()) {
+                anOIs.add(oi);
+            }
+        }
+        RoamingConsortiumElement rcElement = anqpElementMap != null ?
+                (RoamingConsortiumElement) anqpElementMap.get(ANQPElementType.ANQPRoamingConsortium)
+                : null;
+        if (rcElement != null) {
+            anOIs.addAll(rcElement.getOIs());
+        }
+
+        // It may seem reasonable to check for home provider match prior to checking for roaming
+        // relationship, but it is possible to avoid an ANQP query if it turns out that the
+        // "match all" rule fails based only on beacon info only.
+        boolean roamingMatch = false;
+
+        if (!mMatchAllOIs.isEmpty()) {
+            boolean matchesAll = true;
+
+            for (long spOI : mMatchAllOIs) {
+                if (!anOIs.contains(spOI)) {
+                    matchesAll = false;
+                    break;
+                }
+            }
+            if (matchesAll) {
+                roamingMatch = true;
+            }
+            else {
+                if (anqpElementMap != null || networkDetail.getAnqpOICount() == 0) {
+                    return PasspointMatch.Declined;
+                }
+                else {
+                    return PasspointMatch.Incomplete;
+                }
+            }
+        }
+
+        if (!roamingMatch &&
+                (!Collections.disjoint(mMatchAnyOIs, anOIs) ||
+                        !Collections.disjoint(mRoamingConsortiums, anOIs))) {
+            roamingMatch = true;
+        }
+
+        if (anqpElementMap == null) {
+            return PasspointMatch.Incomplete;
+        }
+
+        DomainNameElement domainNameElement =
+                (DomainNameElement) anqpElementMap.get(ANQPElementType.ANQPDomName);
+
+        if (domainNameElement != null) {
+            for (String domain : domainNameElement.getDomains()) {
+                List<String> anLabels = Utils.splitDomain(domain);
+                DomainMatcher.Match match = mDomainMatcher.isSubDomain(anLabels);
+                if (match != DomainMatcher.Match.None) {
+                    return PasspointMatch.HomeProvider;
+                }
+
+                if (imsiMatch(imsis, anLabels) != null) {
+                    return PasspointMatch.HomeProvider;
+                }
+            }
+        }
+
+        return roamingMatch ? PasspointMatch.RoamingProvider : PasspointMatch.None;
+    }
+
+    private String imsiMatch(List<String> imsis, ThreeGPPNetworkElement plmnElement) {
+        if (imsis == null || plmnElement == null || plmnElement.getPlmns().isEmpty()) {
+            return null;
+        }
+        for (CellularNetwork network : plmnElement.getPlmns()) {
+            for (String mccMnc : network) {
+                String imsi = imsiMatch(imsis, mccMnc);
+                if (imsi != null) {
+                    return imsi;
+                }
+            }
+        }
+        return null;
+    }
+
+    private String imsiMatch(List<String> imsis, List<String> fqdn) {
+        if (imsis == null) {
+            return null;
+        }
+        String mccMnc = Utils.getMccMnc(fqdn);
+        return mccMnc != null ? imsiMatch(imsis, mccMnc) : null;
+    }
+
+    private String imsiMatch(List<String> imsis, String mccMnc) {
+        if (mCredential.getImsi().matchesMccMnc(mccMnc)) {
+            for (String imsi : imsis) {
+                if (imsi.startsWith(mccMnc)) {
+                    return imsi;
+                }
+            }
+        }
+        return null;
+    }
+
+    public String getFQDN() { return mFQDN; }
+    public String getFriendlyName() { return mFriendlyName; }
+    public HashSet<Long> getRoamingConsortiums() { return mRoamingConsortiums; }
+    public Credential getCredential() { return mCredential; }
+
+    public Map<String, Long> getSSIDs() {
+        return mSSIDs;
+    }
+
+    public Collection<String> getOtherHomePartners() {
+        return mOtherHomePartners;
+    }
+
+    public Set<Long> getMatchAnyOIs() {
+        return mMatchAnyOIs;
+    }
+
+    public List<Long> getMatchAllOIs() {
+        return mMatchAllOIs;
+    }
+
+    public String getIconURL() {
+        return mIconURL;
+    }
+
+    public boolean deepEquals(HomeSP other) {
+        return mFQDN.equals(other.mFQDN) &&
+                mSSIDs.equals(other.mSSIDs) &&
+                mOtherHomePartners.equals(other.mOtherHomePartners) &&
+                mRoamingConsortiums.equals(other.mRoamingConsortiums) &&
+                mMatchAnyOIs.equals(other.mMatchAnyOIs) &&
+                mMatchAllOIs.equals(other.mMatchAllOIs) &&
+                mFriendlyName.equals(other.mFriendlyName) &&
+                Utils.compare(mIconURL, other.mIconURL) == 0 &&
+                mCredential.equals(other.mCredential);
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        } else if (thatObject == null || getClass() != thatObject.getClass()) {
+            return false;
+        }
+
+        HomeSP that = (HomeSP) thatObject;
+        return mFQDN.equals(that.mFQDN);
+    }
+
+    @Override
+    public int hashCode() {
+        return mFQDN.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return "HomeSP{" +
+                "mSSIDs=" + mSSIDs +
+                ", mFQDN='" + mFQDN + '\'' +
+                ", mDomainMatcher=" + mDomainMatcher +
+                ", mRoamingConsortiums={" + Utils.roamingConsortiumsToString(mRoamingConsortiums) +
+                '}' +
+                ", mMatchAnyOIs={" + Utils.roamingConsortiumsToString(mMatchAnyOIs) + '}' +
+                ", mMatchAllOIs={" + Utils.roamingConsortiumsToString(mMatchAllOIs) + '}' +
+                ", mCredential=" + mCredential +
+                ", mFriendlyName='" + mFriendlyName + '\'' +
+                ", mIconURL='" + mIconURL + '\'' +
+                '}';
+    }
+}
diff --git a/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java b/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
index 1c04e43..9a034ad 100644
--- a/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
+++ b/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
@@ -1779,6 +1779,9 @@
                 .create();
 
             dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+            WindowManager.LayoutParams attrs = dialog.getWindow().getAttributes();
+            attrs.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+            dialog.getWindow().setAttributes(attrs);
             dialog.show();
             mFrequencyConflictDialog = dialog;
         }
@@ -2259,6 +2262,9 @@
             .setPositiveButton(r.getString(R.string.ok), null)
             .create();
         dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+        WindowManager.LayoutParams attrs = dialog.getWindow().getAttributes();
+        attrs.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+        dialog.getWindow().setAttributes(attrs);
         dialog.show();
     }
 
@@ -2287,6 +2293,9 @@
             .setPositiveButton(r.getString(R.string.ok), null)
             .create();
         dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+        WindowManager.LayoutParams attrs = dialog.getWindow().getAttributes();
+        attrs.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+        dialog.getWindow().setAttributes(attrs);
         dialog.show();
     }
 
@@ -2365,6 +2374,9 @@
         }
 
         dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+        WindowManager.LayoutParams attrs = dialog.getWindow().getAttributes();
+        attrs.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+        dialog.getWindow().setAttributes(attrs);
         dialog.show();
     }
 
diff --git a/service/jni/com_android_server_wifi_WifiNative.cpp b/service/jni/com_android_server_wifi_WifiNative.cpp
index e9e85ac..bf073f7 100644
--- a/service/jni/com_android_server_wifi_WifiNative.cpp
+++ b/service/jni/com_android_server_wifi_WifiNative.cpp
@@ -17,17 +17,21 @@
 #define LOG_TAG "wifi"
 
 #include "jni.h"
+#include "JniConstants.h"
 #include <ScopedUtfChars.h>
+#include <ScopedBytes.h>
 #include <utils/misc.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <utils/Log.h>
 #include <utils/String16.h>
 #include <ctype.h>
-
+#include <sys/socket.h>
+#include <linux/if.h>
 #include "wifi.h"
 #include "wifi_hal.h"
 #include "jni_helper.h"
-
+#include "rtt.h"
+#include "wifi_hal_stub.h"
 #define REPLY_BUF_SIZE 4096 // wpa_supplicant's maximum size.
 #define EVENT_BUF_SIZE 2048
 
@@ -35,6 +39,67 @@
 
 static jint DBG = false;
 
+//Please put all HAL function call here and call from the function table instead of directly call
+static wifi_hal_fn hal_fn;
+int init_wifi_hal_func_table(wifi_hal_fn *hal_fn) {
+    if (hal_fn == NULL) {
+        return -1;
+    }
+    hal_fn->wifi_initialize = wifi_initialize_stub;
+    hal_fn->wifi_cleanup = wifi_cleanup_stub;
+    hal_fn->wifi_event_loop = wifi_event_loop_stub;
+    hal_fn->wifi_get_error_info = wifi_get_error_info_stub;
+    hal_fn->wifi_get_supported_feature_set = wifi_get_supported_feature_set_stub;
+    hal_fn->wifi_get_concurrency_matrix = wifi_get_concurrency_matrix_stub;
+    hal_fn->wifi_set_scanning_mac_oui =  wifi_set_scanning_mac_oui_stub;
+    hal_fn->wifi_get_supported_channels = wifi_get_supported_channels_stub;
+    hal_fn->wifi_is_epr_supported = wifi_is_epr_supported_stub;
+    hal_fn->wifi_get_ifaces = wifi_get_ifaces_stub;
+    hal_fn->wifi_get_iface_name = wifi_get_iface_name_stub;
+    hal_fn->wifi_reset_iface_event_handler = wifi_reset_iface_event_handler_stub;
+    hal_fn->wifi_start_gscan = wifi_start_gscan_stub;
+    hal_fn->wifi_stop_gscan = wifi_stop_gscan_stub;
+    hal_fn->wifi_get_cached_gscan_results = wifi_get_cached_gscan_results_stub;
+    hal_fn->wifi_set_bssid_hotlist = wifi_set_bssid_hotlist_stub;
+    hal_fn->wifi_reset_bssid_hotlist = wifi_reset_bssid_hotlist_stub;
+    hal_fn->wifi_set_significant_change_handler = wifi_set_significant_change_handler_stub;
+    hal_fn->wifi_reset_significant_change_handler = wifi_reset_significant_change_handler_stub;
+    hal_fn->wifi_get_gscan_capabilities = wifi_get_gscan_capabilities_stub;
+    hal_fn->wifi_set_link_stats = wifi_set_link_stats_stub;
+    hal_fn->wifi_get_link_stats = wifi_get_link_stats_stub;
+    hal_fn->wifi_clear_link_stats = wifi_clear_link_stats_stub;
+    hal_fn->wifi_get_valid_channels = wifi_get_valid_channels_stub;
+    hal_fn->wifi_rtt_range_request = wifi_rtt_range_request_stub;
+    hal_fn->wifi_rtt_range_cancel = wifi_rtt_range_cancel_stub;
+    hal_fn->wifi_get_rtt_capabilities = wifi_get_rtt_capabilities_stub;
+    hal_fn->wifi_start_logging = wifi_start_logging_stub;
+    hal_fn->wifi_set_epno_list = wifi_set_epno_list_stub;
+    hal_fn->wifi_set_country_code = wifi_set_country_code_stub;
+    hal_fn->wifi_enable_tdls = wifi_enable_tdls_stub;
+    hal_fn->wifi_disable_tdls = wifi_disable_tdls_stub;
+    hal_fn->wifi_get_tdls_status = wifi_get_tdls_status_stub;
+    hal_fn->wifi_get_tdls_capabilities = wifi_get_tdls_capabilities_stub;
+    hal_fn->wifi_set_nodfs_flag = wifi_set_nodfs_flag_stub;
+    hal_fn->wifi_get_firmware_memory_dump = wifi_get_firmware_memory_dump_stub;
+    hal_fn->wifi_set_log_handler = wifi_set_log_handler_stub;
+    hal_fn->wifi_reset_log_handler = wifi_reset_log_handler_stub;
+    hal_fn->wifi_set_alert_handler = wifi_set_alert_handler_stub;
+    hal_fn->wifi_reset_alert_handler = wifi_reset_alert_handler_stub;
+    hal_fn->wifi_get_firmware_version = wifi_get_firmware_version_stub;
+    hal_fn->wifi_get_ring_buffers_status = wifi_get_ring_buffers_status_stub;
+    hal_fn->wifi_get_logger_supported_feature_set = wifi_get_logger_supported_feature_set_stub;
+    hal_fn->wifi_get_ring_data = wifi_get_ring_data_stub;
+    hal_fn->wifi_get_driver_version = wifi_get_driver_version_stub;
+    hal_fn->wifi_set_ssid_white_list = wifi_set_ssid_white_list_stub;
+    hal_fn->wifi_set_gscan_roam_params = wifi_set_gscan_roam_params_stub;
+    hal_fn->wifi_set_bssid_preference = wifi_set_bssid_preference_stub;
+    hal_fn->wifi_enable_lazy_roam = wifi_enable_lazy_roam_stub;
+    hal_fn->wifi_set_bssid_blacklist = wifi_set_bssid_blacklist_stub;
+    hal_fn->wifi_start_sending_offloaded_packet = wifi_start_sending_offloaded_packet_stub;
+    hal_fn->wifi_stop_sending_offloaded_packet = wifi_stop_sending_offloaded_packet_stub;
+    return 0;
+}
+
 static bool doCommand(JNIEnv* env, jstring javaCommand,
                       char* reply, size_t reply_len) {
     ScopedUtfChars command(env, javaCommand);
@@ -152,53 +217,140 @@
 static const char *WifiIfaceHandleVarName = "sWifiIfaceHandles";
 static jmethodID OnScanResultsMethodID;
 
-static JNIEnv *getEnv() {
-    JNIEnv *env = NULL;
-    mVM->AttachCurrentThread(&env, NULL);
-    return env;
+static wifi_handle getWifiHandle(JNIHelper &helper, jclass cls) {
+    return (wifi_handle) helper.getStaticLongField(cls, WifiHandleVarName);
 }
 
-static wifi_handle getWifiHandle(JNIEnv *env, jclass cls) {
-    return (wifi_handle) getStaticLongField(env, cls, WifiHandleVarName);
+static wifi_interface_handle getIfaceHandle(JNIHelper &helper, jclass cls, jint index) {
+    return (wifi_interface_handle) helper.getStaticLongArrayField(cls, WifiIfaceHandleVarName, index);
 }
 
-static wifi_interface_handle getIfaceHandle(JNIEnv *env, jclass cls, jint index) {
-    return (wifi_interface_handle) getStaticLongArrayField(env, cls, WifiIfaceHandleVarName, index);
-}
+jboolean setSSIDField(JNIHelper helper, jobject scanResult, const char *rawSsid) {
 
-static jobject createScanResult(JNIEnv *env, wifi_scan_result *result) {
+    int len = strlen(rawSsid);
+
+    if (len > 0) {
+        JNIObject<jbyteArray> ssidBytes = helper.newByteArray(len);
+        helper.setByteArrayRegion(ssidBytes, 0, len, (jbyte *) rawSsid);
+        jboolean ret = helper.callStaticMethod(mCls,
+                "setSsid", "([BLandroid/net/wifi/ScanResult;)Z", ssidBytes.get(), scanResult);
+        return ret;
+    } else {
+        //empty SSID or SSID start with \0
+        return true;
+    }
+}
+static JNIObject<jobject> createScanResult(JNIHelper &helper, wifi_scan_result *result) {
 
     // ALOGD("creating scan result");
 
-    jobject scanResult = createObject(env, "android/net/wifi/ScanResult");
+    JNIObject<jobject> scanResult = helper.createObject("android/net/wifi/ScanResult");
     if (scanResult == NULL) {
         ALOGE("Error in creating scan result");
-        return NULL;
+        return JNIObject<jobject>(helper, NULL);
     }
 
-    // ALOGD("setting SSID to %s", result.ssid);
-    setStringField(env, scanResult, "SSID", result->ssid);
+    ALOGV("setting SSID to %s", result->ssid);
+
+    if (!setSSIDField(helper, scanResult, result->ssid)) {
+        ALOGE("Error on set SSID");
+        return JNIObject<jobject>(helper, NULL);
+    }
 
     char bssid[32];
     sprintf(bssid, "%02x:%02x:%02x:%02x:%02x:%02x", result->bssid[0], result->bssid[1],
         result->bssid[2], result->bssid[3], result->bssid[4], result->bssid[5]);
 
-    setStringField(env, scanResult, "BSSID", bssid);
+    helper.setStringField(scanResult, "BSSID", bssid);
 
-    setIntField(env, scanResult, "level", result->rssi);
-    setIntField(env, scanResult, "frequency", result->channel);
-    setLongField(env, scanResult, "timestamp", result->ts);
+    helper.setIntField(scanResult, "level", result->rssi);
+    helper.setIntField(scanResult, "frequency", result->channel);
+    helper.setLongField(scanResult, "timestamp", result->ts);
 
     return scanResult;
 }
 
-static jboolean android_net_wifi_startHal(JNIEnv* env, jclass cls) {
-    wifi_handle halHandle = getWifiHandle(env, cls);
+int set_iface_flags(const char *ifname, int dev_up) {
+    struct ifreq ifr;
+    int ret;
+    int sock = socket(PF_INET, SOCK_DGRAM, 0);
+    if (sock < 0) {
+        ALOGD("Bad socket: %d\n", sock);
+        return -errno;
+    }
 
+    //ALOGD("setting interface %s flags (%s)\n", ifname, dev_up ? "UP" : "DOWN");
+
+    memset(&ifr, 0, sizeof(ifr));
+    strlcpy(ifr.ifr_name, ifname, IFNAMSIZ);
+
+    //ALOGD("reading old value\n");
+
+    if (ioctl(sock, SIOCGIFFLAGS, &ifr) != 0) {
+      ret = errno ? -errno : -999;
+      ALOGE("Could not read interface %s flags: %d\n", ifname, errno);
+      close(sock);
+      return ret;
+    } else {
+      //ALOGD("writing new value\n");
+    }
+
+    if (dev_up) {
+      if (ifr.ifr_flags & IFF_UP) {
+        // ALOGD("interface %s is already up\n", ifname);
+        close(sock);
+        return 0;
+      }
+      ifr.ifr_flags |= IFF_UP;
+    } else {
+      if (!(ifr.ifr_flags & IFF_UP)) {
+        // ALOGD("interface %s is already down\n", ifname);
+        close(sock);
+        return 0;
+      }
+      ifr.ifr_flags &= ~IFF_UP;
+    }
+
+    if (ioctl(sock, SIOCSIFFLAGS, &ifr) != 0) {
+      ALOGE("Could not set interface %s flags: %d\n", ifname, errno);
+      ret = errno ? -errno : -999;
+      close(sock);
+      return ret;
+    } else {
+      ALOGD("set interface %s flags (%s)\n", ifname, dev_up ? "UP" : "DOWN");
+    }
+    close(sock);
+    return 0;
+}
+
+static jboolean android_net_wifi_toggle_interface(JNIEnv* env, jclass cls, int toggle) {
+    return(set_iface_flags("wlan0", toggle) == 0);
+}
+
+static jboolean android_net_wifi_startHal(JNIEnv* env, jclass cls) {
+    JNIHelper helper(env);
+    wifi_handle halHandle = getWifiHandle(helper, cls);
     if (halHandle == NULL) {
-        wifi_error res = wifi_initialize(&halHandle);
+
+        if(init_wifi_hal_func_table(&hal_fn) != 0 ) {
+            ALOGD("Can not initialize the basic function pointer table");
+            return false;
+        }
+
+        wifi_error res = init_wifi_vendor_hal_func_table(&hal_fn);
+        if (res != WIFI_SUCCESS) {
+            ALOGD("Can not initialize the vendor function pointer table");
+	    return false;
+        }
+
+        int ret = set_iface_flags("wlan0", 1);
+        if(ret != 0) {
+            return false;
+        }
+
+        res = hal_fn.wifi_initialize(&halHandle);
         if (res == WIFI_SUCCESS) {
-            setStaticLongField(env, cls, WifiHandleVarName, (jlong)halHandle);
+            helper.setStaticLongField(cls, WifiHandleVarName, (jlong)halHandle);
             ALOGD("Did set static halHandle = %p", halHandle);
         }
         env->GetJavaVM(&mVM);
@@ -206,61 +358,73 @@
         ALOGD("halHandle = %p, mVM = %p, mCls = %p", halHandle, mVM, mCls);
         return res == WIFI_SUCCESS;
     } else {
-        return true;
+        return (set_iface_flags("wlan0", 1) == 0);
     }
 }
 
 void android_net_wifi_hal_cleaned_up_handler(wifi_handle handle) {
     ALOGD("In wifi cleaned up handler");
 
-    JNIEnv * env = getEnv();
-    setStaticLongField(env, mCls, WifiHandleVarName, 0);
-    env->DeleteGlobalRef(mCls);
+    JNIHelper helper(mVM);
+    helper.setStaticLongField(mCls, WifiHandleVarName, 0);
+
+    helper.deleteGlobalRef(mCls);
     mCls = NULL;
     mVM  = NULL;
 }
 
 static void android_net_wifi_stopHal(JNIEnv* env, jclass cls) {
     ALOGD("In wifi stop Hal");
-    wifi_handle halHandle = getWifiHandle(env, cls);
-    wifi_cleanup(halHandle, android_net_wifi_hal_cleaned_up_handler);
+
+    JNIHelper helper(env);
+    wifi_handle halHandle = getWifiHandle(helper, cls);
+    if (halHandle == NULL)
+        return;
+
+    ALOGD("halHandle = %p, mVM = %p, mCls = %p", halHandle, mVM, mCls);
+    hal_fn.wifi_cleanup(halHandle, android_net_wifi_hal_cleaned_up_handler);
 }
 
 static void android_net_wifi_waitForHalEvents(JNIEnv* env, jclass cls) {
 
     ALOGD("waitForHalEvents called, vm = %p, obj = %p, env = %p", mVM, mCls, env);
 
-    wifi_handle halHandle = getWifiHandle(env, cls);
-    wifi_event_loop(halHandle);
+    JNIHelper helper(env);
+    wifi_handle halHandle = getWifiHandle(helper, cls);
+    hal_fn.wifi_event_loop(halHandle);
+    set_iface_flags("wlan0", 0);
 }
 
 static int android_net_wifi_getInterfaces(JNIEnv *env, jclass cls) {
     int n = 0;
-    wifi_handle halHandle = getWifiHandle(env, cls);
+
+    JNIHelper helper(env);
+
+    wifi_handle halHandle = getWifiHandle(helper, cls);
     wifi_interface_handle *ifaceHandles = NULL;
-    int result = wifi_get_ifaces(halHandle, &n, &ifaceHandles);
+    int result = hal_fn.wifi_get_ifaces(halHandle, &n, &ifaceHandles);
     if (result < 0) {
         return result;
     }
 
     if (n < 0) {
-        THROW(env, "android_net_wifi_getInterfaces no interfaces");
+        THROW(helper,"android_net_wifi_getInterfaces no interfaces");
         return 0;
     }
 
     if (ifaceHandles == NULL) {
-       THROW(env, "android_net_wifi_getInterfaces null interface array");
+       THROW(helper,"android_net_wifi_getInterfaces null interface array");
        return 0;
     }
 
     if (n > 8) {
-        THROW(env, "Too many interfaces");
+        THROW(helper,"Too many interfaces");
         return 0;
     }
 
     jlongArray array = (env)->NewLongArray(n);
     if (array == NULL) {
-        THROW(env, "Error in accessing array");
+        THROW(helper,"Error in accessing array");
         return 0;
     }
 
@@ -268,135 +432,133 @@
     for (int i = 0; i < n; i++) {
         elems[i] = reinterpret_cast<jlong>(ifaceHandles[i]);
     }
-    env->SetLongArrayRegion(array, 0, n, elems);
-    setStaticLongArrayField(env, cls, WifiIfaceHandleVarName, array);
+
+    helper.setLongArrayRegion(array, 0, n, elems);
+    helper.setStaticLongArrayField(cls, WifiIfaceHandleVarName, array);
 
     return (result < 0) ? result : n;
 }
 
 static jstring android_net_wifi_getInterfaceName(JNIEnv *env, jclass cls, jint i) {
+
     char buf[EVENT_BUF_SIZE];
 
-    jlong value = getStaticLongArrayField(env, cls, WifiIfaceHandleVarName, i);
+    JNIHelper helper(env);
+
+    jlong value = helper.getStaticLongArrayField(cls, WifiIfaceHandleVarName, i);
     wifi_interface_handle handle = (wifi_interface_handle) value;
-    int result = ::wifi_get_iface_name(handle, buf, sizeof(buf));
+    int result = hal_fn.wifi_get_iface_name(handle, buf, sizeof(buf));
     if (result < 0) {
         return NULL;
     } else {
-        return env->NewStringUTF(buf);
+        JNIObject<jstring> name = helper.newStringUTF(buf);
+        return name.detach();
     }
 }
 
 
 static void onScanResultsAvailable(wifi_request_id id, unsigned num_results) {
 
-    JNIEnv *env = NULL;
-    mVM->AttachCurrentThread(&env, NULL);
+    JNIHelper helper(mVM);
 
-    ALOGD("onScanResultsAvailable called, vm = %p, obj = %p, env = %p", mVM, mCls, env);
+    // ALOGD("onScanResultsAvailable called, vm = %p, obj = %p, env = %p", mVM, mCls, env);
 
-    reportEvent(env, mCls, "onScanResultsAvailable", "(I)V", id);
+    helper.reportEvent(mCls, "onScanResultsAvailable", "(I)V", id);
 }
 
 static void onScanEvent(wifi_scan_event event, unsigned status) {
-    JNIEnv *env = NULL;
-    mVM->AttachCurrentThread(&env, NULL);
 
-    ALOGD("onScanStatus called, vm = %p, obj = %p, env = %p", mVM, mCls, env);
+    JNIHelper helper(mVM);
 
-    reportEvent(env, mCls, "onScanStatus", "(I)V", status);
+    // ALOGD("onScanStatus called, vm = %p, obj = %p, env = %p", mVM, mCls, env);
+
+    helper.reportEvent(mCls, "onScanStatus", "(I)V", event);
 }
 
 static void onFullScanResult(wifi_request_id id, wifi_scan_result *result) {
 
-    JNIEnv *env = NULL;
-    mVM->AttachCurrentThread(&env, NULL);
+    JNIHelper helper(mVM);
 
-    ALOGD("onFullScanResult called, vm = %p, obj = %p, env = %p", mVM, mCls, env);
+    //ALOGD("onFullScanResult called, vm = %p, obj = %p, env = %p", mVM, mCls, env);
 
-    jobject scanResult = createScanResult(env, result);
+    JNIObject<jobject> scanResult = createScanResult(helper, result);
 
-    ALOGD("Creating a byte array of length %d", result->ie_length);
+    //ALOGD("Creating a byte array of length %d", result->ie_length);
 
-    jbyteArray elements = env->NewByteArray(result->ie_length);
+    JNIObject<jbyteArray> elements = helper.newByteArray(result->ie_length);
     if (elements == NULL) {
         ALOGE("Error in allocating array");
         return;
     }
 
-    ALOGE("Setting byte array");
+    // ALOGD("Setting byte array");
 
     jbyte *bytes = (jbyte *)&(result->ie_data[0]);
-    env->SetByteArrayRegion(elements, 0, result->ie_length, bytes);
+    helper.setByteArrayRegion(elements, 0, result->ie_length, bytes);
 
-    ALOGE("Returning result");
+    // ALOGD("Returning result");
 
-    reportEvent(env, mCls, "onFullScanResult", "(ILandroid/net/wifi/ScanResult;[B)V", id,
-            scanResult, elements);
-
-    env->DeleteLocalRef(scanResult);
-    env->DeleteLocalRef(elements);
+    helper.reportEvent(mCls, "onFullScanResult", "(ILandroid/net/wifi/ScanResult;[B)V", id,
+            scanResult.get(), elements.get());
 }
 
 static jboolean android_net_wifi_startScan(
         JNIEnv *env, jclass cls, jint iface, jint id, jobject settings) {
 
-    wifi_interface_handle handle = getIfaceHandle(env, cls, iface);
-    ALOGD("starting scan on interface[%d] = %p", iface, handle);
+    JNIHelper helper(env);
+    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
+    // ALOGD("starting scan on interface[%d] = %p", iface, handle);
 
     wifi_scan_cmd_params params;
     memset(&params, 0, sizeof(params));
 
-    params.base_period = getIntField(env, settings, "base_period_ms");
-    params.max_ap_per_scan = getIntField(env, settings, "max_ap_per_scan");
-    params.report_threshold = getIntField(env, settings, "report_threshold");
+    params.base_period = helper.getIntField(settings, "base_period_ms");
+    params.max_ap_per_scan = helper.getIntField(settings, "max_ap_per_scan");
+    params.report_threshold_percent = helper.getIntField(settings, "report_threshold_percent");
+    params.report_threshold_num_scans = helper.getIntField(settings, "report_threshold_num_scans");
 
-    ALOGD("Initialized common fields %d, %d, %d", params.base_period,
-            params.max_ap_per_scan, params.report_threshold);
+    ALOGD("Initialized common fields %d, %d, %d, %d", params.base_period, params.max_ap_per_scan,
+            params.report_threshold_percent, params.report_threshold_num_scans);
 
     const char *bucket_array_type = "[Lcom/android/server/wifi/WifiNative$BucketSettings;";
     const char *channel_array_type = "[Lcom/android/server/wifi/WifiNative$ChannelSettings;";
 
-    jobjectArray buckets = (jobjectArray)getObjectField(env, settings, "buckets", bucket_array_type);
-    params.num_buckets = getIntField(env, settings, "num_buckets");
+    params.num_buckets = helper.getIntField(settings, "num_buckets");
 
-    ALOGD("Initialized num_buckets to %d", params.num_buckets);
+    // ALOGD("Initialized num_buckets to %d", params.num_buckets);
 
     for (int i = 0; i < params.num_buckets; i++) {
-        jobject bucket = getObjectArrayField(env, settings, "buckets", bucket_array_type, i);
+        JNIObject<jobject> bucket = helper.getObjectArrayField(
+                settings, "buckets", bucket_array_type, i);
 
-        params.buckets[i].bucket = getIntField(env, bucket, "bucket");
-        params.buckets[i].band = (wifi_band) getIntField(env, bucket, "band");
-        params.buckets[i].period = getIntField(env, bucket, "period_ms");
+        params.buckets[i].bucket = helper.getIntField(bucket, "bucket");
+        params.buckets[i].band = (wifi_band) helper.getIntField(bucket, "band");
+        params.buckets[i].period = helper.getIntField(bucket, "period_ms");
 
-        ALOGD("Initialized common bucket fields %d:%d:%d", params.buckets[i].bucket,
-                params.buckets[i].band, params.buckets[i].period);
-
-        int report_events = getIntField(env, bucket, "report_events");
+        int report_events = helper.getIntField(bucket, "report_events");
         params.buckets[i].report_events = report_events;
 
-        ALOGD("Initialized report events to %d", params.buckets[i].report_events);
+        ALOGD("bucket[%d] = %d:%d:%d:%d", i, params.buckets[i].bucket,
+                params.buckets[i].band, params.buckets[i].period, report_events);
 
-        jobjectArray channels = (jobjectArray)getObjectField(
-                env, bucket, "channels", channel_array_type);
-
-        params.buckets[i].num_channels = getIntField(env, bucket, "num_channels");
-        ALOGD("Initialized num_channels to %d", params.buckets[i].num_channels);
+        params.buckets[i].num_channels = helper.getIntField(bucket, "num_channels");
+        // ALOGD("Initialized num_channels to %d", params.buckets[i].num_channels);
 
         for (int j = 0; j < params.buckets[i].num_channels; j++) {
-            jobject channel = getObjectArrayField(env, bucket, "channels", channel_array_type, j);
+            JNIObject<jobject> channel = helper.getObjectArrayField(
+                    bucket, "channels", channel_array_type, j);
 
-            params.buckets[i].channels[j].channel = getIntField(env, channel, "frequency");
-            params.buckets[i].channels[j].dwellTimeMs = getIntField(env, channel, "dwell_time_ms");
+            params.buckets[i].channels[j].channel = helper.getIntField(channel, "frequency");
+            params.buckets[i].channels[j].dwellTimeMs = helper.getIntField(channel, "dwell_time_ms");
 
-            bool passive = getBoolField(env, channel, "passive");
+            bool passive = helper.getBoolField(channel, "passive");
             params.buckets[i].channels[j].passive = (passive ? 1 : 0);
 
-            ALOGD("Initialized channel %d", params.buckets[i].channels[j].channel);
+            // ALOGD("Initialized channel %d", params.buckets[i].channels[j].channel);
         }
     }
 
-    ALOGD("Initialized all fields");
+    // ALOGD("Initialized all fields");
 
     wifi_scan_result_handler handler;
     memset(&handler, 0, sizeof(handler));
@@ -404,65 +566,84 @@
     handler.on_full_scan_result = &onFullScanResult;
     handler.on_scan_event = &onScanEvent;
 
-    return wifi_start_gscan(id, handle, params, handler) == WIFI_SUCCESS;
+    return hal_fn.wifi_start_gscan(id, handle, params, handler) == WIFI_SUCCESS;
 }
 
 static jboolean android_net_wifi_stopScan(JNIEnv *env, jclass cls, jint iface, jint id) {
-    wifi_interface_handle handle = getIfaceHandle(env, cls, iface);
-    ALOGD("stopping scan on interface[%d] = %p", iface, handle);
 
-    return wifi_stop_gscan(id, handle)  == WIFI_SUCCESS;
+    JNIHelper helper(env);
+    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
+    // ALOGD("stopping scan on interface[%d] = %p", iface, handle);
+
+    return hal_fn.wifi_stop_gscan(id, handle)  == WIFI_SUCCESS;
+}
+
+static int compare_scan_result_timestamp(const void *v1, const void *v2) {
+    const wifi_scan_result *result1 = static_cast<const wifi_scan_result *>(v1);
+    const wifi_scan_result *result2 = static_cast<const wifi_scan_result *>(v2);
+    return result1->ts - result2->ts;
 }
 
 static jobject android_net_wifi_getScanResults(
         JNIEnv *env, jclass cls, jint iface, jboolean flush)  {
-    
-    wifi_scan_result results[256];
-    int num_results = 256;
-    
-    wifi_interface_handle handle = getIfaceHandle(env, cls, iface);
-    ALOGD("getting scan results on interface[%d] = %p", iface, handle);
-    
-    int result = wifi_get_cached_gscan_results(handle, 1, num_results, results, &num_results);
+
+    JNIHelper helper(env);
+    wifi_cached_scan_results scan_data[64];
+    int num_scan_data = 64;
+
+    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
+    // ALOGD("getting scan results on interface[%d] = %p", iface, handle);
+
+    byte b = flush ? 0xFF : 0;
+    int result = hal_fn.wifi_get_cached_gscan_results(handle, b, num_scan_data, scan_data, &num_scan_data);
     if (result == WIFI_SUCCESS) {
-        jclass clsScanResult = (env)->FindClass("android/net/wifi/ScanResult");
-        if (clsScanResult == NULL) {
-            ALOGE("Error in accessing class");
+        JNIObject<jobjectArray> scanData = helper.createObjectArray(
+                "android/net/wifi/WifiScanner$ScanData", num_scan_data);
+        if (scanData == NULL) {
+            ALOGE("Error in allocating array of scanData");
             return NULL;
         }
 
-        jobjectArray scanResults = env->NewObjectArray(num_results, clsScanResult, NULL);
-        if (scanResults == NULL) {
-            ALOGE("Error in allocating array");
-            return NULL;
-        }
+        for (int i = 0; i < num_scan_data; i++) {
 
-        for (int i = 0; i < num_results; i++) {
-
-            jobject scanResult = createObject(env, "android/net/wifi/ScanResult");
-            if (scanResult == NULL) {
-                ALOGE("Error in creating scan result");
+            JNIObject<jobject> data = helper.createObject("android/net/wifi/WifiScanner$ScanData");
+            if (data == NULL) {
+                ALOGE("Error in allocating scanData");
                 return NULL;
             }
 
-            setStringField(env, scanResult, "SSID", results[i].ssid);
+            helper.setIntField(data, "mId", scan_data[i].scan_id);
+            helper.setIntField(data, "mFlags", scan_data[i].flags);
 
-            char bssid[32];
-            sprintf(bssid, "%02x:%02x:%02x:%02x:%02x:%02x", results[i].bssid[0],
-                    results[i].bssid[1], results[i].bssid[2], results[i].bssid[3],
-                    results[i].bssid[4], results[i].bssid[5]);
+            /* sort all scan results by timestamp */
+            qsort(scan_data[i].results, scan_data[i].num_results,
+                    sizeof(wifi_scan_result), compare_scan_result_timestamp);
 
-            setStringField(env, scanResult, "BSSID", bssid);
+            JNIObject<jobjectArray> scanResults = helper.createObjectArray(
+                    "android/net/wifi/ScanResult", scan_data[i].num_results);
+            if (scanResults == NULL) {
+                ALOGE("Error in allocating scanResult array");
+                return NULL;
+            }
 
-            setIntField(env, scanResult, "level", results[i].rssi);
-            setIntField(env, scanResult, "frequency", results[i].channel);
-            setLongField(env, scanResult, "timestamp", results[i].ts);
+            wifi_scan_result *results = scan_data[i].results;
+            for (int j = 0; j < scan_data[i].num_results; j++) {
 
-            env->SetObjectArrayElement(scanResults, i, scanResult);
-            env->DeleteLocalRef(scanResult);
+                JNIObject<jobject> scanResult = createScanResult(helper, &results[j]);
+                if (scanResult == NULL) {
+                    ALOGE("Error in creating scan result");
+                    return NULL;
+                }
+
+                helper.setObjectArrayElement(scanResults, j, scanResult);
+            }
+
+            helper.setObjectField(data, "mResults", "[Landroid/net/wifi/ScanResult;", scanResults);
+            helper.setObjectArrayElement(scanData, i, data);
         }
 
-        return scanResults;
+        // ALOGD("retrieved %d scan data from interface[%d] = %p", num_scan_data, iface, handle);
+        return scanData.detach();
     } else {
         return NULL;
     }
@@ -472,24 +653,25 @@
 static jboolean android_net_wifi_getScanCapabilities(
         JNIEnv *env, jclass cls, jint iface, jobject capabilities) {
 
-    wifi_interface_handle handle = getIfaceHandle(env, cls, iface);
-    ALOGD("getting scan capabilities on interface[%d] = %p", iface, handle);
+    JNIHelper helper(env);
+    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
+    // ALOGD("getting scan capabilities on interface[%d] = %p", iface, handle);
 
     wifi_gscan_capabilities c;
     memset(&c, 0, sizeof(c));
-    int result = wifi_get_gscan_capabilities(handle, &c);
+    int result = hal_fn.wifi_get_gscan_capabilities(handle, &c);
     if (result != WIFI_SUCCESS) {
         ALOGD("failed to get capabilities : %d", result);
         return JNI_FALSE;
     }
 
-    setIntField(env, capabilities, "max_scan_cache_size", c.max_scan_cache_size);
-    setIntField(env, capabilities, "max_scan_buckets", c.max_scan_buckets);
-    setIntField(env, capabilities, "max_ap_cache_per_scan", c.max_ap_cache_per_scan);
-    setIntField(env, capabilities, "max_rssi_sample_size", c.max_rssi_sample_size);
-    setIntField(env, capabilities, "max_scan_reporting_threshold", c.max_scan_reporting_threshold);
-    setIntField(env, capabilities, "max_hotlist_aps", c.max_hotlist_aps);
-    setIntField(env, capabilities, "max_significant_wifi_change_aps",
+    helper.setIntField(capabilities, "max_scan_cache_size", c.max_scan_cache_size);
+    helper.setIntField(capabilities, "max_scan_buckets", c.max_scan_buckets);
+    helper.setIntField(capabilities, "max_ap_cache_per_scan", c.max_ap_cache_per_scan);
+    helper.setIntField(capabilities, "max_rssi_sample_size", c.max_rssi_sample_size);
+    helper.setIntField(capabilities, "max_scan_reporting_threshold", c.max_scan_reporting_threshold);
+    helper.setIntField(capabilities, "max_hotlist_bssids", c.max_hotlist_bssids);
+    helper.setIntField(capabilities, "max_significant_wifi_change_aps",
                 c.max_significant_wifi_change_aps);
 
     return JNI_TRUE;
@@ -539,15 +721,15 @@
 }
 
 static bool parseMacAddress(JNIEnv *env, jobject obj, mac_addr addr) {
-    jstring macAddrString = (jstring) getObjectField(
-            env, obj, "bssid", "Ljava/lang/String;");
-
+    JNIHelper helper(env);
+    JNIObject<jstring> macAddrString = helper.getStringField(obj, "bssid");
     if (macAddrString == NULL) {
         ALOGE("Error getting bssid field");
         return false;
     }
 
-    const char *bssid = env->GetStringUTFChars(macAddrString, NULL);
+    ScopedUtfChars chars(env, macAddrString);
+    const char *bssid = chars.c_str();
     if (bssid == NULL) {
         ALOGE("Error getting bssid");
         return false;
@@ -560,19 +742,11 @@
 static void onHotlistApFound(wifi_request_id id,
         unsigned num_results, wifi_scan_result *results) {
 
-    JNIEnv *env = NULL;
-    mVM->AttachCurrentThread(&env, NULL);
+    JNIHelper helper(mVM);
+    ALOGD("onHotlistApFound called, vm = %p, obj = %p, num_results = %d", mVM, mCls, num_results);
 
-    ALOGD("onHotlistApFound called, vm = %p, obj = %p, env = %p, num_results = %d",
-            mVM, mCls, env, num_results);
-
-    jclass clsScanResult = (env)->FindClass("android/net/wifi/ScanResult");
-    if (clsScanResult == NULL) {
-        ALOGE("Error in accessing class");
-        return;
-    }
-
-    jobjectArray scanResults = env->NewObjectArray(num_results, clsScanResult, NULL);
+    JNIObject<jobjectArray> scanResults = helper.newObjectArray(num_results,
+            "android/net/wifi/ScanResult", NULL);
     if (scanResults == NULL) {
         ALOGE("Error in allocating array");
         return;
@@ -580,69 +754,91 @@
 
     for (unsigned i = 0; i < num_results; i++) {
 
-        jobject scanResult = createObject(env, "android/net/wifi/ScanResult");
+        JNIObject<jobject> scanResult = createScanResult(helper, &results[i]);
         if (scanResult == NULL) {
             ALOGE("Error in creating scan result");
             return;
         }
 
-        setStringField(env, scanResult, "SSID", results[i].ssid);
+        helper.setObjectArrayElement(scanResults, i, scanResult);
 
-        char bssid[32];
-        sprintf(bssid, "%02x:%02x:%02x:%02x:%02x:%02x", results[i].bssid[0], results[i].bssid[1],
-            results[i].bssid[2], results[i].bssid[3], results[i].bssid[4], results[i].bssid[5]);
-
-        setStringField(env, scanResult, "BSSID", bssid);
-
-        setIntField(env, scanResult, "level", results[i].rssi);
-        setIntField(env, scanResult, "frequency", results[i].channel);
-        setLongField(env, scanResult, "timestamp", results[i].ts);
-
-        env->SetObjectArrayElement(scanResults, i, scanResult);
-
-        ALOGD("Found AP %32s %s", results[i].ssid, bssid);
+        ALOGD("Found AP %32s", results[i].ssid);
     }
 
-    reportEvent(env, mCls, "onHotlistApFound", "(I[Landroid/net/wifi/ScanResult;)V",
-        id, scanResults);
+    helper.reportEvent(mCls, "onHotlistApFound", "(I[Landroid/net/wifi/ScanResult;)V",
+        id, scanResults.get());
 }
 
+static void onHotlistApLost(wifi_request_id id,
+        unsigned num_results, wifi_scan_result *results) {
+
+    JNIHelper helper(mVM);
+    ALOGD("onHotlistApLost called, vm = %p, obj = %p, num_results = %d", mVM, mCls, num_results);
+
+    JNIObject<jobjectArray> scanResults = helper.newObjectArray(num_results,
+            "android/net/wifi/ScanResult", NULL);
+    if (scanResults == NULL) {
+        ALOGE("Error in allocating array");
+        return;
+    }
+
+    for (unsigned i = 0; i < num_results; i++) {
+
+        JNIObject<jobject> scanResult = createScanResult(helper, &results[i]);
+        if (scanResult == NULL) {
+            ALOGE("Error in creating scan result");
+            return;
+        }
+
+        helper.setObjectArrayElement(scanResults, i, scanResult);
+
+        ALOGD("Lost AP %32s", results[i].ssid);
+    }
+
+    helper.reportEvent(mCls, "onHotlistApLost", "(I[Landroid/net/wifi/ScanResult;)V",
+        id, scanResults.get());
+}
+
+
 static jboolean android_net_wifi_setHotlist(
         JNIEnv *env, jclass cls, jint iface, jint id, jobject ap)  {
 
-    wifi_interface_handle handle = getIfaceHandle(env, cls, iface);
+    JNIHelper helper(env);
+    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
     ALOGD("setting hotlist on interface[%d] = %p", iface, handle);
 
     wifi_bssid_hotlist_params params;
     memset(&params, 0, sizeof(params));
 
-    jobjectArray array = (jobjectArray) getObjectField(env, ap,
-            "bssidInfos", "[Landroid/net/wifi/WifiScanner$BssidInfo;");
-    params.num_ap = env->GetArrayLength(array);
+    params.lost_ap_sample_size = helper.getIntField(ap, "apLostThreshold");
 
-    if (params.num_ap == 0) {
+    JNIObject<jobjectArray> array = helper.getArrayField(
+            ap, "bssidInfos", "[Landroid/net/wifi/WifiScanner$BssidInfo;");
+    params.num_bssid = helper.getArrayLength(array);
+
+    if (params.num_bssid == 0) {
         ALOGE("Error in accesing array");
         return false;
     }
 
-    if (params.num_ap >
+    if (params.num_bssid >
             static_cast<int>(sizeof(params.ap) / sizeof(params.ap[0]))) {
         ALOGE("setHotlist array length is too long");
         android_errorWriteLog(0x534e4554, "31856351");
         return false;
     }
 
-    for (int i = 0; i < params.num_ap; i++) {
-        jobject objAp = env->GetObjectArrayElement(array, i);
+    for (int i = 0; i < params.num_bssid; i++) {
+        JNIObject<jobject> objAp = helper.getObjectArrayElement(array, i);
 
-        jstring macAddrString = (jstring) getObjectField(
-                env, objAp, "bssid", "Ljava/lang/String;");
+        JNIObject<jstring> macAddrString = helper.getStringField(objAp, "bssid");
         if (macAddrString == NULL) {
             ALOGE("Error getting bssid field");
             return false;
         }
 
-        const char *bssid = env->GetStringUTFChars(macAddrString, NULL);
+        ScopedUtfChars chars(env, macAddrString);
+        const char *bssid = chars.c_str();
         if (bssid == NULL) {
             ALOGE("Error getting bssid");
             return false;
@@ -658,40 +854,36 @@
 
         ALOGD("Added bssid %s", bssidOut);
 
-        params.ap[i].low = getIntField(env, objAp, "low");
-        params.ap[i].high = getIntField(env, objAp, "high");
+        params.ap[i].low = helper.getIntField(objAp, "low");
+        params.ap[i].high = helper.getIntField(objAp, "high");
     }
 
     wifi_hotlist_ap_found_handler handler;
     memset(&handler, 0, sizeof(handler));
 
     handler.on_hotlist_ap_found = &onHotlistApFound;
-    return wifi_set_bssid_hotlist(id, handle, params, handler) == WIFI_SUCCESS;
+    handler.on_hotlist_ap_lost  = &onHotlistApLost;
+    return hal_fn.wifi_set_bssid_hotlist(id, handle, params, handler) == WIFI_SUCCESS;
 }
 
-static jboolean android_net_wifi_resetHotlist(
-        JNIEnv *env, jclass cls, jint iface, jint id)  {
+static jboolean android_net_wifi_resetHotlist(JNIEnv *env, jclass cls, jint iface, jint id)  {
 
-    wifi_interface_handle handle = getIfaceHandle(env, cls, iface);
+    JNIHelper helper(env);
+    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
     ALOGD("resetting hotlist on interface[%d] = %p", iface, handle);
 
-    return wifi_reset_bssid_hotlist(id, handle) == WIFI_SUCCESS;
+    return hal_fn.wifi_reset_bssid_hotlist(id, handle) == WIFI_SUCCESS;
 }
 
 void onSignificantWifiChange(wifi_request_id id,
         unsigned num_results, wifi_significant_change_result **results) {
-    JNIEnv *env = NULL;
-    mVM->AttachCurrentThread(&env, NULL);
 
-    ALOGD("onSignificantWifiChange called, vm = %p, obj = %p, env = %p", mVM, mCls, env);
+    JNIHelper helper(mVM);
 
-    jclass clsScanResult = (env)->FindClass("android/net/wifi/ScanResult");
-    if (clsScanResult == NULL) {
-        ALOGE("Error in accessing class");
-        return;
-    }
+    ALOGD("onSignificantWifiChange called, vm = %p, obj = %p", mVM, mCls);
 
-    jobjectArray scanResults = env->NewObjectArray(num_results, clsScanResult, NULL);
+    JNIObject<jobjectArray> scanResults = helper.newObjectArray(
+            num_results, "android/net/wifi/ScanResult", NULL);
     if (scanResults == NULL) {
         ALOGE("Error in allocating array");
         return;
@@ -699,71 +891,72 @@
 
     for (unsigned i = 0; i < num_results; i++) {
 
-        wifi_significant_change_result result = *(results[i]);
+        wifi_significant_change_result &result = *(results[i]);
 
-        jobject scanResult = createObject(env, "android/net/wifi/ScanResult");
+        JNIObject<jobject> scanResult = helper.createObject("android/net/wifi/ScanResult");
         if (scanResult == NULL) {
             ALOGE("Error in creating scan result");
             return;
         }
 
-        // setStringField(env, scanResult, "SSID", results[i].ssid);
+        // helper.setStringField(scanResult, "SSID", results[i].ssid);
 
         char bssid[32];
         sprintf(bssid, "%02x:%02x:%02x:%02x:%02x:%02x", result.bssid[0], result.bssid[1],
             result.bssid[2], result.bssid[3], result.bssid[4], result.bssid[5]);
 
-        setStringField(env, scanResult, "BSSID", bssid);
+        helper.setStringField(scanResult, "BSSID", bssid);
 
-        setIntField(env, scanResult, "level", result.rssi[0]);
-        setIntField(env, scanResult, "frequency", result.channel);
-        // setLongField(env, scanResult, "timestamp", result.ts);
+        helper.setIntField(scanResult, "level", result.rssi[0]);
+        helper.setIntField(scanResult, "frequency", result.channel);
+        // helper.setLongField(scanResult, "timestamp", result.ts);
 
-        env->SetObjectArrayElement(scanResults, i, scanResult);
+        helper.setObjectArrayElement(scanResults, i, scanResult);
     }
 
-    reportEvent(env, mCls, "onSignificantWifiChange", "(I[Landroid/net/wifi/ScanResult;)V",
-        id, scanResults);
+    helper.reportEvent(mCls, "onSignificantWifiChange", "(I[Landroid/net/wifi/ScanResult;)V",
+        id, scanResults.get());
 
 }
 
 static jboolean android_net_wifi_trackSignificantWifiChange(
         JNIEnv *env, jclass cls, jint iface, jint id, jobject settings)  {
 
-    wifi_interface_handle handle = getIfaceHandle(env, cls, iface);
+    JNIHelper helper(env);
+    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
     ALOGD("tracking significant wifi change on interface[%d] = %p", iface, handle);
 
     wifi_significant_change_params params;
     memset(&params, 0, sizeof(params));
 
-    params.rssi_sample_size = getIntField(env, settings, "rssiSampleSize");
-    params.lost_ap_sample_size = getIntField(env, settings, "lostApSampleSize");
-    params.min_breaching = getIntField(env, settings, "minApsBreachingThreshold");
+    params.rssi_sample_size = helper.getIntField(settings, "rssiSampleSize");
+    params.lost_ap_sample_size = helper.getIntField(settings, "lostApSampleSize");
+    params.min_breaching = helper.getIntField(settings, "minApsBreachingThreshold");
 
     const char *bssid_info_array_type = "[Landroid/net/wifi/WifiScanner$BssidInfo;";
-    jobjectArray bssids = (jobjectArray)getObjectField(
-                env, settings, "bssidInfos", bssid_info_array_type);
-    params.num_ap = env->GetArrayLength(bssids);
+    JNIObject<jobjectArray> bssids = helper.getArrayField(
+            settings, "bssidInfos", bssid_info_array_type);
+    params.num_bssid = helper.getArrayLength(bssids);
 
-    if (params.num_ap == 0) {
+    if (params.num_bssid == 0) {
         ALOGE("Error in accessing array");
         return false;
     }
 
     ALOGD("Initialized common fields %d, %d, %d, %d", params.rssi_sample_size,
-            params.lost_ap_sample_size, params.min_breaching, params.num_ap);
+            params.lost_ap_sample_size, params.min_breaching, params.num_bssid);
 
-    for (int i = 0; i < params.num_ap; i++) {
-        jobject objAp = env->GetObjectArrayElement(bssids, i);
+    for (int i = 0; i < params.num_bssid; i++) {
+        JNIObject<jobject> objAp = helper.getObjectArrayElement(bssids, i);
 
-        jstring macAddrString = (jstring) getObjectField(
-                env, objAp, "bssid", "Ljava/lang/String;");
+        JNIObject<jstring> macAddrString = helper.getStringField(objAp, "bssid");
         if (macAddrString == NULL) {
             ALOGE("Error getting bssid field");
             return false;
         }
 
-        const char *bssid = env->GetStringUTFChars(macAddrString, NULL);
+        ScopedUtfChars chars(env, macAddrString.get());
+        const char *bssid = chars.c_str();
         if (bssid == NULL) {
             ALOGE("Error getting bssid");
             return false;
@@ -777,28 +970,29 @@
         sprintf(bssidOut, "%02x:%02x:%02x:%02x:%02x:%02x", addr[0], addr[1],
             addr[2], addr[3], addr[4], addr[5]);
 
-        params.ap[i].low = getIntField(env, objAp, "low");
-        params.ap[i].high = getIntField(env, objAp, "high");
+        params.ap[i].low = helper.getIntField(objAp, "low");
+        params.ap[i].high = helper.getIntField(objAp, "high");
 
         ALOGD("Added bssid %s, [%04d, %04d]", bssidOut, params.ap[i].low, params.ap[i].high);
     }
 
-    ALOGD("Added %d bssids", params.num_ap);
+    ALOGD("Added %d bssids", params.num_bssid);
 
     wifi_significant_change_handler handler;
     memset(&handler, 0, sizeof(handler));
 
     handler.on_significant_change = &onSignificantWifiChange;
-    return wifi_set_significant_change_handler(id, handle, params, handler) == WIFI_SUCCESS;
+    return hal_fn.wifi_set_significant_change_handler(id, handle, params, handler) == WIFI_SUCCESS;
 }
 
 static jboolean android_net_wifi_untrackSignificantWifiChange(
         JNIEnv *env, jclass cls, jint iface, jint id)  {
 
-    wifi_interface_handle handle = getIfaceHandle(env, cls, iface);
+    JNIHelper helper(env);
+    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
     ALOGD("resetting significant wifi change on interface[%d] = %p", iface, handle);
 
-    return wifi_reset_significant_change_handler(id, handle) == WIFI_SUCCESS;
+    return hal_fn.wifi_reset_significant_change_handler(id, handle) == WIFI_SUCCESS;
 }
 
 wifi_iface_stat link_stat;
@@ -820,54 +1014,70 @@
     }
 }
 
+static void android_net_wifi_setLinkLayerStats (JNIEnv *env, jclass cls, jint iface, int enable)  {
+    JNIHelper helper(env);
+    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
+
+    wifi_link_layer_params params;
+    params.aggressive_statistics_gathering = enable;
+    params.mpdu_size_threshold = 128;
+
+    ALOGD("android_net_wifi_setLinkLayerStats: %u\n", enable);
+
+    hal_fn.wifi_set_link_stats(handle, params);
+}
+
 static jobject android_net_wifi_getLinkLayerStats (JNIEnv *env, jclass cls, jint iface)  {
 
+    JNIHelper helper(env);
     wifi_stats_result_handler handler;
     memset(&handler, 0, sizeof(handler));
     handler.on_link_stats_results = &onLinkStatsResults;
-    wifi_interface_handle handle = getIfaceHandle(env, cls, iface);
-    int result = wifi_get_link_stats(0, handle, handler);
+    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
+    int result = hal_fn.wifi_get_link_stats(0, handle, handler);
     if (result < 0) {
         ALOGE("android_net_wifi_getLinkLayerStats: failed to get link statistics\n");
         return NULL;
     }
 
-    jobject wifiLinkLayerStats = createObject(env, "android/net/wifi/WifiLinkLayerStats");
+    JNIObject<jobject> wifiLinkLayerStats = helper.createObject(
+            "android/net/wifi/WifiLinkLayerStats");
     if (wifiLinkLayerStats == NULL) {
        ALOGE("Error in allocating wifiLinkLayerStats");
        return NULL;
     }
 
-    setIntField(env, wifiLinkLayerStats, "beacon_rx", link_stat.beacon_rx);
-    setIntField(env, wifiLinkLayerStats, "rssi_mgmt", link_stat.rssi_mgmt);
-    setLongField(env, wifiLinkLayerStats, "rxmpdu_be", link_stat.ac[WIFI_AC_BE].rx_mpdu);
-    setLongField(env, wifiLinkLayerStats, "rxmpdu_bk", link_stat.ac[WIFI_AC_BK].rx_mpdu);
-    setLongField(env, wifiLinkLayerStats, "rxmpdu_vi", link_stat.ac[WIFI_AC_VI].rx_mpdu);
-    setLongField(env, wifiLinkLayerStats, "rxmpdu_vo", link_stat.ac[WIFI_AC_VO].rx_mpdu);
-    setLongField(env, wifiLinkLayerStats, "txmpdu_be", link_stat.ac[WIFI_AC_BE].tx_mpdu);
-    setLongField(env, wifiLinkLayerStats, "txmpdu_bk", link_stat.ac[WIFI_AC_BK].tx_mpdu);
-    setLongField(env, wifiLinkLayerStats, "txmpdu_vi", link_stat.ac[WIFI_AC_VI].tx_mpdu);
-    setLongField(env, wifiLinkLayerStats, "txmpdu_vo", link_stat.ac[WIFI_AC_VO].tx_mpdu);
-    setLongField(env, wifiLinkLayerStats, "lostmpdu_be", link_stat.ac[WIFI_AC_BE].mpdu_lost);
-    setLongField(env, wifiLinkLayerStats, "lostmpdu_bk", link_stat.ac[WIFI_AC_BK].mpdu_lost);
-    setLongField(env, wifiLinkLayerStats, "lostmpdu_vi",  link_stat.ac[WIFI_AC_VI].mpdu_lost);
-    setLongField(env, wifiLinkLayerStats, "lostmpdu_vo", link_stat.ac[WIFI_AC_VO].mpdu_lost);
-    setLongField(env, wifiLinkLayerStats, "retries_be", link_stat.ac[WIFI_AC_BE].retries);
-    setLongField(env, wifiLinkLayerStats, "retries_bk", link_stat.ac[WIFI_AC_BK].retries);
-    setLongField(env, wifiLinkLayerStats, "retries_vi", link_stat.ac[WIFI_AC_VI].retries);
-    setLongField(env, wifiLinkLayerStats, "retries_vo", link_stat.ac[WIFI_AC_VO].retries);
+    helper.setIntField(wifiLinkLayerStats, "beacon_rx", link_stat.beacon_rx);
+    helper.setIntField(wifiLinkLayerStats, "rssi_mgmt", link_stat.rssi_mgmt);
+    helper.setLongField(wifiLinkLayerStats, "rxmpdu_be", link_stat.ac[WIFI_AC_BE].rx_mpdu);
+    helper.setLongField(wifiLinkLayerStats, "rxmpdu_bk", link_stat.ac[WIFI_AC_BK].rx_mpdu);
+    helper.setLongField(wifiLinkLayerStats, "rxmpdu_vi", link_stat.ac[WIFI_AC_VI].rx_mpdu);
+    helper.setLongField(wifiLinkLayerStats, "rxmpdu_vo", link_stat.ac[WIFI_AC_VO].rx_mpdu);
+    helper.setLongField(wifiLinkLayerStats, "txmpdu_be", link_stat.ac[WIFI_AC_BE].tx_mpdu);
+    helper.setLongField(wifiLinkLayerStats, "txmpdu_bk", link_stat.ac[WIFI_AC_BK].tx_mpdu);
+    helper.setLongField(wifiLinkLayerStats, "txmpdu_vi", link_stat.ac[WIFI_AC_VI].tx_mpdu);
+    helper.setLongField(wifiLinkLayerStats, "txmpdu_vo", link_stat.ac[WIFI_AC_VO].tx_mpdu);
+    helper.setLongField(wifiLinkLayerStats, "lostmpdu_be", link_stat.ac[WIFI_AC_BE].mpdu_lost);
+    helper.setLongField(wifiLinkLayerStats, "lostmpdu_bk", link_stat.ac[WIFI_AC_BK].mpdu_lost);
+    helper.setLongField(wifiLinkLayerStats, "lostmpdu_vi",  link_stat.ac[WIFI_AC_VI].mpdu_lost);
+    helper.setLongField(wifiLinkLayerStats, "lostmpdu_vo", link_stat.ac[WIFI_AC_VO].mpdu_lost);
+    helper.setLongField(wifiLinkLayerStats, "retries_be", link_stat.ac[WIFI_AC_BE].retries);
+    helper.setLongField(wifiLinkLayerStats, "retries_bk", link_stat.ac[WIFI_AC_BK].retries);
+    helper.setLongField(wifiLinkLayerStats, "retries_vi", link_stat.ac[WIFI_AC_VI].retries);
+    helper.setLongField(wifiLinkLayerStats, "retries_vo", link_stat.ac[WIFI_AC_VO].retries);
 
+    helper.setIntField(wifiLinkLayerStats, "on_time", radio_stat.on_time);
+    helper.setIntField(wifiLinkLayerStats, "tx_time", radio_stat.tx_time);
+    helper.setIntField(wifiLinkLayerStats, "rx_time", radio_stat.rx_time);
+    helper.setIntField(wifiLinkLayerStats, "on_time_scan", radio_stat.on_time_scan);
 
-    setIntField(env, wifiLinkLayerStats, "on_time", radio_stat.on_time);
-    setIntField(env, wifiLinkLayerStats, "tx_time", radio_stat.tx_time);
-    setIntField(env, wifiLinkLayerStats, "rx_time", radio_stat.rx_time);
-    setIntField(env, wifiLinkLayerStats, "on_time_scan", radio_stat.on_time_scan);
-
-    return wifiLinkLayerStats;
+    return wifiLinkLayerStats.detach();
 }
 
 static jint android_net_wifi_getSupportedFeatures(JNIEnv *env, jclass cls, jint iface) {
-    wifi_interface_handle handle = getIfaceHandle(env, cls, iface);
+
+    JNIHelper helper(env);
+    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
     feature_set set = 0;
 
     wifi_error result = WIFI_SUCCESS;
@@ -883,31 +1093,24 @@
         | WIFI_FEATURE_EPR;
     */
 
-    result = wifi_get_supported_feature_set(handle, &set);
+    result = hal_fn.wifi_get_supported_feature_set(handle, &set);
     if (result == WIFI_SUCCESS) {
-        /* Temporary workaround for RTT capability */
-        set = set | WIFI_FEATURE_D2AP_RTT;
-        ALOGD("wifi_get_supported_feature_set returned set = 0x%x", set);
+        // ALOGD("wifi_get_supported_feature_set returned set = 0x%x", set);
         return set;
     } else {
-        ALOGD("wifi_get_supported_feature_set returned error = 0x%x", result);
+        ALOGE("wifi_get_supported_feature_set returned error = 0x%x", result);
         return 0;
     }
 }
 
-static void onRttResults(wifi_request_id id, unsigned num_results, wifi_rtt_result results[]) {
-    JNIEnv *env = NULL;
-    mVM->AttachCurrentThread(&env, NULL);
+static void onRttResults(wifi_request_id id, unsigned num_results, wifi_rtt_result* results[]) {
 
-    ALOGD("onRttResults called, vm = %p, obj = %p, env = %p", mVM, mCls, env);
+    JNIHelper helper(mVM);
 
-    jclass clsRttResult = (env)->FindClass("android/net/wifi/RttManager$RttResult");
-    if (clsRttResult == NULL) {
-        ALOGE("Error in accessing class");
-        return;
-    }
+    ALOGD("onRttResults called, vm = %p, obj = %p", mVM, mCls);
 
-    jobjectArray rttResults = env->NewObjectArray(num_results, clsRttResult, NULL);
+    JNIObject<jobjectArray> rttResults = helper.newObjectArray(
+            num_results, "android/net/wifi/RttManager$RttResult", NULL);
     if (rttResults == NULL) {
         ALOGE("Error in allocating array");
         return;
@@ -915,37 +1118,76 @@
 
     for (unsigned i = 0; i < num_results; i++) {
 
-        wifi_rtt_result& result = results[i];
+        wifi_rtt_result *result = results[i];
 
-        jobject rttResult = createObject(env, "android/net/wifi/RttManager$RttResult");
+        JNIObject<jobject> rttResult = helper.createObject("android/net/wifi/RttManager$RttResult");
         if (rttResult == NULL) {
             ALOGE("Error in creating rtt result");
             return;
         }
 
         char bssid[32];
-        sprintf(bssid, "%02x:%02x:%02x:%02x:%02x:%02x", result.addr[0], result.addr[1],
-            result.addr[2], result.addr[3], result.addr[4], result.addr[5]);
+        sprintf(bssid, "%02x:%02x:%02x:%02x:%02x:%02x", result->addr[0], result->addr[1],
+            result->addr[2], result->addr[3], result->addr[4], result->addr[5]);
 
-        setStringField(env, rttResult, "bssid", bssid);
-        setIntField(env,  rttResult, "status",               result.status);
-        setIntField(env,  rttResult, "requestType",          result.type);
-        setLongField(env, rttResult, "ts",                   result.ts);
-        setIntField(env,  rttResult, "rssi",                 result.rssi);
-        setIntField(env,  rttResult, "rssi_spread",          result.rssi_spread);
-        setIntField(env,  rttResult, "tx_rate",              result.tx_rate.bitrate);
-        setLongField(env, rttResult, "rtt_ns",               result.rtt);
-        setLongField(env, rttResult, "rtt_sd_ns",            result.rtt_sd);
-        setLongField(env, rttResult, "rtt_spread_ns",        result.rtt_spread);
-        setIntField(env,  rttResult, "distance_cm",          result.distance);
-        setIntField(env,  rttResult, "distance_sd_cm",       result.distance_sd);
-        setIntField(env,  rttResult, "distance_spread_cm",   result.distance_spread);
+        helper.setStringField(rttResult, "bssid", bssid);
+        helper.setIntField( rttResult, "burstNumber",              result->burst_num);
+        helper.setIntField( rttResult, "measurementFrameNumber",   result->measurement_number);
+        helper.setIntField( rttResult, "successMeasurementFrameNumber",   result->success_number);
+        helper.setIntField(rttResult, "frameNumberPerBurstPeer",   result->number_per_burst_peer);
+        helper.setIntField( rttResult, "status",                   result->status);
+        helper.setIntField( rttResult, "measurementType",          result->type);
+        helper.setIntField(rttResult, "retryAfterDuration",       result->retry_after_duration);
+        helper.setLongField(rttResult, "ts",                       result->ts);
+        helper.setIntField( rttResult, "rssi",                     result->rssi);
+        helper.setIntField( rttResult, "rssiSpread",               result->rssi_spread);
+        helper.setIntField( rttResult, "txRate",                   result->tx_rate.bitrate);
+        helper.setIntField( rttResult, "rxRate",                   result->rx_rate.bitrate);
+        helper.setLongField(rttResult, "rtt",                      result->rtt);
+        helper.setLongField(rttResult, "rttStandardDeviation",     result->rtt_sd);
+        helper.setIntField( rttResult, "distance",                 result->distance);
+        helper.setIntField( rttResult, "distanceStandardDeviation", result->distance_sd);
+        helper.setIntField( rttResult, "distanceSpread",           result->distance_spread);
+        helper.setIntField( rttResult, "burstDuration",             result->burst_duration);
+        helper.setIntField( rttResult, "negotiatedBurstNum",      result->negotiated_burst_num);
 
-        env->SetObjectArrayElement(rttResults, i, rttResult);
+        JNIObject<jobject> LCI = helper.createObject(
+                "android/net/wifi/RttManager$WifiInformationElement");
+        if (result->LCI != NULL && result->LCI->len > 0) {
+            ALOGD("Add LCI in result");
+            helper.setByteField(LCI, "id", result->LCI->id);
+            JNIObject<jbyteArray> elements = helper.newByteArray(result->LCI->len);
+            jbyte *bytes = (jbyte *)&(result->LCI->data[0]);
+            helper.setByteArrayRegion(elements, 0, result->LCI->len, bytes);
+            helper.setObjectField(LCI, "data", "[B", elements);
+        } else {
+            ALOGD("No LCI in result");
+            helper.setByteField(LCI, "id", (byte)(0xff));
+        }
+        helper.setObjectField(rttResult, "LCI",
+            "Landroid/net/wifi/RttManager$WifiInformationElement;", LCI);
+
+        JNIObject<jobject> LCR = helper.createObject(
+                "android/net/wifi/RttManager$WifiInformationElement");
+        if (result->LCR != NULL && result->LCR->len > 0) {
+            ALOGD("Add LCR in result");
+            helper.setByteField(LCR, "id",           result->LCR->id);
+            JNIObject<jbyteArray> elements = helper.newByteArray(result->LCI->len);
+            jbyte *bytes = (jbyte *)&(result->LCR->data[0]);
+            helper.setByteArrayRegion(elements, 0, result->LCI->len, bytes);
+            helper.setObjectField(LCR, "data", "[B", elements);
+        } else {
+            ALOGD("No LCR in result");
+            helper.setByteField(LCR, "id", (byte)(0xff));
+        }
+        helper.setObjectField(rttResult, "LCR",
+            "Landroid/net/wifi/RttManager$WifiInformationElement;", LCR);
+
+        helper.setObjectArrayElement(rttResults, i, rttResult);
     }
 
-    reportEvent(env, mCls, "onRttResults", "(I[Landroid/net/wifi/RttManager$RttResult;)V",
-        id, rttResults);
+    helper.reportEvent(mCls, "onRttResults", "(I[Landroid/net/wifi/RttManager$RttResult;)V",
+        id, rttResults.get());
 }
 
 const int MaxRttConfigs = 16;
@@ -953,20 +1195,22 @@
 static jboolean android_net_wifi_requestRange(
         JNIEnv *env, jclass cls, jint iface, jint id, jobject params)  {
 
-    wifi_interface_handle handle = getIfaceHandle(env, cls, iface);
+    JNIHelper helper(env);
+
+    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
     ALOGD("sending rtt request [%d] = %p", id, handle);
 
     wifi_rtt_config configs[MaxRttConfigs];
     memset(&configs, 0, sizeof(configs));
 
-    int len = env->GetArrayLength((jobjectArray)params);
+    int len = helper.getArrayLength((jobjectArray)params);
     if (len > MaxRttConfigs) {
         return false;
     }
 
     for (int i = 0; i < len; i++) {
 
-        jobject param = env->GetObjectArrayElement((jobjectArray)params, i);
+        JNIObject<jobject> param = helper.getObjectArrayElement((jobjectArray)params, i);
         if (param == NULL) {
             ALOGD("could not get element %d", i);
             continue;
@@ -975,37 +1219,62 @@
         wifi_rtt_config &config = configs[i];
 
         parseMacAddress(env, param, config.addr);
-        config.type = (wifi_rtt_type)getIntField(env, param, "requestType");
-        config.peer = (wifi_peer_type)getIntField(env, param, "deviceType");
-        config.channel.center_freq = getIntField(env, param, "frequency");
-        config.channel.width = (wifi_channel_width)getIntField(env, param, "channelWidth");
-        config.num_samples_per_measurement = getIntField(env, param, "num_samples");
-        config.num_retries_per_measurement = getIntField(env, param, "num_retries");
+        config.type = (wifi_rtt_type)helper.getIntField(param, "requestType");
+        config.peer = (rtt_peer_type)helper.getIntField(param, "deviceType");
+        config.channel.center_freq = helper.getIntField(param, "frequency");
+        config.channel.width = (wifi_channel_width) helper.getIntField(param, "channelWidth");
+        config.channel.center_freq0 = helper.getIntField(param, "centerFreq0");
+        config.channel.center_freq1 = helper.getIntField(param, "centerFreq1");
+
+        config.num_burst = helper.getIntField(param, "numberBurst");
+        config.burst_period = (unsigned) helper.getIntField(param, "interval");
+        config.num_frames_per_burst = (unsigned) helper.getIntField(param, "numSamplesPerBurst");
+        config.num_retries_per_rtt_frame = (unsigned) helper.getIntField(param,
+                "numRetriesPerMeasurementFrame");
+        config.num_retries_per_ftmr = (unsigned) helper.getIntField(param, "numRetriesPerFTMR");
+        config.LCI_request = helper.getBoolField(param, "LCIRequest") ? 1 : 0;
+        config.LCR_request = helper.getBoolField(param, "LCRRequest") ? 1 : 0;
+        config.burst_duration = (unsigned) helper.getIntField(param, "burstTimeout");
+        config.preamble = (wifi_rtt_preamble) helper.getIntField(param, "preamble");
+        config.bw = (wifi_rtt_bw) helper.getIntField(param, "bandwidth");
+
+        ALOGD("RTT request destination %d: type is %d, peer is %d, bw is %d, center_freq is %d ", i,
+                config.type,config.peer, config.channel.width,  config.channel.center_freq);
+        ALOGD("center_freq0 is %d, center_freq1 is %d, num_burst is %d,interval is %d",
+                config.channel.center_freq0, config.channel.center_freq1, config.num_burst,
+                config.burst_period);
+        ALOGD("frames_per_burst is %d, retries of measurement frame is %d, retries_per_ftmr is %d",
+                config.num_frames_per_burst, config.num_retries_per_rtt_frame,
+                config.num_retries_per_ftmr);
+        ALOGD("LCI_requestis %d, LCR_request is %d,  burst_timeout is %d, preamble is %d, bw is %d",
+                config.LCI_request, config.LCR_request, config.burst_duration, config.preamble,
+                config.bw);
     }
 
     wifi_rtt_event_handler handler;
     handler.on_rtt_results = &onRttResults;
 
-    return wifi_rtt_range_request(id, handle, len, configs, handler) == WIFI_SUCCESS;
+    return hal_fn.wifi_rtt_range_request(id, handle, len, configs, handler) == WIFI_SUCCESS;
 }
 
 static jboolean android_net_wifi_cancelRange(
         JNIEnv *env, jclass cls, jint iface, jint id, jobject params)  {
 
-    wifi_interface_handle handle = getIfaceHandle(env, cls, iface);
+    JNIHelper helper(env);
+    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
     ALOGD("cancelling rtt request [%d] = %p", id, handle);
 
     mac_addr addrs[MaxRttConfigs];
     memset(&addrs, 0, sizeof(addrs));
 
-    int len = env->GetArrayLength((jobjectArray)params);
+    int len = helper.getArrayLength((jobjectArray)params);
     if (len > MaxRttConfigs) {
         return false;
     }
 
     for (int i = 0; i < len; i++) {
 
-        jobject param = env->GetObjectArrayElement((jobjectArray)params, i);
+        JNIObject<jobject> param = helper.getObjectArrayElement(params, i);
         if (param == NULL) {
             ALOGD("could not get element %d", i);
             continue;
@@ -1014,58 +1283,803 @@
         parseMacAddress(env, param, addrs[i]);
     }
 
-    return wifi_rtt_range_cancel(id, handle, len, addrs) == WIFI_SUCCESS;
+    return hal_fn.wifi_rtt_range_cancel(id, handle, len, addrs) == WIFI_SUCCESS;
 }
 
 static jboolean android_net_wifi_setScanningMacOui(JNIEnv *env, jclass cls,
         jint iface, jbyteArray param)  {
 
-    wifi_interface_handle handle = getIfaceHandle(env, cls, iface);
+    JNIHelper helper(env);
+    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
     ALOGD("setting scan oui %p", handle);
 
     static const unsigned oui_len = 3;          /* OUI is upper 3 bytes of mac_address */
-    int len = env->GetArrayLength(param);
+    int len = helper.getArrayLength(param);
     if (len != oui_len) {
         ALOGE("invalid oui length %d", len);
         return false;
     }
 
-    jbyte* bytes = env->GetByteArrayElements(param, NULL);
+    ScopedBytesRO paramBytes(env, param);
+    const jbyte* bytes = paramBytes.get();
     if (bytes == NULL) {
         ALOGE("failed to get array");
         return false;
     }
 
-    return wifi_set_scanning_mac_oui(handle, (byte *)bytes) == WIFI_SUCCESS;
+    return hal_fn.wifi_set_scanning_mac_oui(handle, (byte *)bytes) == WIFI_SUCCESS;
+}
+
+static jboolean android_net_wifi_is_get_channels_for_band_supported(JNIEnv *env, jclass cls){
+    return (hal_fn.wifi_get_valid_channels == wifi_get_valid_channels_stub);
 }
 
 static jintArray android_net_wifi_getValidChannels(JNIEnv *env, jclass cls,
         jint iface, jint band)  {
 
-    wifi_interface_handle handle = getIfaceHandle(env, cls, iface);
+    JNIHelper helper(env);
+    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
     ALOGD("getting valid channels %p", handle);
 
     static const int MaxChannels = 64;
     wifi_channel channels[64];
     int num_channels = 0;
-    wifi_error result = wifi_get_valid_channels(handle, band, MaxChannels,
+    wifi_error result = hal_fn.wifi_get_valid_channels(handle, band, MaxChannels,
             channels, &num_channels);
 
     if (result == WIFI_SUCCESS) {
-        jintArray channelArray = env->NewIntArray(num_channels);
+        JNIObject<jintArray> channelArray = helper.newIntArray(num_channels);
         if (channelArray == NULL) {
             ALOGE("failed to allocate channel list");
             return NULL;
         }
 
-        env->SetIntArrayRegion(channelArray, 0, num_channels, channels);
-        return channelArray;
+        helper.setIntArrayRegion(channelArray, 0, num_channels, channels);
+        return channelArray.detach();
     } else {
         ALOGE("failed to get channel list : %d", result);
         return NULL;
     }
 }
 
+static jboolean android_net_wifi_setDfsFlag(JNIEnv *env, jclass cls, jint iface, jboolean dfs) {
+
+    JNIHelper helper(env);
+    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
+    ALOGD("setting dfs flag to %s, %p", dfs ? "true" : "false", handle);
+
+    u32 nodfs = dfs ? 0 : 1;
+    wifi_error result = hal_fn.wifi_set_nodfs_flag(handle, nodfs);
+    return result == WIFI_SUCCESS;
+}
+
+static jobject android_net_wifi_get_rtt_capabilities(JNIEnv *env, jclass cls, jint iface) {
+
+    JNIHelper helper(env);
+    wifi_rtt_capabilities rtt_capabilities;
+    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
+    wifi_error ret = hal_fn.wifi_get_rtt_capabilities(handle, &rtt_capabilities);
+
+    if(WIFI_SUCCESS == ret) {
+         JNIObject<jobject> capabilities = helper.createObject(
+                "android/net/wifi/RttManager$RttCapabilities");
+         helper.setBooleanField(capabilities, "oneSidedRttSupported",
+                 rtt_capabilities.rtt_one_sided_supported == 1);
+         helper.setBooleanField(capabilities, "twoSided11McRttSupported",
+                 rtt_capabilities.rtt_ftm_supported == 1);
+         helper.setBooleanField(capabilities, "lciSupported",
+                 rtt_capabilities.lci_support);
+         helper.setBooleanField(capabilities, "lcrSupported",
+                 rtt_capabilities.lcr_support);
+         helper.setIntField(capabilities, "preambleSupported",
+                 rtt_capabilities.preamble_support);
+         helper.setIntField(capabilities, "bwSupported",
+                 rtt_capabilities.bw_support);
+         ALOGD("One side RTT is: %s", rtt_capabilities.rtt_one_sided_supported ==1 ? "support" :
+                 "not support");
+         ALOGD("Two side RTT is: %s", rtt_capabilities.rtt_ftm_supported == 1 ? "support" :
+                 "not support");
+         ALOGD("LCR is: %s", rtt_capabilities.lcr_support == 1 ? "support" : "not support");
+
+         ALOGD("LCI is: %s", rtt_capabilities.lci_support == 1 ? "support" : "not support");
+
+         ALOGD("Support Preamble is : %d support BW is %d", rtt_capabilities.preamble_support,
+                 rtt_capabilities.bw_support);
+         return capabilities.detach();
+    } else {
+        return NULL;
+    }
+}
+
+static jboolean android_net_wifi_set_Country_Code_Hal(JNIEnv *env,jclass cls, jint iface,
+        jstring country_code) {
+
+    JNIHelper helper(env);
+    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
+
+    ScopedUtfChars chars(env, country_code);
+    const char *country = chars.c_str();
+
+    ALOGD("set country code: %s", country);
+    wifi_error res = hal_fn.wifi_set_country_code(handle, country);
+    return res == WIFI_SUCCESS;
+}
+
+static jboolean android_net_wifi_enable_disable_tdls(JNIEnv *env,jclass cls, jint iface,
+        jboolean enable, jstring addr) {
+
+    JNIHelper helper(env);
+    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
+
+    mac_addr address;
+    parseMacAddress(env, addr, address);
+    wifi_tdls_handler tdls_handler;
+    //tdls_handler.on_tdls_state_changed = &on_tdls_state_changed;
+
+    if(enable) {
+        return (hal_fn.wifi_enable_tdls(handle, address, NULL, tdls_handler) == WIFI_SUCCESS);
+    } else {
+        return (hal_fn.wifi_disable_tdls(handle, address) == WIFI_SUCCESS);
+    }
+}
+
+static void on_tdls_state_changed(mac_addr addr, wifi_tdls_status status) {
+
+    JNIHelper helper(mVM);
+
+    ALOGD("on_tdls_state_changed is called: vm = %p, obj = %p", mVM, mCls);
+
+    char mac[32];
+    sprintf(mac, "%02x:%02x:%02x:%02x:%02x:%02x", addr[0], addr[1], addr[2], addr[3], addr[4],
+            addr[5]);
+
+    JNIObject<jstring> mac_address = helper.newStringUTF(mac);
+    helper.reportEvent(mCls, "onTdlsStatus", "(Ljava/lang/StringII;)V",
+        mac_address.get(), status.state, status.reason);
+
+}
+
+static jobject android_net_wifi_get_tdls_status(JNIEnv *env,jclass cls, jint iface,jstring addr) {
+
+    JNIHelper helper(env);
+    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
+
+    mac_addr address;
+    parseMacAddress(env, addr, address);
+
+    wifi_tdls_status status;
+
+    wifi_error ret;
+    ret = hal_fn.wifi_get_tdls_status(handle, address, &status );
+
+    if (ret != WIFI_SUCCESS) {
+        return NULL;
+    } else {
+        JNIObject<jobject> tdls_status = helper.createObject(
+                "com/android/server/wifi/WifiNative$TdlsStatus");
+        helper.setIntField(tdls_status, "channel", status.channel);
+        helper.setIntField(tdls_status, "global_operating_class", status.global_operating_class);
+        helper.setIntField(tdls_status, "state", status.state);
+        helper.setIntField(tdls_status, "reason", status.reason);
+        return tdls_status.detach();
+    }
+}
+
+static jobject android_net_wifi_get_tdls_capabilities(JNIEnv *env, jclass cls, jint iface) {
+
+    JNIHelper helper(env);
+    wifi_tdls_capabilities tdls_capabilities;
+    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
+    wifi_error ret = hal_fn.wifi_get_tdls_capabilities(handle, &tdls_capabilities);
+
+    if (WIFI_SUCCESS == ret) {
+         JNIObject<jobject> capabilities = helper.createObject(
+                 "com/android/server/wifi/WifiNative$TdlsCapabilities");
+         helper.setIntField(capabilities, "maxConcurrentTdlsSessionNumber",
+                 tdls_capabilities.max_concurrent_tdls_session_num);
+         helper.setBooleanField(capabilities, "isGlobalTdlsSupported",
+                 tdls_capabilities.is_global_tdls_supported == 1);
+         helper.setBooleanField(capabilities, "isPerMacTdlsSupported",
+                 tdls_capabilities.is_per_mac_tdls_supported == 1);
+         helper.setBooleanField(capabilities, "isOffChannelTdlsSupported",
+                 tdls_capabilities.is_off_channel_tdls_supported);
+
+         ALOGD("TDLS Max Concurrent Tdls Session Number is: %d",
+                 tdls_capabilities.max_concurrent_tdls_session_num);
+         ALOGD("Global Tdls is: %s", tdls_capabilities.is_global_tdls_supported == 1 ? "support" :
+                 "not support");
+         ALOGD("Per Mac Tdls is: %s", tdls_capabilities.is_per_mac_tdls_supported == 1 ? "support" :
+                 "not support");
+         ALOGD("Off Channel Tdls is: %s", tdls_capabilities.is_off_channel_tdls_supported == 1 ?
+                 "support" : "not support");
+
+         return capabilities.detach();
+    } else {
+        return NULL;
+    }
+}
+
+// ----------------------------------------------------------------------------
+// Debug framework
+// ----------------------------------------------------------------------------
+static jint android_net_wifi_get_supported_logger_feature(JNIEnv *env, jclass cls, jint iface){
+    //Not implemented yet
+    JNIHelper helper(env);
+    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
+    return -1;
+}
+
+static jobject android_net_wifi_get_driver_version(JNIEnv *env, jclass cls, jint iface) {
+     //Need to be fixed. The memory should be allocated from lower layer
+    //char *buffer = NULL;
+    JNIHelper helper(env);
+    int buffer_length =  256;
+    char *buffer = (char *)malloc(buffer_length);
+    if (!buffer) return NULL;
+    memset(buffer, 0, buffer_length);
+    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
+
+    ALOGD("android_net_wifi_get_driver_version = %p", handle);
+
+    if (handle == 0) {
+        return NULL;
+    }
+
+    wifi_error result = hal_fn.wifi_get_driver_version(handle, buffer, buffer_length);
+
+    if (result == WIFI_SUCCESS) {
+        ALOGD("buffer is %p, length is %d", buffer, buffer_length);
+        JNIObject<jstring> driver_version = helper.newStringUTF(buffer);
+        free(buffer);
+        return driver_version.detach();
+    } else {
+        ALOGD("Fail to get driver version");
+        free(buffer);
+        return NULL;
+    }
+}
+
+static jobject android_net_wifi_get_firmware_version(JNIEnv *env, jclass cls, jint iface) {
+
+    //char *buffer = NULL;
+    JNIHelper helper(env);
+    int buffer_length = 256;
+    char *buffer = (char *)malloc(buffer_length);
+    if (!buffer) return NULL;
+    memset(buffer, 0, buffer_length);
+    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
+
+    ALOGD("android_net_wifi_get_firmware_version = %p", handle);
+
+    if (handle == 0) {
+        return NULL;
+    }
+
+    wifi_error result = hal_fn.wifi_get_firmware_version(handle, buffer, buffer_length);
+
+    if (result == WIFI_SUCCESS) {
+        ALOGD("buffer is %p, length is %d", buffer, buffer_length);
+        JNIObject<jstring> firmware_version = helper.newStringUTF(buffer);
+        free(buffer);
+        return firmware_version.detach();
+    } else {
+        ALOGD("Fail to get Firmware version");
+        free(buffer);
+        return NULL;
+    }
+}
+
+static jobject android_net_wifi_get_ring_buffer_status (JNIEnv *env, jclass cls, jint iface) {
+
+    JNIHelper helper(env);
+    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
+
+    ALOGD("android_net_wifi_get_ring_buffer_status = %p", handle);
+
+    if (handle == 0) {
+        return NULL;
+    }
+
+    //wifi_ring_buffer_status *status = NULL;
+    u32 num_rings = 10;
+    wifi_ring_buffer_status *status =
+        (wifi_ring_buffer_status *)malloc(sizeof(wifi_ring_buffer_status) * num_rings);
+    if (!status) return NULL;
+    memset(status, 0, sizeof(wifi_ring_buffer_status) * num_rings);
+    wifi_error result = hal_fn.wifi_get_ring_buffers_status(handle, &num_rings, status);
+    if (result == WIFI_SUCCESS) {
+        ALOGD("status is %p, number is %d", status, num_rings);
+
+        JNIObject<jobjectArray> ringBuffersStatus = helper.newObjectArray(
+            num_rings, "com/android/server/wifi/WifiNative$RingBufferStatus", NULL);
+
+        wifi_ring_buffer_status *tmp = status;
+
+        for(u32 i = 0; i < num_rings; i++, tmp++) {
+
+            JNIObject<jobject> ringStatus = helper.createObject(
+                    "com/android/server/wifi/WifiNative$RingBufferStatus");
+
+            if (ringStatus == NULL) {
+                ALOGE("Error in creating ringBufferStatus");
+                free(status);
+                return NULL;
+            }
+
+            char name[32];
+            for(int j = 0; j < 32; j++) {
+                name[j] = tmp->name[j];
+            }
+
+            helper.setStringField(ringStatus, "name", name);
+            helper.setIntField(ringStatus, "flag", tmp->flags);
+            helper.setIntField(ringStatus, "ringBufferId", tmp->ring_id);
+            helper.setIntField(ringStatus, "ringBufferByteSize", tmp->ring_buffer_byte_size);
+            helper.setIntField(ringStatus, "verboseLevel", tmp->verbose_level);
+            helper.setIntField(ringStatus, "writtenBytes", tmp->written_bytes);
+            helper.setIntField(ringStatus, "readBytes", tmp->read_bytes);
+            helper.setIntField(ringStatus, "writtenRecords", tmp->written_records);
+
+            helper.setObjectArrayElement(ringBuffersStatus, i, ringStatus);
+        }
+
+        free(status);
+        return ringBuffersStatus.detach();
+    } else {
+        free(status);
+        return NULL;
+    }
+}
+
+static void on_ring_buffer_data(char *ring_name, char *buffer, int buffer_size,
+        wifi_ring_buffer_status *status) {
+
+    if (!ring_name || !buffer || !status ||
+            (unsigned int)buffer_size <= sizeof(wifi_ring_buffer_entry)) {
+        ALOGE("Error input for on_ring_buffer_data!");
+        return;
+    }
+
+
+    JNIHelper helper(mVM);
+    /* ALOGD("on_ring_buffer_data called, vm = %p, obj = %p, env = %p buffer size = %d", mVM,
+            mCls, env, buffer_size); */
+
+    JNIObject<jobject> ringStatus = helper.createObject(
+                    "com/android/server/wifi/WifiNative$RingBufferStatus");
+    if (status == NULL) {
+        ALOGE("Error in creating ringBufferStatus");
+        return;
+    }
+
+    helper.setStringField(ringStatus, "name", ring_name);
+    helper.setIntField(ringStatus, "flag", status->flags);
+    helper.setIntField(ringStatus, "ringBufferId", status->ring_id);
+    helper.setIntField(ringStatus, "ringBufferByteSize", status->ring_buffer_byte_size);
+    helper.setIntField(ringStatus, "verboseLevel", status->verbose_level);
+    helper.setIntField(ringStatus, "writtenBytes", status->written_bytes);
+    helper.setIntField(ringStatus, "readBytes", status->read_bytes);
+    helper.setIntField(ringStatus, "writtenRecords", status->written_records);
+
+    JNIObject<jbyteArray> bytes = helper.newByteArray(buffer_size);
+    helper.setByteArrayRegion(bytes, 0, buffer_size, (jbyte*)buffer);
+
+    helper.reportEvent(mCls,"onRingBufferData",
+            "(Lcom/android/server/wifi/WifiNative$RingBufferStatus;[B)V",
+            ringStatus.get(), bytes.get());
+}
+
+static void on_alert_data(wifi_request_id id, char *buffer, int buffer_size, int err_code){
+
+    JNIHelper helper(mVM);
+    ALOGD("on_alert_data called, vm = %p, obj = %p, buffer_size = %d, error code = %d"
+            , mVM, mCls, buffer_size, err_code);
+
+    if (buffer_size > 0) {
+        JNIObject<jbyteArray> records = helper.newByteArray(buffer_size);
+        jbyte *bytes = (jbyte *) buffer;
+        helper.setByteArrayRegion(records, 0,buffer_size, bytes);
+        helper.reportEvent(mCls,"onWifiAlert","([BI)V", records.get(), err_code);
+    } else {
+        helper.reportEvent(mCls,"onWifiAlert","([BI)V", NULL, err_code);
+    }
+}
+
+
+static jboolean android_net_wifi_start_logging_ring_buffer(JNIEnv *env, jclass cls, jint iface,
+        jint verbose_level,jint flags, jint max_interval,jint min_data_size, jstring ring_name) {
+
+    JNIHelper helper(env);
+    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
+
+    ALOGD("android_net_wifi_start_logging_ring_buffer = %p", handle);
+
+    if (handle == 0) {
+        return false;
+    }
+
+    ScopedUtfChars chars(env, ring_name);
+    const char* ring_name_const_char = chars.c_str();
+    int ret = hal_fn.wifi_start_logging(handle, verbose_level,
+            flags, max_interval, min_data_size, const_cast<char *>(ring_name_const_char));
+
+    if (ret != WIFI_SUCCESS) {
+        ALOGE("Fail to start logging for ring %s", ring_name_const_char);
+    } else {
+        ALOGD("start logging for ring %s", ring_name_const_char);
+    }
+
+    return ret == WIFI_SUCCESS;
+}
+
+static jboolean android_net_wifi_get_ring_buffer_data(JNIEnv *env, jclass cls, jint iface,
+        jstring ring_name) {
+
+    JNIHelper helper(env);
+    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
+    // ALOGD("android_net_wifi_get_ring_buffer_data = %p", handle);
+
+    ScopedUtfChars chars(env, ring_name);
+    const char* ring_name_const_char = chars.c_str();
+    int result = hal_fn.wifi_get_ring_data(handle, const_cast<char *>(ring_name_const_char));
+    return result == WIFI_SUCCESS;
+}
+
+
+void on_firmware_memory_dump(char *buffer, int buffer_size) {
+
+    JNIHelper helper(mVM);
+    /* ALOGD("on_firmware_memory_dump called, vm = %p, obj = %p, env = %p buffer_size = %d"
+            , mVM, mCls, env, buffer_size); */
+
+    if (buffer_size > 0) {
+        JNIObject<jbyteArray> dump = helper.newByteArray(buffer_size);
+        jbyte *bytes = (jbyte *) (buffer);
+        helper.setByteArrayRegion(dump, 0, buffer_size, bytes);
+        helper.reportEvent(mCls,"onWifiFwMemoryAvailable","([B)V", dump.get());
+    }
+}
+
+static jboolean android_net_wifi_get_fw_memory_dump(JNIEnv *env, jclass cls, jint iface){
+
+    JNIHelper helper(env);
+    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
+    // ALOGD("android_net_wifi_get_fw_memory_dump = %p", handle);
+
+    if (handle == NULL) {
+        ALOGE("Can not get wifi_interface_handle");
+        return false;
+    }
+
+    wifi_firmware_memory_dump_handler fw_dump_handle;
+    fw_dump_handle.on_firmware_memory_dump = on_firmware_memory_dump;
+    int result = hal_fn.wifi_get_firmware_memory_dump(handle, fw_dump_handle);
+    return result == WIFI_SUCCESS;
+
+}
+
+static jboolean android_net_wifi_set_log_handler(JNIEnv *env, jclass cls, jint iface, jint id) {
+
+    JNIHelper helper(env);
+    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
+    ALOGD("android_net_wifi_set_log_handler = %p", handle);
+
+    //initialize the handler on first time
+    wifi_ring_buffer_data_handler handler;
+    handler.on_ring_buffer_data = &on_ring_buffer_data;
+    int result = hal_fn.wifi_set_log_handler(id, handle, handler);
+    if (result != WIFI_SUCCESS) {
+        ALOGE("Fail to set logging handler");
+        return false;
+    }
+
+    //set alter handler This will start alert too
+    wifi_alert_handler alert_handler;
+    alert_handler.on_alert = &on_alert_data;
+    result = hal_fn.wifi_set_alert_handler(id, handle, alert_handler);
+    if (result != WIFI_SUCCESS) {
+        ALOGE(" Fail to set alert handler");
+        return false;
+    }
+
+    return true;
+}
+
+static jboolean android_net_wifi_reset_log_handler(JNIEnv *env, jclass cls, jint iface, jint id) {
+
+    JNIHelper helper(env);
+    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
+
+    //reset alter handler
+    ALOGD("android_net_wifi_reset_alert_handler = %p", handle);
+    int result = hal_fn.wifi_reset_alert_handler(id, handle);
+    if (result != WIFI_SUCCESS) {
+        ALOGE(" Fail to reset alert handler");
+        return false;
+    }
+
+    //reset log handler
+    ALOGD("android_net_wifi_reset_log_handler = %p", handle);
+    result = hal_fn.wifi_reset_log_handler(id, handle);
+    if (result != WIFI_SUCCESS) {
+        ALOGE("Fail to reset logging handler");
+        return false;
+    }
+
+    return true;
+}
+
+// ----------------------------------------------------------------------------
+// ePno framework
+// ----------------------------------------------------------------------------
+
+
+static void onPnoNetworkFound(wifi_request_id id,
+                                          unsigned num_results, wifi_scan_result *results) {
+
+    JNIHelper helper(mVM);
+
+    ALOGD("onPnoNetworkFound called, vm = %p, obj = %p, num_results %u", mVM, mCls, num_results);
+
+    if (results == 0 || num_results == 0) {
+       ALOGE("onPnoNetworkFound: Error no results");
+       return;
+    }
+
+    jbyte *bytes;
+    JNIObject<jobjectArray> scanResults(helper, NULL);
+    //jbyteArray elements;
+
+    for (unsigned i=0; i<num_results; i++) {
+
+        JNIObject<jobject> scanResult = createScanResult(helper, &results[i]);
+        if (i == 0) {
+            scanResults = helper.newObjectArray(
+                    num_results, "android/net/wifi/ScanResult", scanResult);
+            if (scanResults == 0) {
+                ALOGD("cant allocate array");
+            } else {
+                ALOGD("allocated array %u", helper.getArrayLength(scanResults));
+            }
+        } else {
+            helper.setObjectArrayElement(scanResults, i, scanResult);
+        }
+
+        ALOGD("Scan result with ie length %d, i %u, <%s> rssi=%d %02x:%02x:%02x:%02x:%02x:%02x",
+                results->ie_length, i, results[i].ssid, results[i].rssi, results[i].bssid[0],
+                results[i].bssid[1],results[i].bssid[2], results[i].bssid[3], results[i].bssid[4],
+                results[i].bssid[5]);
+
+        /*elements = helper.newByteArray(results->ie_length);
+        if (elements == NULL) {
+            ALOGE("Error in allocating array");
+            return;
+        }*/
+
+        //ALOGD("onPnoNetworkFound: Setting byte array");
+
+        //bytes = (jbyte *)&(results->ie_data[0]);
+        //helper.setByteArrayRegion(elements, 0, results->ie_length, bytes);
+
+        //ALOGD("onPnoNetworkFound: Returning result");
+    }
+
+
+    ALOGD("calling report");
+
+    helper.reportEvent(mCls, "onPnoNetworkFound", "(I[Landroid/net/wifi/ScanResult;)V", id,
+               scanResults.get());
+        ALOGD("free ref");
+}
+
+static jboolean android_net_wifi_setPnoListNative(
+        JNIEnv *env, jclass cls, jint iface, jint id, jobject list)  {
+
+    JNIHelper helper(env);
+    wifi_epno_handler handler;
+    handler.on_network_found = &onPnoNetworkFound;
+
+    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
+    ALOGD("configure ePno list request [%d] = %p", id, handle);
+
+    if (list == NULL) {
+        // stop pno
+        int result = hal_fn.wifi_set_epno_list(id, handle, 0, NULL, handler);
+        ALOGE(" setPnoListNative: STOP result = %d", result);
+        return result >= 0;
+    }
+
+    wifi_epno_network net_list[MAX_PNO_SSID];
+    memset(&net_list, 0, sizeof(net_list));
+
+    size_t len = helper.getArrayLength((jobjectArray)list);
+    if (len > (size_t)MAX_PNO_SSID) {
+        return false;
+    }
+
+    for (unsigned int i = 0; i < len; i++) {
+
+        JNIObject<jobject> pno_net = helper.getObjectArrayElement((jobjectArray)list, i);
+        if (pno_net == NULL) {
+            ALOGD("setPnoListNative: could not get element %d", i);
+            continue;
+        }
+
+        JNIObject<jstring> sssid = helper.getStringField(pno_net, "SSID");
+        if (sssid == NULL) {
+              ALOGE("Error setPnoListNative: getting ssid field");
+              return false;
+        }
+
+        ScopedUtfChars chars(env, (jstring)sssid.get());
+        const char *ssid = chars.c_str();
+        if (ssid == NULL) {
+             ALOGE("Error setPnoListNative: getting ssid");
+             return false;
+        }
+        int ssid_len = strnlen((const char*)ssid, 33);
+        if (ssid_len > 32) {
+           ALOGE("Error setPnoListNative: long ssid %u", strnlen((const char*)ssid, 256));
+           return false;
+        }
+
+        if (ssid_len > 1 && ssid[0] == '"' && ssid[ssid_len-1])
+        {
+            // strip leading and trailing '"'
+            ssid++;
+            ssid_len-=2;
+        }
+        if (ssid_len == 0) {
+            ALOGE("Error setPnoListNative: zero length ssid, skip it");
+            continue;
+        }
+        memcpy(net_list[i].ssid, ssid, ssid_len);
+
+        int rssit = helper.getIntField(pno_net, "rssi_threshold");
+        net_list[i].rssi_threshold = (byte)rssit;
+        int a = helper.getIntField(pno_net, "auth");
+        net_list[i].auth_bit_field = a;
+        int f = helper.getIntField(pno_net, "flags");
+        net_list[i].flags = f;
+        ALOGE(" setPnoListNative: idx %u rssi %d/%d auth %x/%x flags %x/%x [%s]", i,
+                (signed)net_list[i].rssi_threshold, net_list[i].rssi_threshold,
+                net_list[i].auth_bit_field, a, net_list[i].flags, f, net_list[i].ssid);
+    }
+
+    int result = hal_fn.wifi_set_epno_list(id, handle, len, net_list, handler);
+    ALOGE(" setPnoListNative: result %d", result);
+
+    return result >= 0;
+}
+
+static jboolean android_net_wifi_setLazyRoam(
+        JNIEnv *env, jclass cls, jint iface, jint id, jboolean enabled, jobject roam_param)  {
+
+    JNIHelper helper(env);
+    wifi_error status = WIFI_SUCCESS;
+    wifi_roam_params params;
+    memset(&params, 0, sizeof(params));
+
+    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
+    ALOGD("configure lazy roam request [%d] = %p", id, handle);
+
+    if (roam_param != NULL) {
+        params.A_band_boost_threshold  = helper.getIntField(roam_param, "A_band_boost_threshold");
+        params.A_band_penalty_threshold  = helper.getIntField(roam_param, "A_band_penalty_threshold");
+        params.A_band_boost_factor = helper.getIntField(roam_param, "A_band_boost_factor");
+        params.A_band_penalty_factor  = helper.getIntField(roam_param, "A_band_penalty_factor");
+        params.A_band_max_boost  = helper.getIntField(roam_param, "A_band_max_boost");
+        params.lazy_roam_hysteresis = helper.getIntField(roam_param, "lazy_roam_hysteresis");
+        params.alert_roam_rssi_trigger = helper.getIntField(roam_param, "alert_roam_rssi_trigger");
+        status = hal_fn.wifi_set_gscan_roam_params(id, handle, &params);
+    }
+    ALOGE("android_net_wifi_setLazyRoam configured params status=%d\n", status);
+
+    if (status >= 0) {
+        int doEnable = enabled ? 1 : 0;
+        status = hal_fn.wifi_enable_lazy_roam(id, handle, doEnable);
+        ALOGE("android_net_wifi_setLazyRoam enabled roam status=%d\n", status);
+    }
+    return status >= 0;
+}
+
+static jboolean android_net_wifi_setBssidBlacklist(
+        JNIEnv *env, jclass cls, jint iface, jint id, jobject list)  {
+
+    JNIHelper helper(env);
+    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
+    ALOGD("configure BSSID black list request [%d] = %p", id, handle);
+
+    wifi_bssid_params params;
+    memset(&params, 0, sizeof(params));
+
+    if (list != NULL) {
+        size_t len = helper.getArrayLength((jobjectArray)list);
+        if (len > (size_t)MAX_BLACKLIST_BSSID) {
+            return false;
+        }
+        for (unsigned int i = 0; i < len; i++) {
+
+            JNIObject<jobject> jbssid = helper.getObjectArrayElement(list, i);
+            if (jbssid == NULL) {
+                ALOGD("configure BSSID blacklist: could not get element %d", i);
+                continue;
+            }
+
+            ScopedUtfChars chars(env, (jstring)jbssid.get());
+            const char *bssid = chars.c_str();
+            if (bssid == NULL) {
+                ALOGE("Error getting bssid");
+                return false;
+            }
+
+            mac_addr addr;
+            parseMacAddress(bssid, addr);
+            memcpy(params.bssids[i], addr, sizeof(mac_addr));
+
+            char bssidOut[32];
+            sprintf(bssidOut, "%0x:%0x:%0x:%0x:%0x:%0x", addr[0], addr[1],
+                addr[2], addr[3], addr[4], addr[5]);
+
+            ALOGD("BSSID blacklist: added bssid %s", bssidOut);
+
+            params.num_bssid++;
+        }
+    }
+
+    ALOGD("Added %d bssids", params.num_bssid);
+    return hal_fn.wifi_set_bssid_blacklist(id, handle, params) == WIFI_SUCCESS;
+}
+
+static jboolean android_net_wifi_setSsidWhitelist(
+        JNIEnv *env, jclass cls, jint iface, jint id, jobject list)  {
+
+    JNIHelper helper(env);
+    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
+    ALOGD("configure SSID white list request [%d] = %p", id, handle);
+    wifi_ssid *ssids = NULL;
+    int num_ssids = 0;
+    if (list != NULL) {
+        size_t len = helper.getArrayLength((jobjectArray)list);
+        if (len > 0) {
+            ssids = (wifi_ssid *)malloc(len * sizeof (wifi_ssid));
+            if (!ssids) return false;
+            memset(ssids, 0, len * sizeof (wifi_ssid));
+            for (unsigned int i = 0; i < len; i++) {
+
+                JNIObject<jobject> jssid = helper.getObjectArrayElement(list, i);
+                if (jssid == NULL) {
+                    ALOGD("configure SSID whitelist: could not get element %d", i);
+                    free(ssids);
+                   return false;
+                }
+
+                ScopedUtfChars chars(env, (jstring)jssid.get());
+                const char *utf = chars.c_str();
+                if (utf == NULL) {
+                    ALOGE("Error getting sssid");
+                    free(ssids);
+                    return false;
+                }
+
+                int slen = strnlen(utf, 33);
+                if (slen <= 0 || slen > 32) {
+                    ALOGE("Error wrong ssid length %d", slen);
+                    free(ssids);
+                    return false;
+                }
+
+                memcpy(ssids[i].ssid, utf, slen);
+                num_ssids++;
+                ALOGD("SSID white list: added ssid %s", utf);
+            }
+        }
+    }
+
+    ALOGD("android_net_wifi_setSsidWhitelist Added %d sssids", num_ssids);
+    return hal_fn.wifi_set_ssid_white_list(id, handle, num_ssids, ssids) == WIFI_SUCCESS;
+}
+
 // ----------------------------------------------------------------------------
 
 /*
@@ -1097,7 +2111,7 @@
     { "startScanNative", "(IILcom/android/server/wifi/WifiNative$ScanSettings;)Z",
             (void*) android_net_wifi_startScan},
     { "stopScanNative", "(II)Z", (void*) android_net_wifi_stopScan},
-    { "getScanResultsNative", "(IZ)[Landroid/net/wifi/ScanResult;",
+    { "getScanResultsNative", "(IZ)[Landroid/net/wifi/WifiScanner$ScanData;",
             (void *) android_net_wifi_getScanResults},
     { "setHotlistNative", "(IILandroid/net/wifi/WifiScanner$HotlistSettings;)Z",
             (void*) android_net_wifi_setHotlist},
@@ -1108,14 +2122,53 @@
             (void*) android_net_wifi_untrackSignificantWifiChange},
     { "getWifiLinkLayerStatsNative", "(I)Landroid/net/wifi/WifiLinkLayerStats;",
             (void*) android_net_wifi_getLinkLayerStats},
+    { "setWifiLinkLayerStatsNative", "(II)V",
+            (void*) android_net_wifi_setLinkLayerStats},
     { "getSupportedFeatureSetNative", "(I)I",
             (void*) android_net_wifi_getSupportedFeatures},
     { "requestRangeNative", "(II[Landroid/net/wifi/RttManager$RttParams;)Z",
             (void*) android_net_wifi_requestRange},
     { "cancelRangeRequestNative", "(II[Landroid/net/wifi/RttManager$RttParams;)Z",
             (void*) android_net_wifi_cancelRange},
-    { "setScanningMacOuiNative", "(I[B)Z", (void*) android_net_wifi_setScanningMacOui},
-    { "getChannelsForBandNative", "(II)[I", (void*) android_net_wifi_getValidChannels}
+    { "setScanningMacOuiNative", "(I[B)Z",  (void*) android_net_wifi_setScanningMacOui},
+    { "getChannelsForBandNative", "(II)[I", (void*) android_net_wifi_getValidChannels},
+    { "setDfsFlagNative",         "(IZ)Z",  (void*) android_net_wifi_setDfsFlag},
+    { "toggleInterfaceNative",    "(I)Z",  (void*) android_net_wifi_toggle_interface},
+    { "getRttCapabilitiesNative", "(I)Landroid/net/wifi/RttManager$RttCapabilities;",
+            (void*) android_net_wifi_get_rtt_capabilities},
+    {"setCountryCodeHalNative", "(ILjava/lang/String;)Z",
+            (void*) android_net_wifi_set_Country_Code_Hal},
+    { "setPnoListNative", "(II[Lcom/android/server/wifi/WifiNative$WifiPnoNetwork;)Z",
+            (void*) android_net_wifi_setPnoListNative},
+    {"enableDisableTdlsNative", "(IZLjava/lang/String;)Z",
+            (void*) android_net_wifi_enable_disable_tdls},
+    {"getTdlsStatusNative", "(ILjava/lang/String;)Lcom/android/server/wifi/WifiNative$TdlsStatus;",
+            (void*) android_net_wifi_get_tdls_status},
+    {"getTdlsCapabilitiesNative", "(I)Lcom/android/server/wifi/WifiNative$TdlsCapabilities;",
+            (void*) android_net_wifi_get_tdls_capabilities},
+    {"getSupportedLoggerFeatureSetNative","(I)I",
+            (void*) android_net_wifi_get_supported_logger_feature},
+    {"getDriverVersionNative", "(I)Ljava/lang/String;",
+            (void*) android_net_wifi_get_driver_version},
+    {"getFirmwareVersionNative", "(I)Ljava/lang/String;",
+            (void*) android_net_wifi_get_firmware_version},
+    {"getRingBufferStatusNative", "(I)[Lcom/android/server/wifi/WifiNative$RingBufferStatus;",
+            (void*) android_net_wifi_get_ring_buffer_status},
+    {"startLoggingRingBufferNative", "(IIIIILjava/lang/String;)Z",
+            (void*) android_net_wifi_start_logging_ring_buffer},
+    {"getRingBufferDataNative", "(ILjava/lang/String;)Z",
+            (void*) android_net_wifi_get_ring_buffer_data},
+    {"getFwMemoryDumpNative","(I)Z", (void*) android_net_wifi_get_fw_memory_dump},
+    { "setLazyRoamNative", "(IIZLcom/android/server/wifi/WifiNative$WifiLazyRoamParams;)Z",
+            (void*) android_net_wifi_setLazyRoam},
+    { "setBssidBlacklistNative", "(II[Ljava/lang/String;)Z",
+            (void*)android_net_wifi_setBssidBlacklist},
+    { "setSsidWhitelistNative", "(II[Ljava/lang/String;)Z",
+            (void*)android_net_wifi_setSsidWhitelist},
+    {"setLoggingEventHandlerNative", "(II)Z", (void *) android_net_wifi_set_log_handler},
+    {"resetLogHandlerNative", "(II)Z", (void *) android_net_wifi_reset_log_handler},
+    {"isGetChannelsForBandSupportedNative", "()Z",
+            (void*)android_net_wifi_is_get_channels_for_band_supported}
 };
 
 int register_android_net_wifi_WifiNative(JNIEnv* env) {
diff --git a/service/jni/jni_helper.cpp b/service/jni/jni_helper.cpp
index 8d5b520..7320961 100644
--- a/service/jni/jni_helper.cpp
+++ b/service/jni/jni_helper.cpp
@@ -31,393 +31,595 @@
 
 /* JNI Helpers for wifi_hal implementation */
 
-void throwException( JNIEnv *env, const char *message, int line )
+JNIHelper::JNIHelper(JavaVM *vm)
+{
+    vm->AttachCurrentThread(&mEnv, NULL);
+    mVM = vm;
+}
+
+JNIHelper::JNIHelper(JNIEnv *env)
+{
+    mVM  = NULL;
+    mEnv = env;
+}
+
+JNIHelper::~JNIHelper()
+{
+    if (mVM != NULL) {
+        // mVM->DetachCurrentThread();  /* 'attempting to detach while still running code' */
+        mVM = NULL;                     /* not really required; but may help debugging */
+        mEnv = NULL;                    /* not really required; but may help debugging */
+    }
+}
+
+jobject JNIHelper::newGlobalRef(jobject obj) {
+    return mEnv->NewGlobalRef(obj);
+}
+
+void JNIHelper::deleteGlobalRef(jobject obj) {
+    mEnv->DeleteGlobalRef(obj);
+}
+
+jobject JNIHelper::newLocalRef(jobject obj) {
+    return mEnv->NewLocalRef(obj);
+}
+
+void JNIHelper::deleteLocalRef(jobject obj) {
+    mEnv->DeleteLocalRef(obj);
+}
+
+void JNIHelper::throwException(const char *message, int line)
 {
     ALOGE("error at line %d: %s", line, message);
 
     const char *className = "java/lang/Exception";
 
-    jclass exClass = (env)->FindClass(className );
+    jclass exClass = mEnv->FindClass(className );
     if ( exClass == NULL ) {
         ALOGE("Could not find exception class to throw error");
         ALOGE("error at line %d: %s", line, message);
         return;
     }
 
-    (env)->ThrowNew(exClass, message);
+    mEnv->ThrowNew(exClass, message);
 }
 
-jboolean getBoolField(JNIEnv *env, jobject obj, const char *name)
+jboolean JNIHelper::getBoolField(jobject obj, const char *name)
 {
-    jclass cls = (env)->GetObjectClass(obj);
-    jfieldID field = (env)->GetFieldID(cls, name, "Z");
+    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
+    jfieldID field = mEnv->GetFieldID(cls, name, "Z");
     if (field == 0) {
-        THROW(env, "Error in accessing field");
+        THROW(*this, "Error in accessing field");
         return 0;
     }
 
-    jboolean value = (env)->GetBooleanField(obj, field);
-    env->DeleteLocalRef(cls);
-    return value;
+    return mEnv->GetBooleanField(obj, field);
 }
 
-jint getIntField(JNIEnv *env, jobject obj, const char *name)
+jint JNIHelper::getIntField(jobject obj, const char *name)
 {
-    jclass cls = (env)->GetObjectClass(obj);
-    jfieldID field = (env)->GetFieldID(cls, name, "I");
+    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
+    jfieldID field = mEnv->GetFieldID(cls, name, "I");
     if (field == 0) {
-        THROW(env, "Error in accessing field");
+        THROW(*this, "Error in accessing field");
         return 0;
     }
 
-    jint value = (env)->GetIntField(obj, field);
-    env->DeleteLocalRef(cls);
-    return value;
+    return mEnv->GetIntField(obj, field);
 }
 
-jlong getLongField(JNIEnv *env, jobject obj, const char *name)
+jbyte JNIHelper::getByteField(jobject obj, const char *name)
 {
-    jclass cls = (env)->GetObjectClass(obj);
-    jfieldID field = (env)->GetFieldID(cls, name, "J");
+    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
+    jfieldID field = mEnv->GetFieldID(cls, name, "B");
     if (field == 0) {
-        THROW(env, "Error in accessing field");
+        THROW(*this, "Error in accessing field");
         return 0;
     }
 
-    jlong value = (env)->GetLongField(obj, field);
-    env->DeleteLocalRef(cls);
-    return value;
+    return mEnv->GetByteField(obj, field);
 }
 
-jlong getStaticLongField(JNIEnv *env, jobject obj, const char *name)
+jlong JNIHelper::getLongField(jobject obj, const char *name)
 {
-    jclass cls = (env)->GetObjectClass(obj);
-    jlong result = getStaticLongField(env, cls, name);
-    env->DeleteLocalRef(cls);
-    return result;
-}
-
-jlong getStaticLongField(JNIEnv *env, jclass cls, const char *name)
-{
-    jfieldID field = (env)->GetStaticFieldID(cls, name, "J");
+    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
+    jfieldID field = mEnv->GetFieldID(cls, name, "J");
     if (field == 0) {
-        THROW(env, "Error in accessing field");
-        return 0;
-    }
-    ALOGE("getStaticLongField %s %p", name, cls);
-
-    return (env)->GetStaticLongField(cls, field);
-}
-
-jobject getObjectField(JNIEnv *env, jobject obj, const char *name, const char *type)
-{
-    jclass cls = (env)->GetObjectClass(obj);
-    jfieldID field = (env)->GetFieldID(cls, name, type);
-    if (field == 0) {
-        THROW(env, "Error in accessing field");
+        THROW(*this, "Error in accessing field");
         return 0;
     }
 
-    jobject value = (env)->GetObjectField(obj, field);
-    env->DeleteLocalRef(cls);
-    return value;
+    return mEnv->GetLongField(obj, field);
 }
 
-jlong getLongArrayField(JNIEnv *env, jobject obj, const char *name, int index)
+JNIObject<jstring> JNIHelper::getStringField(jobject obj, const char *name)
 {
-    jclass cls = (env)->GetObjectClass(obj);
-    jfieldID field = (env)->GetFieldID(cls, name, "[J");
+    JNIObject<jobject> m = getObjectField(obj, name, "Ljava/lang/String;");
+    if (m == NULL) {
+        THROW(*this, "Error in accessing field");
+        return JNIObject<jstring>(*this, NULL);
+    }
+
+    return JNIObject<jstring>(*this, (jstring)m.detach());
+}
+
+bool JNIHelper::getStringFieldValue(jobject obj, const char *name, char *buf, int size)
+{
+    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
+    jfieldID field = mEnv->GetFieldID(cls, name, "Ljava/lang/String;");
     if (field == 0) {
-        THROW(env, "Error in accessing field definition");
+        THROW(*this, "Error in accessing field");
         return 0;
     }
 
-    jlongArray array = (jlongArray)(env)->GetObjectField(obj, field);
+    JNIObject<jobject> value(*this, mEnv->GetObjectField(obj, field));
+    JNIObject<jstring> string(*this, (jstring)value.clone());
+    ScopedUtfChars chars(mEnv, string);
+
+    const char *utf = chars.c_str();
+    if (utf == NULL) {
+        THROW(*this, "Error in accessing value");
+        return false;
+    }
+
+    if (*utf != 0 && size < 1) {
+        return false;
+    }
+
+    strncpy(buf, utf, size);
+    if (size > 0) {
+        buf[size - 1] = 0;
+    }
+
+    return true;
+}
+
+jlong JNIHelper::getStaticLongField(jobject obj, const char *name)
+{
+    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
+    return getStaticLongField(cls, name);
+}
+
+jlong JNIHelper::getStaticLongField(jclass cls, const char *name)
+{
+    jfieldID field = mEnv->GetStaticFieldID(cls, name, "J");
+    if (field == 0) {
+        THROW(*this, "Error in accessing field");
+        return 0;
+    }
+    //ALOGE("getStaticLongField %s %p", name, cls);
+    return mEnv->GetStaticLongField(cls, field);
+}
+
+JNIObject<jobject> JNIHelper::getObjectField(jobject obj, const char *name, const char *type)
+{
+    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
+    jfieldID field = mEnv->GetFieldID(cls, name, type);
+    if (field == 0) {
+        THROW(*this, "Error in accessing field");
+        return JNIObject<jobject>(*this, NULL);
+    }
+
+    return JNIObject<jobject>(*this, mEnv->GetObjectField(obj, field));
+}
+
+JNIObject<jobjectArray> JNIHelper::getArrayField(jobject obj, const char *name, const char *type)
+{
+    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
+    jfieldID field = mEnv->GetFieldID(cls, name, type);
+    if (field == 0) {
+        THROW(*this, "Error in accessing field");
+        return JNIObject<jobjectArray>(*this, NULL);
+    }
+
+    return JNIObject<jobjectArray>(*this, (jobjectArray)mEnv->GetObjectField(obj, field));
+}
+
+jlong JNIHelper::getLongArrayField(jobject obj, const char *name, int index)
+{
+    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
+    jfieldID field = mEnv->GetFieldID(cls, name, "[J");
+    if (field == 0) {
+        THROW(*this, "Error in accessing field definition");
+        return 0;
+    }
+
+    JNIObject<jlongArray> array(*this, (jlongArray)mEnv->GetObjectField(obj, field));
     if (array == NULL) {
-        THROW(env, "Error in accessing array");
+        THROW(*this, "Error in accessing array");
         return 0;
     }
 
-    jlong *elem = (env)->GetLongArrayElements(array, 0);
+    jlong *elem = mEnv->GetLongArrayElements(array, 0);
     if (elem == NULL) {
-        THROW(env, "Error in accessing index element");
+        THROW(*this, "Error in accessing index element");
         return 0;
     }
 
     jlong value = elem[index];
-    (env)->ReleaseLongArrayElements(array, elem, 0);
-
-    env->DeleteLocalRef(array);
-    env->DeleteLocalRef(cls);
-
+    mEnv->ReleaseLongArrayElements(array, elem, 0);
     return value;
 }
 
-jlong getStaticLongArrayField(JNIEnv *env, jobject obj, const char *name, int index)
+jlong JNIHelper::getStaticLongArrayField(jobject obj, const char *name, int index)
 {
-    jclass cls = (env)->GetObjectClass(obj);
-    jlong result = getStaticLongArrayField(env, cls, name, index);
-    env->DeleteLocalRef(cls);
-    return result;
+    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
+    return getStaticLongArrayField(cls, name, index);
 }
 
-jlong getStaticLongArrayField(JNIEnv *env, jclass cls, const char *name, int index)
+jlong JNIHelper::getStaticLongArrayField(jclass cls, const char *name, int index)
 {
-    jfieldID field = (env)->GetStaticFieldID(cls, name, "[J");
+    jfieldID field = mEnv->GetStaticFieldID(cls, name, "[J");
     if (field == 0) {
-        THROW(env, "Error in accessing field definition");
+        THROW(*this, "Error in accessing field definition");
         return 0;
     }
 
-    jlongArray array = (jlongArray)(env)->GetStaticObjectField(cls, field);
-    jlong *elem = (env)->GetLongArrayElements(array, 0);
+    JNIObject<jlongArray> array(*this, (jlongArray)mEnv->GetStaticObjectField(cls, field));
+    jlong *elem = mEnv->GetLongArrayElements(array, 0);
     if (elem == NULL) {
-        THROW(env, "Error in accessing index element");
+        THROW(*this, "Error in accessing index element");
         return 0;
     }
 
     jlong value = elem[index];
-    (env)->ReleaseLongArrayElements(array, elem, 0);
-
-    env->DeleteLocalRef(array);
+    mEnv->ReleaseLongArrayElements(array, elem, 0);
     return value;
 }
 
-jobject getObjectArrayField(JNIEnv *env, jobject obj, const char *name, const char *type, int index)
+JNIObject<jobject> JNIHelper::getObjectArrayField(jobject obj, const char *name, const char *type,
+int index)
 {
-    jclass cls = (env)->GetObjectClass(obj);
-    jfieldID field = (env)->GetFieldID(cls, name, type);
+    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
+    jfieldID field = mEnv->GetFieldID(cls, name, type);
     if (field == 0) {
-        THROW(env, "Error in accessing field definition");
-        return 0;
+        THROW(*this, "Error in accessing field definition");
+        return JNIObject<jobject>(*this, NULL);
     }
 
-    jobjectArray array = (jobjectArray)(env)->GetObjectField(obj, field);
-    jobject elem = (env)->GetObjectArrayElement(array, index);
-    if (elem == NULL) {
-        THROW(env, "Error in accessing index element");
-        return 0;
+    JNIObject<jobjectArray> array(*this, (jobjectArray)mEnv->GetObjectField(obj, field));
+    JNIObject<jobject> elem(*this, mEnv->GetObjectArrayElement(array, index));
+    if (elem.isNull()) {
+        THROW(*this, "Error in accessing index element");
+        return JNIObject<jobject>(*this, NULL);
     }
-
-    env->DeleteLocalRef(array);
-    env->DeleteLocalRef(cls);
     return elem;
 }
 
-void setIntField(JNIEnv *env, jobject obj, const char *name, jint value)
+void JNIHelper::setIntField(jobject obj, const char *name, jint value)
 {
-    jclass cls = (env)->GetObjectClass(obj);
+    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
     if (cls == NULL) {
-        THROW(env, "Error in accessing class");
+        THROW(*this, "Error in accessing class");
         return;
     }
 
-    jfieldID field = (env)->GetFieldID(cls, name, "I");
+    jfieldID field = mEnv->GetFieldID(cls, name, "I");
     if (field == NULL) {
-        THROW(env, "Error in accessing field");
+        THROW(*this, "Error in accessing field");
         return;
     }
 
-    (env)->SetIntField(obj, field, value);
-    env->DeleteLocalRef(cls);
+    mEnv->SetIntField(obj, field, value);
 }
 
-void setLongField(JNIEnv *env, jobject obj, const char *name, jlong value)
+void JNIHelper::setByteField(jobject obj, const char *name, jbyte value)
 {
-    jclass cls = (env)->GetObjectClass(obj);
+    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
     if (cls == NULL) {
-        THROW(env, "Error in accessing class");
+        THROW(*this, "Error in accessing class");
         return;
     }
 
-    jfieldID field = (env)->GetFieldID(cls, name, "J");
+    jfieldID field = mEnv->GetFieldID(cls, name, "B");
     if (field == NULL) {
-        THROW(env, "Error in accessing field");
+        THROW(*this, "Error in accessing field");
         return;
     }
 
-    (env)->SetLongField(obj, field, value);
-    env->DeleteLocalRef(cls);
+    mEnv->SetByteField(obj, field, value);
 }
 
-void setStaticLongField(JNIEnv *env, jobject obj, const char *name, jlong value)
+void JNIHelper::setBooleanField(jobject obj, const char *name, jboolean value)
 {
-    jclass cls = (env)->GetObjectClass(obj);
+    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
     if (cls == NULL) {
-        THROW(env, "Error in accessing class");
+        THROW(*this, "Error in accessing class");
         return;
     }
 
-    setStaticLongField(env, cls, name, value);
-    env->DeleteLocalRef(cls);
-}
-
-void setStaticLongField(JNIEnv *env, jclass cls, const char *name, jlong value)
-{
-    jfieldID field = (env)->GetStaticFieldID(cls, name, "J");
+    jfieldID field = mEnv->GetFieldID(cls, name, "Z");
     if (field == NULL) {
-        THROW(env, "Error in accessing field");
+        THROW(*this, "Error in accessing field");
         return;
     }
 
-    (env)->SetStaticLongField(cls, field, value);
+    mEnv->SetBooleanField(obj, field, value);
 }
 
-void setLongArrayField(JNIEnv *env, jobject obj, const char *name, jlongArray value)
+void JNIHelper::setLongField(jobject obj, const char *name, jlong value)
 {
-    jclass cls = (env)->GetObjectClass(obj);
+    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
     if (cls == NULL) {
-        THROW(env, "Error in accessing field");
+        THROW(*this, "Error in accessing class");
         return;
-    } else {
-        ALOGD("cls = %p", cls);
     }
 
-    jfieldID field = (env)->GetFieldID(cls, name, "[J");
+    jfieldID field = mEnv->GetFieldID(cls, name, "J");
     if (field == NULL) {
-        THROW(env, "Error in accessing field");
+        THROW(*this, "Error in accessing field");
         return;
     }
 
-    (env)->SetObjectField(obj, field, value);
-    ALOGD("array field set");
-
-    env->DeleteLocalRef(cls);
+    mEnv->SetLongField(obj, field, value);
 }
 
-void setStaticLongArrayField(JNIEnv *env, jobject obj, const char *name, jlongArray value)
+void JNIHelper::setStaticLongField(jobject obj, const char *name, jlong value)
 {
-    jclass cls = (env)->GetObjectClass(obj);
+    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
     if (cls == NULL) {
-        THROW(env, "Error in accessing field");
+        THROW(*this, "Error in accessing class");
         return;
-    } else {
-        ALOGD("cls = %p", cls);
     }
 
-    setStaticLongArrayField(env, cls, name, value);
-    env->DeleteLocalRef(cls);
+    setStaticLongField(cls, name, value);
 }
 
-void setStaticLongArrayField(JNIEnv *env, jclass cls, const char *name, jlongArray value)
+void JNIHelper::setStaticLongField(jclass cls, const char *name, jlong value)
 {
-    jfieldID field = (env)->GetStaticFieldID(cls, name, "[J");
+    jfieldID field = mEnv->GetStaticFieldID(cls, name, "J");
     if (field == NULL) {
-        THROW(env, "Error in accessing field");
+        THROW(*this, "Error in accessing field");
         return;
     }
 
-    (env)->SetStaticObjectField(cls, field, value);
+    mEnv->SetStaticLongField(cls, field, value);
+}
+
+void JNIHelper::setLongArrayField(jobject obj, const char *name, jlongArray value)
+{
+    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
+    if (cls == NULL) {
+        THROW(*this, "Error in accessing field");
+        return;
+    }
+
+    jfieldID field = mEnv->GetFieldID(cls, name, "[J");
+    if (field == NULL) {
+        THROW(*this, "Error in accessing field");
+        return;
+    }
+
+    mEnv->SetObjectField(obj, field, value);
+}
+
+void JNIHelper::setStaticLongArrayField(jobject obj, const char *name, jlongArray value)
+{
+    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
+    if (cls == NULL) {
+        THROW(*this, "Error in accessing field");
+        return;
+    }
+
+    setStaticLongArrayField(cls, name, value);
+}
+
+void JNIHelper::setStaticLongArrayField(jclass cls, const char *name, jlongArray value)
+{
+    jfieldID field = mEnv->GetStaticFieldID(cls, name, "[J");
+    if (field == NULL) {
+        THROW(*this, "Error in accessing field");
+        return;
+    }
+
+    mEnv->SetStaticObjectField(cls, field, value);
     ALOGD("array field set");
 }
 
-void setLongArrayElement(JNIEnv *env, jobject obj, const char *name, int index, jlong value)
+void JNIHelper::setLongArrayElement(jobject obj, const char *name, int index, jlong value)
 {
-    jclass cls = (env)->GetObjectClass(obj);
+    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
     if (cls == NULL) {
-        THROW(env, "Error in accessing field");
+        THROW(*this, "Error in accessing field");
         return;
-    } else {
-        ALOGD("cls = %p", cls);
     }
 
-    jfieldID field = (env)->GetFieldID(cls, name, "[J");
+    jfieldID field = mEnv->GetFieldID(cls, name, "[J");
     if (field == NULL) {
-        THROW(env, "Error in accessing field");
+        THROW(*this, "Error in accessing field");
         return;
-    } else {
-        ALOGD("field = %p", field);
     }
 
-    jlongArray array = (jlongArray)(env)->GetObjectField(obj, field);
+    JNIObject<jlongArray> array(*this, (jlongArray)mEnv->GetObjectField(obj, field));
     if (array == NULL) {
-        THROW(env, "Error in accessing array");
+        THROW(*this, "Error in accessing array");
         return;
-    } else {
-        ALOGD("array = %p", array);
     }
 
-    jlong *elem = (env)->GetLongArrayElements(array, NULL);
+    jlong *elem = mEnv->GetLongArrayElements(array, NULL);
     if (elem == NULL) {
-        THROW(env, "Error in accessing index element");
+        THROW(*this, "Error in accessing index element");
         return;
     }
 
     elem[index] = value;
-    env->ReleaseLongArrayElements(array, elem, 0);
-    env->DeleteLocalRef(array);
-    env->DeleteLocalRef(cls);
+    mEnv->ReleaseLongArrayElements(array, elem, 0);
 }
 
-void setObjectField(JNIEnv *env, jobject obj, const char *name, const char *type, jobject value)
+void JNIHelper::setObjectField(jobject obj, const char *name, const char *type, jobject value)
 {
-    jclass cls = (env)->GetObjectClass(obj);
+    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
     if (cls == NULL) {
-        THROW(env, "Error in accessing class");
+        THROW(*this, "Error in accessing class");
         return;
     }
 
-    jfieldID field = (env)->GetFieldID(cls, name, type);
+    jfieldID field = mEnv->GetFieldID(cls, name, type);
     if (field == NULL) {
-        THROW(env, "Error in accessing field");
+        THROW(*this, "Error in accessing field");
         return;
     }
 
-    (env)->SetObjectField(obj, field, value);
-    env->DeleteLocalRef(cls);
+    mEnv->SetObjectField(obj, field, value);
 }
 
-void setStringField(JNIEnv *env, jobject obj, const char *name, const char *value)
+jboolean JNIHelper::setStringField(jobject obj, const char *name, const char *value)
 {
-    jstring str = env->NewStringUTF(value);
+    JNIObject<jstring> str(*this, mEnv->NewStringUTF(value));
+
+    if (mEnv->ExceptionCheck()) {
+        mEnv->ExceptionDescribe();
+        mEnv->ExceptionClear();
+        return false;
+    }
 
     if (str == NULL) {
-        THROW(env, "Error in accessing class");
-        return;
+        THROW(*this, "Error creating string");
+        return false;
     }
 
-    setObjectField(env, obj, name, "Ljava/lang/String;", str);
-    env->DeleteLocalRef(str);
+    setObjectField(obj, name, "Ljava/lang/String;", str);
+    return true;
 }
 
-void reportEvent(JNIEnv *env, jclass cls, const char *method, const char *signature, ...)
+void JNIHelper::reportEvent(jclass cls, const char *method, const char *signature, ...)
 {
     va_list params;
     va_start(params, signature);
 
-    jmethodID methodID = env->GetStaticMethodID(cls, method, signature);
-    if (method == NULL) {
+    jmethodID methodID = mEnv->GetStaticMethodID(cls, method, signature);
+    if (methodID == 0) {
         ALOGE("Error in getting method ID");
         return;
     }
 
-    env->CallStaticVoidMethodV(cls, methodID, params);
+    mEnv->CallStaticVoidMethodV(cls, methodID, params);
+    if (mEnv->ExceptionCheck()) {
+        mEnv->ExceptionDescribe();
+        mEnv->ExceptionClear();
+    }
+
     va_end(params);
 }
 
-jobject createObject(JNIEnv *env, const char *className)
+jboolean JNIHelper::callStaticMethod(jclass cls, const char *method, const char *signature, ...)
 {
-    jclass cls = env->FindClass(className);
+    va_list params;
+    va_start(params, signature);
+
+    jmethodID methodID = mEnv->GetStaticMethodID(cls, method, signature);
+    if (methodID == 0) {
+        ALOGE("Error in getting method ID");
+        return false;
+    }
+
+    jboolean result = mEnv->CallStaticBooleanMethodV(cls, methodID, params);
+    if (mEnv->ExceptionCheck()) {
+        mEnv->ExceptionDescribe();
+        mEnv->ExceptionClear();
+        return false;
+    }
+
+    va_end(params);
+    return result;
+}
+
+JNIObject<jobject> JNIHelper::createObject(const char *className)
+{
+    JNIObject<jclass> cls(*this, mEnv->FindClass(className));
     if (cls == NULL) {
-        ALOGE("Error in finding class");
-        return NULL;
+        ALOGE("Error in finding class %s", className);
+        return JNIObject<jobject>(*this, NULL);
     }
 
-    jmethodID constructor = env->GetMethodID(cls, "<init>", "()V");
-    if (constructor == NULL) {
-        ALOGE("Error in constructor ID");
-        return NULL;
+    jmethodID constructor = mEnv->GetMethodID(cls, "<init>", "()V");
+    if (constructor == 0) {
+        ALOGE("Error in constructor ID for %s", className);
+        return JNIObject<jobject>(*this, NULL);
     }
-    jobject obj = env->NewObject(cls, constructor);
-    if (constructor == NULL) {
+
+    JNIObject<jobject> obj(*this, mEnv->NewObject(cls, constructor));
+    if (obj == NULL) {
         ALOGE("Could not create new object of %s", className);
-        return NULL;
+        return JNIObject<jobject>(*this, NULL);
     }
 
-    env->DeleteLocalRef(cls);
     return obj;
 }
 
+JNIObject<jobjectArray> JNIHelper::createObjectArray(const char *className, int num)
+{
+    JNIObject<jclass> cls(*this, mEnv->FindClass(className));
+    if (cls == NULL) {
+        ALOGE("Error in finding class %s", className);
+        return JNIObject<jobjectArray>(*this, NULL);
+    }
+
+    JNIObject<jobject> array(*this, mEnv->NewObjectArray(num, cls.get(), NULL));
+    if (array.get() == NULL) {
+        ALOGE("Error in creating array of class %s", className);
+        return JNIObject<jobjectArray>(*this, NULL);
+    }
+
+    return JNIObject<jobjectArray>(*this, (jobjectArray)array.detach());
+}
+
+JNIObject<jobject> JNIHelper::getObjectArrayElement(jobjectArray array, int index)
+{
+    return JNIObject<jobject>(*this, mEnv->GetObjectArrayElement(array, index));
+}
+
+JNIObject<jobject> JNIHelper::getObjectArrayElement(jobject array, int index)
+{
+    return getObjectArrayElement((jobjectArray)array, index);
+}
+
+int JNIHelper::getArrayLength(jarray array) {
+    return mEnv->GetArrayLength(array);
+}
+
+JNIObject<jobjectArray> JNIHelper::newObjectArray(int num, const char *className, jobject val) {
+    JNIObject<jclass> cls(*this, mEnv->FindClass(className));
+    if (cls == NULL) {
+        ALOGE("Error in finding class %s", className);
+        return JNIObject<jobjectArray>(*this, NULL);
+    }
+
+    return JNIObject<jobjectArray>(*this, mEnv->NewObjectArray(num, cls, val));
+}
+
+JNIObject<jbyteArray> JNIHelper::newByteArray(int num) {
+    return JNIObject<jbyteArray>(*this, mEnv->NewByteArray(num));
+}
+
+JNIObject<jintArray> JNIHelper::newIntArray(int num) {
+    return JNIObject<jintArray>(*this, mEnv->NewIntArray(num));
+}
+
+JNIObject<jlongArray> JNIHelper::newLongArray(int num) {
+    return JNIObject<jlongArray>(*this, mEnv->NewLongArray(num));
+}
+
+JNIObject<jstring> JNIHelper::newStringUTF(const char *utf) {
+    return JNIObject<jstring>(*this, mEnv->NewStringUTF(utf));
+}
+
+void JNIHelper::setObjectArrayElement(jobjectArray array, int index, jobject obj) {
+    mEnv->SetObjectArrayElement(array, index, obj);
+}
+
+void JNIHelper::setByteArrayRegion(jbyteArray array, int from, int to, jbyte *bytes) {
+    mEnv->SetByteArrayRegion(array, from, to, bytes);
+}
+
+void JNIHelper::setIntArrayRegion(jintArray array, int from, int to, jint *ints) {
+    mEnv->SetIntArrayRegion(array, from, to, ints);
+}
+
+void JNIHelper::setLongArrayRegion(jlongArray array, int from, int to, jlong *longs) {
+    mEnv->SetLongArrayRegion(array, from, to, longs);
+}
+
 }; // namespace android
 
 
diff --git a/service/jni/jni_helper.h b/service/jni/jni_helper.h
index eaa87c2..834b0ac 100644
--- a/service/jni/jni_helper.h
+++ b/service/jni/jni_helper.h
@@ -3,30 +3,158 @@
 
 /* JNI Helpers for wifi_hal to WifiNative bridge implementation */
 
-void throwException( JNIEnv *env, const char *message, int line );
-jboolean  getBoolField(JNIEnv *env, jobject obj, const char *name);
-jint  getIntField(JNIEnv *env, jobject obj, const char *name);
-jlong getLongField(JNIEnv *env, jobject obj, const char *name);
-jobject getObjectField(JNIEnv *env, jobject obj, const char *name, const char *type);
-jlong getLongArrayField(JNIEnv *env, jobject obj, const char *name, int index);
-jobject getObjectArrayField(JNIEnv *env, jobject obj, const char *name, const char *type, int index);
-void setIntField(JNIEnv *env, jobject obj, const char *name, jint value);
-void setLongField(JNIEnv *env, jobject obj, const char *name, jlong value);
-void setLongArrayField(JNIEnv *env, jobject obj, const char *name, jlongArray value);
-void setLongArrayElement(JNIEnv *env, jobject obj, const char *name, int index, jlong value);
-void setStringField(JNIEnv *env, jobject obj, const char *name, const char *value);
-void reportEvent(JNIEnv *env, jclass cls, const char *method, const char *signature, ...);
-jobject createObject(JNIEnv *env, const char *className);
-void setObjectField(JNIEnv *env, jobject obj, const char *name, const char *type, jobject value);
+class JNIHelper;
 
-jlong getStaticLongField(JNIEnv *env, jobject obj, const char *name);
-jlong getStaticLongField(JNIEnv *env, jclass cls, const char *name);
-void setStaticLongField(JNIEnv *env, jobject obj, const char *name, jlong value);
-void setStaticLongField(JNIEnv *env, jclass cls, const char *name, jlong value);
-jlong getStaticLongArrayField(JNIEnv *env, jobject obj, const char *name, int index);
-jlong getStaticLongArrayField(JNIEnv *env, jclass cls, const char *name, int index);
-void setStaticLongArrayField(JNIEnv *env, jobject obj, const char *name, jlongArray value);
-void setStaticLongArrayField(JNIEnv *env, jclass obj, const char *name, jlongArray value);
+template<typename T>
+class JNIObject {
+protected:
+    JNIHelper &mHelper;
+    T mObj;
+public:
+    JNIObject(JNIHelper &helper, T obj);
+    JNIObject(const JNIObject<T>& rhs);
+    virtual ~JNIObject();
+    JNIHelper& getHelper() const {
+        return mHelper;
+    }
+    T get() const {
+        return mObj;
+    }
+    operator T() const {
+        return mObj;
+    }
+    bool isNull() const {
+        return mObj == NULL;
+    }
+    void release();
+    T detach() {
+        T tObj = mObj;
+        mObj = NULL;
+        return tObj;
+    }
+    T clone();
+    JNIObject<T>& operator = (const JNIObject<T>& rhs) {
+        release();
+        mHelper = rhs.mHelper;
+        mObj = rhs.mObj;
+        return *this;
+    }
+    void print() {
+        ALOGD("holding %p", mObj);
+    }
+
+private:
+    template<typename T2>
+    JNIObject(const JNIObject<T2>& rhs);
+};
+
+class JNIHelper {
+    JavaVM *mVM;
+    JNIEnv *mEnv;
+
+public :
+    JNIHelper(JavaVM *vm);
+    JNIHelper(JNIEnv *env);
+    ~JNIHelper();
+
+    void throwException(const char *message, int line);
+
+    /* helpers to deal with members */
+    jboolean getBoolField(jobject obj, const char *name);
+    jint getIntField(jobject obj, const char *name);
+    jlong getLongField(jobject obj, const char *name);
+    JNIObject<jstring> getStringField(jobject obj, const char *name);
+    bool getStringFieldValue(jobject obj, const char *name, char *buf, int size);
+    JNIObject<jobject> getObjectField(jobject obj, const char *name, const char *type);
+    JNIObject<jobjectArray> getArrayField(jobject obj, const char *name, const char *type);
+    jlong getLongArrayField(jobject obj, const char *name, int index);
+    JNIObject<jobject> getObjectArrayField(
+            jobject obj, const char *name, const char *type, int index);
+    void setIntField(jobject obj, const char *name, jint value);
+    void setByteField(jobject obj, const char *name, jbyte value);
+    jbyte getByteField(jobject obj, const char *name);
+    void setBooleanField(jobject obj, const char *name, jboolean value);
+    void setLongField(jobject obj, const char *name, jlong value);
+    void setLongArrayField(jobject obj, const char *name, jlongArray value);
+    void setLongArrayElement(jobject obj, const char *name, int index, jlong value);
+    jboolean setStringField(jobject obj, const char *name, const char *value);
+    void reportEvent(jclass cls, const char *method, const char *signature, ...);
+    JNIObject<jobject> createObject(const char *className);
+    JNIObject<jobjectArray> createObjectArray(const char *className, int size);
+    void setObjectField(jobject obj, const char *name, const char *type, jobject value);
+
+    /* helpers to deal with static members */
+    jlong getStaticLongField(jobject obj, const char *name);
+    jlong getStaticLongField(jclass cls, const char *name);
+    void setStaticLongField(jobject obj, const char *name, jlong value);
+    void setStaticLongField(jclass cls, const char *name, jlong value);
+    jlong getStaticLongArrayField(jobject obj, const char *name, int index);
+    jlong getStaticLongArrayField(jclass cls, const char *name, int index);
+    void setStaticLongArrayField(jobject obj, const char *name, jlongArray value);
+    void setStaticLongArrayField(jclass obj, const char *name, jlongArray value);
+    jboolean callStaticMethod(jclass cls, const char *method, const char *signature, ...);
+
+    JNIObject<jobject> getObjectArrayElement(jobjectArray array, int index);
+    JNIObject<jobject> getObjectArrayElement(jobject array, int index);
+    int getArrayLength(jarray array);
+    JNIObject<jobjectArray> newObjectArray(int num, const char *className, jobject val);
+    JNIObject<jbyteArray> newByteArray(int num);
+    JNIObject<jintArray> newIntArray(int num);
+    JNIObject<jlongArray> newLongArray(int num);
+    JNIObject<jstring> newStringUTF(const char *utf);
+    void setObjectArrayElement(jobjectArray array, int index, jobject obj);
+    void setByteArrayRegion(jbyteArray array, int from, int to, jbyte *bytes);
+    void setIntArrayRegion(jintArray array, int from, int to, jint *ints);
+    void setLongArrayRegion(jlongArray array, int from, int to, jlong *longs);
+
+    jobject newGlobalRef(jobject obj);
+    void deleteGlobalRef(jobject obj);
+
+private:
+    /* Jni wrappers */
+    friend class JNIObject<jobject>;
+    friend class JNIObject<jstring>;
+    friend class JNIObject<jobjectArray>;
+    friend class JNIObject<jclass>;
+    friend class JNIObject<jlongArray>;
+    friend class JNIObject<jbyteArray>;
+    friend class JNIObject<jintArray>;
+    jobject newLocalRef(jobject obj);
+    void deleteLocalRef(jobject obj);
+};
+
+template<typename T>
+JNIObject<T>::JNIObject(JNIHelper &helper, T obj)
+    : mHelper(helper), mObj(obj)
+{ }
+
+template<typename T>
+JNIObject<T>::JNIObject(const JNIObject<T>& rhs)
+    : mHelper(rhs.mHelper), mObj(NULL)
+{
+    mObj = (T)mHelper.newLocalRef(rhs.mObj);
 }
 
-#define THROW(env, message)      throwException(env, message, __LINE__)
+template<typename T>
+JNIObject<T>::~JNIObject() {
+    release();
+}
+
+template<typename T>
+void JNIObject<T>::release()
+{
+    if (mObj != NULL) {
+        mHelper.deleteLocalRef(mObj);
+        mObj = NULL;
+    }
+}
+
+template<typename T>
+T JNIObject<T>::clone()
+{
+    return mHelper.newLocalRef(mObj);
+}
+
+}
+
+#define THROW(env, message)      (env).throwException(message, __LINE__)
diff --git a/service/jni/wifi_hal_stub.h b/service/jni/wifi_hal_stub.h
new file mode 100644
index 0000000..bd00947
--- /dev/null
+++ b/service/jni/wifi_hal_stub.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#ifndef __WIFI_HAL_STUB_H__
+#define __WIFI_HAL_STUB_H__
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+#include "wifi_hal.h"
+/* declare all HAL stub API here*/
+wifi_error wifi_initialize_stub(wifi_handle *handle);
+void wifi_cleanup_stub(wifi_handle handle, wifi_cleaned_up_handler handler);
+void wifi_event_loop_stub(wifi_handle handle);
+void wifi_get_error_info_stub(wifi_error err, const char **msg);
+wifi_error wifi_get_supported_feature_set_stub(wifi_interface_handle handle, feature_set *set);
+wifi_error wifi_get_concurrency_matrix_stub(wifi_interface_handle handle, int set_size_max,
+        feature_set set[], int *set_size);
+wifi_error wifi_get_ifaces_stub(wifi_handle handle, int *num_ifaces, wifi_interface_handle **ifaces);
+wifi_error wifi_get_iface_name_stub(wifi_interface_handle iface, char *name, size_t size);
+wifi_error wifi_set_iface_event_handler_stub(wifi_request_id id, wifi_interface_handle iface,
+        wifi_event_handler eh);
+wifi_error wifi_reset_iface_event_handler_stub(wifi_request_id id, wifi_interface_handle iface);
+wifi_error wifi_set_nodfs_flag_stub(wifi_interface_handle handle, u32 nodfs);
+wifi_error wifi_set_scanning_mac_oui_stub(wifi_interface_handle handle, unsigned char *oui);
+wifi_error wifi_get_supported_channels_stub(wifi_handle handle, int *size, wifi_channel *list);
+wifi_error wifi_is_epr_supported_stub(wifi_handle handle);
+wifi_error wifi_start_gscan_stub(wifi_request_id id, wifi_interface_handle iface,
+        wifi_scan_cmd_params params, wifi_scan_result_handler handler);
+wifi_error wifi_stop_gscan_stub(wifi_request_id id, wifi_interface_handle iface);
+wifi_error wifi_get_cached_gscan_results_stub(wifi_interface_handle iface, byte flush,
+        int max, wifi_cached_scan_results *results, int *num);
+wifi_error wifi_set_bssid_hotlist_stub(wifi_request_id id, wifi_interface_handle iface,
+        wifi_bssid_hotlist_params params, wifi_hotlist_ap_found_handler handler);
+wifi_error wifi_reset_bssid_hotlist_stub(wifi_request_id id, wifi_interface_handle iface);
+wifi_error wifi_set_significant_change_handler_stub(wifi_request_id id, wifi_interface_handle iface,
+        wifi_significant_change_params params, wifi_significant_change_handler handler);
+wifi_error wifi_reset_significant_change_handler_stub(wifi_request_id id,
+        wifi_interface_handle iface);
+wifi_error wifi_get_gscan_capabilities_stub(wifi_interface_handle handle,
+        wifi_gscan_capabilities *capabilities);
+wifi_error wifi_set_link_stats_stub(wifi_interface_handle iface, wifi_link_layer_params params);
+wifi_error wifi_get_link_stats_stub(wifi_request_id id,
+         wifi_interface_handle iface, wifi_stats_result_handler handler);
+wifi_error wifi_clear_link_stats_stub(wifi_interface_handle iface,
+       u32 stats_clear_req_mask, u32 *stats_clear_rsp_mask, u8 stop_req, u8 *stop_rsp);
+wifi_error wifi_get_valid_channels_stub(wifi_interface_handle handle,
+         int band, int max_channels, wifi_channel *channels, int *num_channels);
+wifi_error wifi_rtt_range_request_stub(wifi_request_id id, wifi_interface_handle iface,
+         unsigned num_rtt_config, wifi_rtt_config rtt_config[], wifi_rtt_event_handler handler);
+wifi_error wifi_rtt_range_cancel_stub(wifi_request_id id,  wifi_interface_handle iface,
+         unsigned num_devices, mac_addr addr[]);
+wifi_error wifi_get_rtt_capabilities_stub(wifi_interface_handle iface,
+         wifi_rtt_capabilities *capabilities);
+wifi_error wifi_set_nodfs_flag_stub(wifi_interface_handle iface, u32 nodfs);
+wifi_error wifi_start_logging_stub(wifi_interface_handle iface, u32 verbose_level, u32 flags,
+         u32 max_interval_sec, u32 min_data_size, char *buffer_name);
+wifi_error wifi_set_epno_list_stub(int id, wifi_interface_info *iface, int num_networks,
+         wifi_epno_network *networks, wifi_epno_handler handler);
+wifi_error wifi_set_country_code_stub(wifi_interface_handle iface, const char *code);
+wifi_error wifi_get_firmware_memory_dump_stub( wifi_interface_handle iface,
+        wifi_firmware_memory_dump_handler handler);
+wifi_error wifi_set_log_handler_stub(wifi_request_id id, wifi_interface_handle iface,
+        wifi_ring_buffer_data_handler handler);
+wifi_error wifi_reset_log_handler_stub(wifi_request_id id, wifi_interface_handle iface);
+wifi_error wifi_set_alert_handler_stub(wifi_request_id id, wifi_interface_handle iface,
+        wifi_alert_handler handler);
+wifi_error wifi_reset_alert_handler_stub(wifi_request_id id, wifi_interface_handle iface);
+wifi_error wifi_get_firmware_version_stub(wifi_interface_handle iface, char *buffer,
+        int buffer_size);
+wifi_error wifi_get_ring_buffers_status_stub(wifi_interface_handle iface,
+        u32 *num_rings, wifi_ring_buffer_status *status);
+wifi_error wifi_get_logger_supported_feature_set_stub(wifi_interface_handle iface,
+        unsigned int *support);
+wifi_error wifi_get_ring_data_stub(wifi_interface_handle iface, char *ring_name);
+wifi_error wifi_set_epno_list_stub(int id, wifi_interface_info *iface, int num_networks,
+        wifi_epno_network *networks, wifi_epno_handler handler);
+wifi_error wifi_enable_tdls_stub(wifi_interface_handle iface, mac_addr addr,
+        wifi_tdls_params *params, wifi_tdls_handler handler);
+wifi_error wifi_disable_tdls_stub(wifi_interface_handle iface, mac_addr addr);
+wifi_error wifi_get_tdls_status_stub(wifi_interface_handle iface, mac_addr addr,
+        wifi_tdls_status *status);
+wifi_error wifi_get_tdls_capabilities_stub(wifi_interface_handle iface,
+        wifi_tdls_capabilities *capabilities);
+wifi_error wifi_get_driver_version_stub(wifi_interface_handle iface, char *buffer,
+        int buffer_size);
+ wifi_error wifi_set_country_code_stub(wifi_interface_handle iface, const char *code);
+wifi_error wifi_set_bssid_blacklist_stub(wifi_request_id id, wifi_interface_handle iface,
+        wifi_bssid_params params);
+wifi_error wifi_enable_lazy_roam_stub(wifi_request_id id, wifi_interface_handle iface, int enable);
+wifi_error wifi_set_bssid_preference_stub(wifi_request_id id, wifi_interface_handle iface,
+                                    int num_bssid, wifi_bssid_preference *prefs);
+wifi_error wifi_set_gscan_roam_params_stub(wifi_request_id id, wifi_interface_handle iface,
+                                        wifi_roam_params * params);
+wifi_error wifi_set_ssid_white_list_stub(wifi_request_id id, wifi_interface_handle iface,
+                       int num_networks, wifi_ssid *ssids);
+wifi_error wifi_start_sending_offloaded_packet_stub(wifi_request_id id,
+        wifi_interface_handle iface, u8 *ip_packet, u16 ip_packet_len,
+        u8 *src_mac_addr, u8 *dst_mac_addr, u32 period_msec);
+wifi_error wifi_stop_sending_offloaded_packet_stub(wifi_request_id id, wifi_interface_handle iface);
+#ifdef __cplusplus
+}
+#endif
+#endif //__WIFI_HAL_STUB_H__
diff --git a/service/lib/wifi_hal.cpp b/service/lib/wifi_hal.cpp
index 237efd9..25bb373 100644
--- a/service/lib/wifi_hal.cpp
+++ b/service/lib/wifi_hal.cpp
@@ -1,135 +1,6 @@
 #include <stdint.h>
 #include "wifi_hal.h"
 
-wifi_error wifi_initialize(wifi_handle *handle) {
-    return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-void wifi_cleanup(wifi_handle handle, wifi_cleaned_up_handler handler) {
-}
-
-void wifi_event_loop(wifi_handle handle) {
-
-}
-
-void wifi_get_error_info(wifi_error err, const char **msg) {
-    *msg = NULL;
-}
-
-wifi_error wifi_get_supported_feature_set(wifi_interface_handle handle, feature_set *set) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_get_concurrency_matrix(wifi_interface_handle handle, int max_size,
-        feature_set *matrix, int *size) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_set_scanning_mac_oui(wifi_interface_handle handle, unsigned char *oui) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-/* List of all supported channels, including 5GHz channels */
-wifi_error wifi_get_supported_channels(wifi_handle handle, int *size, wifi_channel *list) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-/* Enhanced power reporting */
-wifi_error wifi_is_epr_supported(wifi_handle handle) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-/* multiple interface support */
-wifi_error wifi_get_ifaces(wifi_handle handle, int *num_ifaces, wifi_interface_handle **ifaces) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_get_iface_name(wifi_interface_handle iface, char *name, size_t size) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_set_iface_event_handler(wifi_request_id id,
-            wifi_interface_handle iface, wifi_event_handler eh) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_reset_iface_event_handler(wifi_request_id id, wifi_interface_handle iface) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_start_gscan(wifi_request_id id, wifi_interface_handle iface,
-        wifi_scan_cmd_params params, wifi_scan_result_handler handler) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_stop_gscan(wifi_request_id id, wifi_interface_handle iface) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_get_cached_gscan_results(wifi_interface_handle iface, byte flush,
-        int max, wifi_scan_result *results, int *num) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_get_cached_gscan_results(wifi_interface_handle iface, byte flush,
-        wifi_scan_result *results, int *num) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_set_bssid_hotlist(wifi_request_id id, wifi_interface_handle iface,
-        wifi_bssid_hotlist_params params, wifi_hotlist_ap_found_handler handler) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_reset_bssid_hotlist(wifi_request_id id, wifi_interface_handle iface) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_set_significant_change_handler(wifi_request_id id, wifi_interface_handle iface,
-        wifi_significant_change_params params, wifi_significant_change_handler handler) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_reset_significant_change_handler(wifi_request_id id, wifi_interface_handle iface) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_get_gscan_capabilities(wifi_interface_handle handle,
-        wifi_gscan_capabilities *capabilities) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_set_link_stats(wifi_interface_handle iface, wifi_link_layer_params params) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_get_link_stats(wifi_request_id id,
-        wifi_interface_handle iface, wifi_stats_result_handler handler) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_clear_link_stats(wifi_interface_handle iface,
-      u32 stats_clear_req_mask, u32 *stats_clear_rsp_mask, u8 stop_req, u8 *stop_rsp) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_get_valid_channels(wifi_interface_handle handle,
-        int band, int max_channels, wifi_channel *channels, int *num_channels) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-/* API to request RTT measurement */
-wifi_error wifi_rtt_range_request(wifi_request_id id, wifi_interface_handle iface,
-        unsigned num_rtt_config, wifi_rtt_config rtt_config[], wifi_rtt_event_handler handler) {
-    return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-/* API to cancel RTT measurements */
-wifi_error wifi_rtt_range_cancel(wifi_request_id id,  wifi_interface_handle iface,
-        unsigned num_devices, mac_addr addr[]) {
-    return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_set_nodfs_flag(wifi_interface_handle iface, u32 nodfs) {
+wifi_error init_wifi_vendor_hal_func_table(wifi_hal_fn *fn) {
     return WIFI_ERROR_NOT_SUPPORTED;
 }
diff --git a/service/lib/wifi_hal_stub.cpp b/service/lib/wifi_hal_stub.cpp
new file mode 100644
index 0000000..65770f3
--- /dev/null
+++ b/service/lib/wifi_hal_stub.cpp
@@ -0,0 +1,253 @@
+#include <stdint.h>
+#include "wifi_hal.h"
+#include "wifi_hal_stub.h"
+
+wifi_error wifi_initialize_stub(wifi_handle *handle) {
+    return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+void wifi_cleanup_stub(wifi_handle handle, wifi_cleaned_up_handler handler) {
+}
+
+void wifi_event_loop_stub(wifi_handle handle) {
+
+}
+
+void wifi_get_error_info_stub(wifi_error err, const char **msg) {
+    *msg = NULL;
+}
+
+wifi_error wifi_get_supported_feature_set_stub(wifi_interface_handle handle, feature_set *set) {
+    return WIFI_ERROR_UNINITIALIZED;
+}
+
+wifi_error wifi_get_concurrency_matrix_stub(wifi_interface_handle handle, int max_size,
+        feature_set *matrix, int *size) {
+    return WIFI_ERROR_UNINITIALIZED;
+}
+
+wifi_error wifi_set_scanning_mac_oui_stub(wifi_interface_handle handle, unsigned char *oui) {
+    return WIFI_ERROR_UNINITIALIZED;
+}
+
+/* List of all supported channels, including 5GHz channels */
+wifi_error wifi_get_supported_channels_stub(wifi_handle handle, int *size, wifi_channel *list) {
+    return WIFI_ERROR_UNINITIALIZED;
+}
+
+/* Enhanced power reporting */
+wifi_error wifi_is_epr_supported_stub(wifi_handle handle) {
+    return WIFI_ERROR_UNINITIALIZED;
+}
+
+/* multiple interface support */
+wifi_error wifi_get_ifaces_stub(wifi_handle handle, int *num_ifaces, wifi_interface_handle **ifaces) {
+    return WIFI_ERROR_UNINITIALIZED;
+}
+
+wifi_error wifi_get_iface_name_stub(wifi_interface_handle iface, char *name, size_t size) {
+    return WIFI_ERROR_UNINITIALIZED;
+}
+
+wifi_error wifi_set_iface_event_handler_stub(wifi_request_id id,
+            wifi_interface_handle iface, wifi_event_handler eh) {
+    return WIFI_ERROR_UNINITIALIZED;
+}
+
+wifi_error wifi_reset_iface_event_handler_stub(wifi_request_id id, wifi_interface_handle iface) {
+    return WIFI_ERROR_UNINITIALIZED;
+}
+
+wifi_error wifi_start_gscan_stub(wifi_request_id id, wifi_interface_handle iface,
+        wifi_scan_cmd_params params, wifi_scan_result_handler handler) {
+    return WIFI_ERROR_UNINITIALIZED;
+}
+
+wifi_error wifi_stop_gscan_stub(wifi_request_id id, wifi_interface_handle iface) {
+    return WIFI_ERROR_UNINITIALIZED;
+}
+
+wifi_error wifi_get_cached_gscan_results_stub(wifi_interface_handle iface, byte flush,
+        int max, wifi_cached_scan_results *results, int *num) {
+    return WIFI_ERROR_UNINITIALIZED;
+}
+
+wifi_error wifi_set_bssid_hotlist_stub(wifi_request_id id, wifi_interface_handle iface,
+        wifi_bssid_hotlist_params params, wifi_hotlist_ap_found_handler handler) {
+    return WIFI_ERROR_UNINITIALIZED;
+}
+
+wifi_error wifi_reset_bssid_hotlist_stub(wifi_request_id id, wifi_interface_handle iface) {
+    return WIFI_ERROR_UNINITIALIZED;
+}
+
+wifi_error wifi_set_significant_change_handler_stub(wifi_request_id id, wifi_interface_handle iface,
+        wifi_significant_change_params params, wifi_significant_change_handler handler) {
+    return WIFI_ERROR_UNINITIALIZED;
+}
+
+wifi_error wifi_reset_significant_change_handler_stub(wifi_request_id id, wifi_interface_handle iface) {
+    return WIFI_ERROR_UNINITIALIZED;
+}
+
+wifi_error wifi_get_gscan_capabilities_stub(wifi_interface_handle handle,
+        wifi_gscan_capabilities *capabilities) {
+    return WIFI_ERROR_UNINITIALIZED;
+}
+
+wifi_error wifi_set_link_stats_stub(wifi_interface_handle iface, wifi_link_layer_params params) {
+    return WIFI_ERROR_UNINITIALIZED;
+}
+
+wifi_error wifi_get_link_stats_stub(wifi_request_id id,
+        wifi_interface_handle iface, wifi_stats_result_handler handler) {
+    return WIFI_ERROR_UNINITIALIZED;
+}
+
+wifi_error wifi_clear_link_stats_stub(wifi_interface_handle iface,
+      u32 stats_clear_req_mask, u32 *stats_clear_rsp_mask, u8 stop_req, u8 *stop_rsp) {
+    return WIFI_ERROR_UNINITIALIZED;
+}
+
+wifi_error wifi_get_valid_channels_stub(wifi_interface_handle handle,
+        int band, int max_channels, wifi_channel *channels, int *num_channels) {
+    return WIFI_ERROR_UNINITIALIZED;
+}
+
+/* API to request RTT measurement */
+wifi_error wifi_rtt_range_request_stub(wifi_request_id id, wifi_interface_handle iface,
+        unsigned num_rtt_config, wifi_rtt_config rtt_config[], wifi_rtt_event_handler handler) {
+    return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+/* API to cancel RTT measurements */
+wifi_error wifi_rtt_range_cancel_stub(wifi_request_id id,  wifi_interface_handle iface,
+        unsigned num_devices, mac_addr addr[]) {
+    return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+/* API to get RTT capability */
+wifi_error wifi_get_rtt_capabilities_stub(wifi_interface_handle iface,
+        wifi_rtt_capabilities *capabilities)
+{
+    return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_set_nodfs_flag_stub(wifi_interface_handle iface, u32 nodfs) {
+    return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_start_logging_stub(wifi_interface_handle iface, u32 verbose_level, u32 flags,
+        u32 max_interval_sec, u32 min_data_size, char *buffer_name) {
+            return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_set_epno_list_stub(int id, wifi_interface_info *iface, int num_networks,
+        wifi_epno_network *networks, wifi_epno_handler handler) {
+    return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_set_country_code_stub(wifi_interface_handle iface, const char *code) {
+    return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_get_firmware_memory_dump_stub( wifi_interface_handle iface,
+        wifi_firmware_memory_dump_handler handler){
+    return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_set_log_handler_stub(wifi_request_id id, wifi_interface_handle iface,
+        wifi_ring_buffer_data_handler handler) {
+    return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_reset_log_handler_stub(wifi_request_id id, wifi_interface_handle iface) {
+    return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_set_alert_handler_stub(wifi_request_id id, wifi_interface_handle iface,
+        wifi_alert_handler handler) {
+    return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_reset_alert_handler_stub(wifi_request_id id, wifi_interface_handle iface) {
+    return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_get_firmware_version_stub( wifi_interface_handle iface, char *buffer,
+        int buffer_size) {
+    return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_get_ring_buffers_status_stub(wifi_interface_handle iface,
+        u32 *num_rings, wifi_ring_buffer_status *status) {
+    return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_get_logger_supported_feature_set_stub(wifi_interface_handle iface,
+        unsigned int *support) {
+    return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_get_ring_data_stub(wifi_interface_handle iface, char *ring_name) {
+    return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_get_driver_version_stub(wifi_interface_handle iface, char *buffer,
+        int buffer_size) {
+    return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_enable_tdls_stub(wifi_interface_handle iface, mac_addr addr,
+        wifi_tdls_params *params, wifi_tdls_handler handler) {
+    return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_disable_tdls_stub(wifi_interface_handle iface, mac_addr addr) {
+    return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_get_tdls_status_stub(wifi_interface_handle iface, mac_addr addr,
+        wifi_tdls_status *status) {
+    return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_get_tdls_capabilities_stub(wifi_interface_handle iface,
+        wifi_tdls_capabilities *capabilities) {
+    return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_set_bssid_blacklist_stub(wifi_request_id id, wifi_interface_handle iface,
+        wifi_bssid_params params) {
+      return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_enable_lazy_roam_stub(wifi_request_id id, wifi_interface_handle iface, int enable)
+{
+      return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_set_bssid_preference_stub(wifi_request_id id, wifi_interface_handle iface,
+                                    int num_bssid, wifi_bssid_preference *prefs) {
+      return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_set_gscan_roam_params_stub(wifi_request_id id, wifi_interface_handle iface,
+                                        wifi_roam_params * params) {
+      return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_set_ssid_white_list_stub(wifi_request_id id, wifi_interface_handle iface,
+                       int num_networks, wifi_ssid *ssids) {
+      return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_start_sending_offloaded_packet_stub(wifi_request_id id,
+        wifi_interface_handle iface, u8 *ip_packet, u16 ip_packet_len,
+        u8 *src_mac_addr, u8 *dst_mac_addr, u32 period_msec) {
+    return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_stop_sending_offloaded_packet_stub(wifi_request_id id, wifi_interface_handle iface) {
+    return WIFI_ERROR_NOT_SUPPORTED;
+}
diff --git a/service/tools/halutil/halutil.cpp b/service/tools/halutil/halutil.cpp
deleted file mode 100644
index 0dc98e3..0000000
--- a/service/tools/halutil/halutil.cpp
+++ /dev/null
@@ -1,1282 +0,0 @@
-#include <stdint.h>
-#include <stdlib.h>
-
-#include "wifi_hal.h"
-
-#define LOG_TAG  "WifiHAL"
-
-#include <utils/Log.h>
-#include <inttypes.h>
-#include <sys/socket.h>
-#include <linux/if.h>
-#include <ctype.h>
-#include <stdarg.h>
-
-pthread_mutex_t printMutex;
-void printMsg(const char *fmt, ...)
-{
-    pthread_mutex_lock(&printMutex);
-    va_list l;
-    va_start(l, fmt);
-
-    vprintf(fmt, l);
-    va_end(l);
-    pthread_mutex_unlock(&printMutex);
-}
-
-template<typename T, unsigned N>
-unsigned countof(T (&rgt)[N]) {
-    return N;
-}
-
-template<typename T>
-T min(const T& t1, const T& t2) {
-    return (t1 < t2) ? t1 : t2;
-}
-
-#define EVENT_BUF_SIZE 2048
-#define MAX_CH_BUF_SIZE  64
-#define MAX_FEATURE_SET  8
-#define HOTLIST_LOST_WINDOW  5
-
-static wifi_handle halHandle;
-static wifi_interface_handle *ifaceHandles;
-static wifi_interface_handle wlan0Handle;
-static wifi_interface_handle p2p0Handle;
-static int numIfaceHandles;
-static int cmdId = 0;
-static int ioctl_sock = 0;
-static int max_event_wait = 5;
-static int stest_max_ap = 10;
-static int stest_base_period = 5000;
-static int stest_threshold = 80;
-static int swctest_rssi_sample_size =  3;
-static int swctest_rssi_lost_ap =  3;
-static int swctest_rssi_min_breaching =  2;
-static int swctest_rssi_ch_threshold =  1;
-static int htest_low_threshold =  90;
-static int htest_high_threshold =  10;
-static int rtt_samples = 30;
-static wifi_band band = WIFI_BAND_UNSPECIFIED;
-
-mac_addr hotlist_bssids[16];
-unsigned char mac_oui[3];
-int channel_list[16];
-int num_hotlist_bssids = 0;
-int num_channels = 0;
-
-void parseMacAddress(const char *str, mac_addr addr);
-
-int linux_set_iface_flags(int sock, const char *ifname, int dev_up)
-{
-    struct ifreq ifr;
-    int ret;
-
-    printMsg("setting interface %s flags (%s)\n", ifname, dev_up ? "UP" : "DOWN");
-
-    if (sock < 0) {
-      printMsg("Bad socket: %d\n", sock);
-      return -1;
-    }
-
-    memset(&ifr, 0, sizeof(ifr));
-    strlcpy(ifr.ifr_name, ifname, IFNAMSIZ);
-
-    printMsg("reading old value\n");
-
-    if (ioctl(sock, SIOCGIFFLAGS, &ifr) != 0) {
-      ret = errno ? -errno : -999;
-      printMsg("Could not read interface %s flags: %d\n", ifname, errno);
-      return ret;
-    }else {
-      printMsg("writing new value\n");
-    }
-
-    if (dev_up) {
-      if (ifr.ifr_flags & IFF_UP) {
-        printMsg("interface %s is already up\n", ifname);
-        return 0;
-      }
-      ifr.ifr_flags |= IFF_UP;
-    }else {
-      if (!(ifr.ifr_flags & IFF_UP)) {
-        printMsg("interface %s is already down\n", ifname);
-        return 0;
-      }
-      ifr.ifr_flags &= ~IFF_UP;
-    }
-
-    if (ioctl(sock, SIOCSIFFLAGS, &ifr) != 0) {
-      printMsg("Could not set interface %s flags \n", ifname);
-      return ret;
-    }else {
-      printMsg("set interface %s flags (%s)\n", ifname, dev_up ? "UP" : "DOWN");
-    }
-    printMsg("Done\n");
-    return 0;
-}
-
-
-static int init() {
-
-    ioctl_sock = socket(PF_INET, SOCK_DGRAM, 0);
-    if (ioctl_sock < 0) {
-      printMsg("Bad socket: %d\n", ioctl_sock);
-      return errno;
-    } else {
-      printMsg("Good socket: %d\n", ioctl_sock);
-    }
-
-    int ret = linux_set_iface_flags(ioctl_sock, "wlan0", 1);
-    if (ret < 0) {
-        return ret;
-    }
-
-    wifi_error res = wifi_initialize(&halHandle);
-    if (res < 0) {
-        return res;
-    }
-
-    res = wifi_get_ifaces(halHandle, &numIfaceHandles, &ifaceHandles);
-    if (res < 0) {
-        return res;
-    }
-
-    char buf[EVENT_BUF_SIZE];
-    for (int i = 0; i < numIfaceHandles; i++) {
-        if (wifi_get_iface_name(ifaceHandles[i], buf, sizeof(buf)) == WIFI_SUCCESS) {
-            if (strcmp(buf, "wlan0") == 0) {
-                printMsg("found interface %s\n", buf);
-                wlan0Handle = ifaceHandles[i];
-            } else if (strcmp(buf, "p2p0") == 0) {
-                printMsg("found interface %s\n", buf);
-                p2p0Handle = ifaceHandles[i];
-            }
-        }
-    }
-
-    return res;
-}
-
-static void cleaned_up_handler(wifi_handle handle) {
-    printMsg("HAL cleaned up handler\n");
-    halHandle = NULL;
-    ifaceHandles = NULL;
-}
-
-static void cleanup() {
-    printMsg("cleaning up HAL\n");
-    wifi_cleanup(halHandle, cleaned_up_handler);
-}
-
-static void *eventThreadFunc(void *context) {
-
-    printMsg("starting wifi event loop\n");
-    wifi_event_loop(halHandle);
-    printMsg("out of wifi event loop\n");
-
-    return NULL;
-}
-
-
-static int getNewCmdId() {
-    return cmdId++;
-}
-
-/* -------------------------------------------  */
-/* helpers                                      */
-/* -------------------------------------------  */
-
-void printScanHeader() {
-    printMsg("SSID\t\t\t\t\tBSSID\t\t  RSSI\tchannel\ttimestamp\tRTT\tRTT SD\n");
-}
-
-void printScanResult(wifi_scan_result result) {
-
-    printMsg("%-32s\t", result.ssid);
-
-    printMsg("%02x:%02x:%02x:%02x:%02x:%02x ", result.bssid[0], result.bssid[1],
-            result.bssid[2], result.bssid[3], result.bssid[4], result.bssid[5]);
-
-    printMsg("%d\t", result.rssi);
-    printMsg("%d\t", result.channel);
-    printMsg("%lld\t", result.ts);
-    printMsg("%lld\t", result.rtt);
-    printMsg("%lld\n", result.rtt_sd);
-}
-
-void printSignificantChangeResult(wifi_significant_change_result *res) {
-
-    wifi_significant_change_result &result = *res;
-    printMsg("%02x:%02x:%02x:%02x:%02x:%02x ", result.bssid[0], result.bssid[1],
-            result.bssid[2], result.bssid[3], result.bssid[4], result.bssid[5]);
-
-    printMsg("%d\t", result.channel);
-
-    for (int i = 0; i < result.num_rssi; i++) {
-        printMsg("%d,", result.rssi[i]);
-    }
-    printMsg("\n");
-}
-
-void printScanCapabilities(wifi_gscan_capabilities capabilities)
-{
-    printMsg("max_scan_cache_size = %d\n", capabilities.max_scan_cache_size);
-    printMsg("max_scan_buckets = %d\n", capabilities.max_scan_buckets);
-    printMsg("max_ap_cache_per_scan = %d\n", capabilities.max_ap_cache_per_scan);
-    printMsg("max_rssi_sample_size = %d\n", capabilities.max_rssi_sample_size);
-    printMsg("max_scan_reporting_threshold = %d\n", capabilities.max_scan_reporting_threshold);
-    printMsg("max_hotlist_aps = %d\n", capabilities.max_hotlist_aps);
-    printMsg("max_significant_wifi_change_aps = %d\n",
-    capabilities.max_significant_wifi_change_aps);
-}
-
-
-/* -------------------------------------------  */
-/* commands and events                          */
-/* -------------------------------------------  */
-
-typedef enum {
-    EVENT_TYPE_SCAN_RESULTS_AVAILABLE = 1000,
-    EVENT_TYPE_HOTLIST_AP_FOUND = 1001,
-    EVENT_TYPE_SIGNIFICANT_WIFI_CHANGE = 1002,
-    EVENT_TYPE_RTT_RESULTS = 1003,
-    EVENT_TYPE_SCAN_COMPLETE = 1004,
-    EVENT_TYPE_HOTLIST_AP_LOST = 1005
-} EventType;
-
-typedef struct {
-    int type;
-    char buf[256];
-} EventInfo;
-
-const int MAX_EVENTS_IN_CACHE = 256;
-EventInfo eventCache[256];
-int eventsInCache = 0;
-pthread_cond_t eventCacheCondition;
-pthread_mutex_t eventCacheMutex;
-
-void putEventInCache(int type, const char *msg) {
-    pthread_mutex_lock(&eventCacheMutex);
-    if (eventsInCache + 1 < MAX_EVENTS_IN_CACHE) {
-        eventCache[eventsInCache].type = type;
-        strcpy(eventCache[eventsInCache].buf, msg);
-        eventsInCache++;
-        pthread_cond_signal(&eventCacheCondition);
-        //printf("put new event in cache; size = %d\n", eventsInCache);
-    } else {
-        printf("Too many events in the cache\n");
-    }
-    pthread_mutex_unlock(&eventCacheMutex);
-}
-
-void getEventFromCache(EventInfo& info) {
-    pthread_mutex_lock(&eventCacheMutex);
-    while (true) {
-        if (eventsInCache > 0) {
-            //printf("found an event in cache; size = %d\n", eventsInCache);
-            info.type = eventCache[0].type;
-            strcpy(info.buf, eventCache[0].buf);
-            eventsInCache--;
-            memmove(&eventCache[0], &eventCache[1], sizeof(EventInfo) * eventsInCache);
-            pthread_mutex_unlock(&eventCacheMutex);
-            return;
-        } else {
-            pthread_cond_wait(&eventCacheCondition, &eventCacheMutex);
-            //printf("pthread_cond_wait unblocked ...\n");
-        }
-    }
-}
-
-int numScanResultsAvailable = 0;
-static void onScanResultsAvailable(wifi_request_id id, unsigned num_results) {
-    printMsg("Received scan results available event\n");
-    numScanResultsAvailable = num_results;
-    putEventInCache(EVENT_TYPE_SCAN_RESULTS_AVAILABLE, "New scan results are available");
-}
-
-static void on_scan_event(wifi_scan_event event, unsigned status) {
-    if (event == WIFI_SCAN_BUFFER_FULL) {
-        printMsg("Received scan complete event - WIFI_SCAN_BUFFER_FULL \n");
-    } else if(event == WIFI_SCAN_COMPLETE) {
-        printMsg("Received scan complete event  - WIFI_SCAN_COMPLETE\n");
-    }
-}
-
-static int scanCmdId;
-static int hotlistCmdId;
-static int significantChangeCmdId;
-static int rttCmdId;
-
-static bool startScan( void (*pfnOnResultsAvailable)(wifi_request_id, unsigned),
-                       int max_ap_per_scan, int base_period, int report_threshold) {
-
-    /* Get capabilties */
-    wifi_gscan_capabilities capabilities;
-    int result = wifi_get_gscan_capabilities(wlan0Handle, &capabilities);
-    if (result < 0) {
-        printMsg("failed to get scan capabilities - %d\n", result);
-        printMsg("trying scan anyway ..\n");
-    } else {
-        printScanCapabilities(capabilities);
-    }
-
-    wifi_scan_cmd_params params;
-    memset(&params, 0, sizeof(params));
-
-    if(num_channels > 0){
-        params.max_ap_per_scan = max_ap_per_scan;
-        params.base_period = base_period;                      // 5 second by default
-        params.report_threshold = report_threshold;
-        params.num_buckets = 1;
-
-        params.buckets[0].bucket = 0;
-        params.buckets[0].band = WIFI_BAND_UNSPECIFIED;
-        params.buckets[0].period = base_period;
-        params.buckets[0].num_channels = num_channels;
-
-        for(int i = 0; i < num_channels; i++){
-            params.buckets[0].channels[i].channel = channel_list[i];
-        }
-
-    } else {
-
-        /* create a schedule to scan channels 1, 6, 11 every 5 second and
-         * scan 36, 40, 44, 149, 153, 157, 161 165 every 10 second */
-
-      params.max_ap_per_scan = max_ap_per_scan;
-      params.base_period = base_period;                      // 5 second
-      params.report_threshold = report_threshold;
-      params.num_buckets = 3;
-
-      params.buckets[0].bucket = 0;
-      params.buckets[0].band = WIFI_BAND_UNSPECIFIED;
-      params.buckets[0].period = 5000;                // 5 second
-      params.buckets[0].report_events = 0;
-      params.buckets[0].num_channels = 2;
-
-      params.buckets[0].channels[0].channel = 2412;
-      params.buckets[0].channels[1].channel = 2437;
-
-      params.buckets[1].bucket = 1;
-      params.buckets[1].band = WIFI_BAND_A;
-      params.buckets[1].period = 10000;               // 10 second
-      params.buckets[1].report_events = 1;
-      params.buckets[1].num_channels = 8;   // driver should ignore list since band is specified
-
-
-      params.buckets[1].channels[0].channel = 5180;
-      params.buckets[1].channels[1].channel = 5200;
-      params.buckets[1].channels[2].channel = 5220;
-      params.buckets[1].channels[3].channel = 5745;
-      params.buckets[1].channels[4].channel = 5765;
-      params.buckets[1].channels[5].channel = 5785;
-      params.buckets[1].channels[6].channel = 5805;
-      params.buckets[1].channels[7].channel = 5825;
-
-      params.buckets[2].bucket = 2;
-      params.buckets[2].band = WIFI_BAND_UNSPECIFIED;
-      params.buckets[2].period = 15000;                // 15 second
-      params.buckets[2].report_events = 2;
-      params.buckets[2].num_channels = 1;
-
-      params.buckets[2].channels[0].channel = 2462;
-
-    }
-
-    wifi_scan_result_handler handler;
-    memset(&handler, 0, sizeof(handler));
-    handler.on_scan_results_available = pfnOnResultsAvailable;
-    handler.on_scan_event = on_scan_event;
-
-    scanCmdId = getNewCmdId();
-    printMsg("Starting scan --->\n");
-    return wifi_start_gscan(scanCmdId, wlan0Handle, params, handler) == WIFI_SUCCESS;
-}
-
-static void stopScan() {
-    wifi_request_id id = scanCmdId;
-    if (id == 0)
-        id = -1;
-
-    wifi_stop_gscan(id, wlan0Handle);
-    scanCmdId = 0;
-}
-
-wifi_scan_result *saved_scan_results;
-unsigned max_saved_scan_results;
-unsigned num_saved_scan_results;
-
-static void on_single_shot_scan_event(wifi_scan_event event, unsigned status) {
-    if (event == WIFI_SCAN_BUFFER_FULL) {
-        printMsg("Received scan complete event - WIFI_SCAN_BUFFER_FULL \n");
-    } else if(event == WIFI_SCAN_COMPLETE) {
-        printMsg("Received scan complete event  - WIFI_SCAN_COMPLETE\n");
-        putEventInCache(EVENT_TYPE_SCAN_COMPLETE, "One scan completed");
-    }
-}
-
-static void on_full_scan_result(wifi_request_id id, wifi_scan_result *r) {
-    if (num_saved_scan_results < max_saved_scan_results) {
-        wifi_scan_result *result = &(saved_scan_results[num_saved_scan_results]);
-        memcpy(result, r, sizeof(wifi_scan_result));
-        //printMsg("Retrieved full scan result for %s(%02x:%02x:%02x:%02x:%02x:%02x)\n",
-        //    result->ssid, result->bssid[0], result->bssid[1], result->bssid[2], result->bssid[3],
-        //    result->bssid[4], result->bssid[5]);
-        num_saved_scan_results++;
-    }
-}
-
-static int scanOnce(wifi_band band, wifi_scan_result *results, int num_results) {
-
-    saved_scan_results = results;
-    max_saved_scan_results = num_results;
-    num_saved_scan_results = 0;
-
-    wifi_scan_cmd_params params;
-    memset(&params, 0, sizeof(params));
-
-    params.max_ap_per_scan = 10;
-    params.base_period = 5000;                        // 5 second by default
-    params.report_threshold = 90;
-    params.num_buckets = 1;
-
-    params.buckets[0].bucket = 0;
-    params.buckets[0].band = band;
-    params.buckets[0].period = 5000;                  // 5 second
-    params.buckets[0].report_events = 2;              // REPORT_EVENTS_AFTER_EACH_SCAN
-    params.buckets[0].num_channels = 0;
-
-    wifi_scan_result_handler handler;
-    memset(&handler, 0, sizeof(handler));
-    handler.on_scan_results_available = NULL;
-    handler.on_scan_event = on_single_shot_scan_event;
-    handler.on_full_scan_result = on_full_scan_result;
-
-    int scanCmdId = getNewCmdId();
-    printMsg("Starting scan --->\n");
-    if (wifi_start_gscan(scanCmdId, wlan0Handle, params, handler) == WIFI_SUCCESS) {
-        int events = 0;
-        while (true) {
-            EventInfo info;
-            memset(&info, 0, sizeof(info));
-            getEventFromCache(info);
-            if (info.type == EVENT_TYPE_SCAN_RESULTS_AVAILABLE
-                || info.type == EVENT_TYPE_SCAN_COMPLETE) {
-                int retrieved_num_results = num_saved_scan_results;
-                if (retrieved_num_results == 0) {
-                    printMsg("fetched 0 scan results, waiting for more..\n");
-                    continue;
-                } else {
-                    printMsg("fetched %d scan results\n", retrieved_num_results);
-
-                    /*
-                    printScanHeader();
-
-                    for (int i = 0; i < retrieved_num_results; i++) {
-                        printScanResult(results[i]);
-                    }
-                    */
-
-                    printMsg("Scan once completed, stopping scan\n");
-                    wifi_stop_gscan(scanCmdId, wlan0Handle);
-                    saved_scan_results = NULL;
-                    max_saved_scan_results = 0;
-                    num_saved_scan_results = 0;
-                    return retrieved_num_results;
-                }
-            }
-        }
-    } else {
-        return 0;
-    }
-}
-
-static void retrieveScanResults() {
-
-    wifi_scan_result results[256];
-    memset(results, 0, sizeof(wifi_scan_result) * 256);
-    printMsg("Retrieve Scan results available -->\n");
-    int num_results = 256;
-    int result = wifi_get_cached_gscan_results(wlan0Handle, 1, num_results, results, &num_results);
-    if (result < 0) {
-        printMsg("failed to fetch scan results : %d\n", result);
-        return;
-    } else {
-        printMsg("fetched %d scan results\n", num_results);
-    }
-
-    printScanHeader();
-    for (int i = 0; i < num_results; i++) {
-        printScanResult(results[i]);
-    }
-}
-
-
-static int compareScanResultsByRssi(const void *p1, const void *p2) {
-    const wifi_scan_result *result1 = static_cast<const wifi_scan_result *>(p1);
-    const wifi_scan_result *result2 = static_cast<const wifi_scan_result *>(p2);
-
-    /* RSSI is -ve, so lower one wins */
-    if (result1->rssi < result2->rssi) {
-        return 1;
-    } else if (result1->rssi == result2->rssi) {
-        return 0;
-    } else {
-        return -1;
-    }
-}
-
-static void sortScanResultsByRssi(wifi_scan_result *results, int num_results) {
-    qsort(results, num_results, sizeof(wifi_scan_result), &compareScanResultsByRssi);
-}
-
-static int removeDuplicateScanResults(wifi_scan_result *results, int num) {
-    /* remove duplicates by BSSID */
-    int num_results = num;
-    for (int i = 0; i < num_results; i++) {
-        //printMsg("Processing result[%d] - %02x:%02x:%02x:%02x:%02x:%02x\n", i,
-        //        results[i].bssid[0], results[i].bssid[1], results[i].bssid[2],
-        //        results[i].bssid[3], results[i].bssid[4], results[i].bssid[5]);
-
-        for (int j = i + 1; j < num_results; ) {
-            if (memcmp(results[i].bssid, results[j].bssid, sizeof(mac_addr)) == 0) {
-                /* 'remove' this scan result from the list */
-                // printMsg("removing dupe entry\n");
-                int num_to_move = num_results - j - 1;
-                memmove(&results[j], &results[j+1], num_to_move * sizeof(wifi_scan_result));
-                num_results--;
-            } else {
-                j++;
-            }
-        }
-
-        // printMsg("num_results = %d\n", num_results);
-    }
-
-    return num_results;
-}
-
-static void onRTTResults (wifi_request_id id, unsigned num_results, wifi_rtt_result result[]) {
-
-    printMsg("RTT results!!\n");
-    printMsg("Addr\t\t\tts\t\tRSSI\tSpread\trtt\tsd\tspread\tdist\tsd\tspread\n");
-
-    for (unsigned i = 0; i < num_results; i++) {
-        printMsg("%02x:%02x:%02x:%02x:%02x:%02x\t%lld\t%d\t%d\t%lld\t%lld\t%lld\t%d\t%d\t%d\n",
-                result[i].addr[0], result[i].addr[1], result[i].addr[2], result[i].addr[3],
-                result[i].addr[4], result[i].addr[5], result[i].ts, result[i].rssi,
-                result[i].rssi_spread, result[i].rtt, result[i].rtt_sd, result[i].rtt_spread,
-                result[i].distance, result[i].distance_sd, result[i].distance_spread);
-    }
-
-    putEventInCache(EVENT_TYPE_RTT_RESULTS, "RTT results");
-}
-
-static void onHotlistAPFound(wifi_request_id id, unsigned num_results, wifi_scan_result *results) {
-
-    printMsg("Found hotlist APs\n");
-    for (unsigned i = 0; i < num_results; i++) {
-        printScanResult(results[i]);
-    }
-    putEventInCache(EVENT_TYPE_HOTLIST_AP_FOUND, "Found a hotlist AP");
-}
-
-static void onHotlistAPLost(wifi_request_id id, unsigned num_results, wifi_scan_result *results) {
-
-    printMsg("Lost hotlist APs\n");
-    for (unsigned i = 0; i < num_results; i++) {
-        printScanResult(results[i]);
-    }
-    putEventInCache(EVENT_TYPE_HOTLIST_AP_LOST, "Lost event Hotlist APs");
-}
-
-static void testRTT() {
-
-    wifi_scan_result results[256];
-    int num_results = scanOnce(WIFI_BAND_ABG, results, countof(results));
-    if (num_results == 0) {
-        printMsg("RTT aborted because of no scan results\n");
-        return;
-    } else {
-        printMsg("Retrieved %d scan results\n", num_results);
-    }
-
-    num_results = removeDuplicateScanResults(results, num_results);
-    /*
-    printMsg("Deduped scan results - %d\n", num_results);
-    for (int i = 0; i < num_results; i++) {
-        printScanResult(results[i]);
-    }
-    */
-
-    sortScanResultsByRssi(results, num_results);
-    printMsg("Sorted scan results -\n");
-    for (int i = 0; i < num_results; i++) {
-        printScanResult(results[i]);
-    }
-
-
-    static const int max_ap = 5;
-    wifi_rtt_config params[max_ap];
-    memset(params, 0, sizeof(params));
-
-    printMsg("Configuring RTT for %d APs, num_samples = %d\n",
-            min(num_results, max_ap), rtt_samples);
-
-    unsigned num_ap = 0;
-    for (int i = 0; i < min(num_results, max_ap); i++, num_ap++) {
-
-        memcpy(params[i].addr, results[i].bssid, sizeof(mac_addr));
-        mac_addr &addr = params[i].addr;
-        printMsg("Adding %02x:%02x:%02x:%02x:%02x:%02x (%d) for RTT\n", addr[0],
-                addr[1], addr[2], addr[3], addr[4], addr[5], results[i].channel);
-
-        params[i].type  = RTT_TYPE_1_SIDED;
-        params[i].channel.center_freq = results[i].channel;
-        params[i].channel.width = WIFI_CHAN_WIDTH_20;
-        params[i].peer  = WIFI_PEER_INVALID;
-        params[i].continuous = 1;
-        params[i].interval = 1000;
-        params[i].num_samples_per_measurement = rtt_samples;
-        params[i].num_retries_per_measurement = 10;
-    }
-
-    wifi_rtt_event_handler handler;
-    handler.on_rtt_results = &onRTTResults;
-
-    int result = wifi_rtt_range_request(rttCmdId, wlan0Handle, num_ap, params, handler);
-
-    if (result == WIFI_SUCCESS) {
-        printMsg("Waiting for RTT results\n");
-
-        while (true) {
-            EventInfo info;
-            memset(&info, 0, sizeof(info));
-            getEventFromCache(info);
-
-            if (info.type == EVENT_TYPE_SCAN_RESULTS_AVAILABLE) {
-                retrieveScanResults();
-            } else if (info.type == EVENT_TYPE_RTT_RESULTS) {
-                break;
-            }
-        }
-    } else {
-        printMsg("Could not set setRTTAPs : %d\n", result);
-    }
-}
-
-
-static wifi_error setHotlistAPsUsingScanResult(wifi_bssid_hotlist_params *params){
-    printMsg("testHotlistAPs Scan started, waiting for event ...\n");
-    EventInfo info;
-    memset(&info, 0, sizeof(info));
-    getEventFromCache(info);
-
-    wifi_scan_result results[256];
-    memset(results, 0, sizeof(wifi_scan_result) * 256);
-
-    printMsg("Retrieving scan results for Hotlist AP setting\n");
-    int num_results = 256;
-    int result = wifi_get_cached_gscan_results(wlan0Handle, 1, num_results, results, &num_results);
-    if (result < 0) {
-        printMsg("failed to fetch scan results : %d\n", result);
-        return WIFI_ERROR_UNKNOWN;
-    } else {
-        printMsg("fetched %d scan results\n", num_results);
-    }
-
-    for (int i = 0; i < num_results; i++) {
-        printScanResult(results[i]);
-    }
-
-    for (int i = 0; i < stest_max_ap; i++) {
-        memcpy(params->ap[i].bssid, results[i].bssid, sizeof(mac_addr));
-        params->ap[i].low  = -htest_low_threshold;
-        params->ap[i].high = -htest_high_threshold;
-    }
-    params->num_ap = stest_max_ap;
-    return WIFI_SUCCESS;
-}
-
-static wifi_error setHotlistAPs() {
-    wifi_bssid_hotlist_params params;
-    memset(&params, 0, sizeof(params));
-
-    params.lost_ap_sample_size = HOTLIST_LOST_WINDOW;
-    if (num_hotlist_bssids > 0) {
-      for (int i = 0; i < num_hotlist_bssids; i++) {
-          memcpy(params.ap[i].bssid, hotlist_bssids[i], sizeof(mac_addr));
-          params.ap[i].low  = -htest_low_threshold;
-          params.ap[i].high = -htest_high_threshold;
-      }
-      params.num_ap = num_hotlist_bssids;
-    } else {
-      setHotlistAPsUsingScanResult(&params);
-    }
-
-    printMsg("BSSID\t\t\tHIGH\tLOW\n");
-    for (int i = 0; i < params.num_ap; i++) {
-        mac_addr &addr = params.ap[i].bssid;
-        printMsg("%02x:%02x:%02x:%02x:%02x:%02x\t%d\t%d\n", addr[0],
-                addr[1], addr[2], addr[3], addr[4], addr[5],
-                params.ap[i].high, params.ap[i].low);
-    }
-
-    wifi_hotlist_ap_found_handler handler;
-    handler.on_hotlist_ap_found = &onHotlistAPFound;
-    handler.on_hotlist_ap_lost = &onHotlistAPLost;
-    hotlistCmdId = getNewCmdId();
-    printMsg("Setting hotlist APs threshold\n");
-    return wifi_set_bssid_hotlist(hotlistCmdId, wlan0Handle, params, handler);
-}
-
-static void resetHotlistAPs() {
-    printMsg(", stoping Hotlist AP scanning\n");
-    wifi_reset_bssid_hotlist(hotlistCmdId, wlan0Handle);
-}
-
-static void setPnoMacOui() {
-    wifi_set_scanning_mac_oui(wlan0Handle, mac_oui);
-}
-
-static void testHotlistAPs(){
-
-    EventInfo info;
-    memset(&info, 0, sizeof(info));
-
-    printMsg("starting Hotlist AP scanning\n");
-    if (!startScan(&onScanResultsAvailable, stest_max_ap,stest_base_period, stest_threshold)) {
-        printMsg("testHotlistAPs failed to start scan!!\n");
-        return;
-    }
-
-    int result = setHotlistAPs();
-    if (result == WIFI_SUCCESS) {
-        printMsg("Waiting for Hotlist AP event\n");
-        while (true) {
-            memset(&info, 0, sizeof(info));
-            getEventFromCache(info);
-
-            if (info.type == EVENT_TYPE_SCAN_RESULTS_AVAILABLE) {
-                retrieveScanResults();
-            } else if (info.type == EVENT_TYPE_HOTLIST_AP_FOUND ||
-                   info.type == EVENT_TYPE_HOTLIST_AP_LOST) {
-                printMsg("Hotlist APs");
-                if (--max_event_wait > 0)
-                  printMsg(", waiting for more event ::%d\n", max_event_wait);
-                else
-                  break;
-            }
-        }
-        resetHotlistAPs();
-    } else {
-        printMsg("Could not set AP hotlist : %d\n", result);
-    }
-}
-
-static void onSignificantWifiChange(wifi_request_id id,
-        unsigned num_results, wifi_significant_change_result **results)
-{
-    printMsg("Significant wifi change for %d\n", num_results);
-    for (unsigned i = 0; i < num_results; i++) {
-        printSignificantChangeResult(results[i]);
-    }
-    putEventInCache(EVENT_TYPE_SIGNIFICANT_WIFI_CHANGE, "significant wifi change noticed");
-}
-
-static int SelectSignificantAPsFromScanResults() {
-    wifi_scan_result results[256];
-    memset(results, 0, sizeof(wifi_scan_result) * 256);
-    printMsg("Retrieving scan results for significant wifi change setting\n");
-    int num_results = 256;
-    int result = wifi_get_cached_gscan_results(wlan0Handle, 1, num_results, results, &num_results);
-    if (result < 0) {
-        printMsg("failed to fetch scan results : %d\n", result);
-        return WIFI_ERROR_UNKNOWN;
-    } else {
-        printMsg("fetched %d scan results\n", num_results);
-    }
-
-    for (int i = 0; i < num_results; i++) {
-        printScanResult(results[i]);
-    }
-
-    wifi_significant_change_params params;
-    memset(&params, 0, sizeof(params));
-
-    params.rssi_sample_size = swctest_rssi_sample_size;
-    params.lost_ap_sample_size = swctest_rssi_lost_ap;
-    params.min_breaching = swctest_rssi_min_breaching;
-
-    for (int i = 0; i < stest_max_ap; i++) {
-        memcpy(params.ap[i].bssid, results[i].bssid, sizeof(mac_addr));
-        params.ap[i].low  = results[i].rssi - swctest_rssi_ch_threshold;
-        params.ap[i].high = results[i].rssi + swctest_rssi_ch_threshold;
-    }
-    params.num_ap = stest_max_ap;
-
-    printMsg("Settting Significant change params rssi_sample_size#%d lost_ap_sample_size#%d"
-        " and min_breaching#%d\n", params.rssi_sample_size,
-        params.lost_ap_sample_size , params.min_breaching);
-    printMsg("BSSID\t\t\tHIGH\tLOW\n");
-    for (int i = 0; i < params.num_ap; i++) {
-        mac_addr &addr = params.ap[i].bssid;
-        printMsg("%02x:%02x:%02x:%02x:%02x:%02x\t%d\t%d\n", addr[0],
-                addr[1], addr[2], addr[3], addr[4], addr[5],
-                params.ap[i].high, params.ap[i].low);
-    }
-    wifi_significant_change_handler handler;
-    memset(&handler, 0, sizeof(handler));
-    handler.on_significant_change = &onSignificantWifiChange;
-
-    int id = getNewCmdId();
-    return wifi_set_significant_change_handler(id, wlan0Handle, params, handler);
-
-}
-
-static void untrackSignificantChange() {
-    printMsg(", Stop tracking SignificantChange\n");
-    wifi_reset_bssid_hotlist(hotlistCmdId, wlan0Handle);
-}
-
-static void trackSignificantChange() {
-    printMsg("starting trackSignificantChange\n");
-
-    if (!startScan(&onScanResultsAvailable, stest_max_ap,stest_base_period, stest_threshold)) {
-        printMsg("trackSignificantChange failed to start scan!!\n");
-        return;
-    } else {
-        printMsg("trackSignificantChange Scan started, waiting for event ...\n");
-    }
-
-    EventInfo info;
-    memset(&info, 0, sizeof(info));
-    getEventFromCache(info);
-
-    int result = SelectSignificantAPsFromScanResults();
-    if (result == WIFI_SUCCESS) {
-        printMsg("Waiting for significant wifi change event\n");
-        while (true) {
-            memset(&info, 0, sizeof(info));
-            getEventFromCache(info);
-
-            if (info.type == EVENT_TYPE_SCAN_RESULTS_AVAILABLE) {
-                retrieveScanResults();
-            } else if(info.type == EVENT_TYPE_SIGNIFICANT_WIFI_CHANGE) {
-                printMsg("Received significant wifi change");
-                if (--max_event_wait > 0)
-                    printMsg(", waiting for more event ::%d\n", max_event_wait);
-                else
-                    break;
-            }
-        }
-        untrackSignificantChange();
-    } else {
-        printMsg("Failed to set significant change  ::%d\n", result);
-    }
-}
-
-/* -------------------------------------------  */
-/* tests                                        */
-/* -------------------------------------------  */
-
-void testScan() {
-    printf("starting scan with max_ap_per_scan#%d  base_period#%d  threshold#%d \n",
-           stest_max_ap,stest_base_period, stest_threshold);
-    if (!startScan(&onScanResultsAvailable, stest_max_ap,stest_base_period, stest_threshold)) {
-        printMsg("failed to start scan!!\n");
-        return;
-    } else {
-        EventInfo info;
-        memset(&info, 0, sizeof(info));
-
-        while (true) {
-            getEventFromCache(info);
-            printMsg("retrieved event %d : %s\n", info.type, info.buf);
-            retrieveScanResults();
-            if(--max_event_wait > 0)
-              printMsg("Waiting for more :: %d event \n", max_event_wait);
-            else
-              break;
-        }
-
-        stopScan();
-        printMsg("stopped scan\n");
-    }
-}
-
-void testStopScan() {
-    stopScan();
-    printMsg("stopped scan\n");
-}
-
-byte parseHexChar(char ch) {
-    if (isdigit(ch))
-        return ch - '0';
-    else if ('A' <= ch && ch <= 'F')
-        return ch - 'A' + 10;
-    else if ('a' <= ch && ch <= 'f')
-        return ch - 'a' + 10;
-    else {
-        printMsg("invalid character in bssid %c\n", ch);
-        return 0;
-    }
-}
-
-byte parseHexByte(char ch1, char ch2) {
-    return (parseHexChar(ch1) << 4) | parseHexChar(ch2);
-}
-
-void parseMacAddress(const char *str, mac_addr addr) {
-    addr[0] = parseHexByte(str[0], str[1]);
-    addr[1] = parseHexByte(str[3], str[4]);
-    addr[2] = parseHexByte(str[6], str[7]);
-    addr[3] = parseHexByte(str[9], str[10]);
-    addr[4] = parseHexByte(str[12], str[13]);
-    addr[5] = parseHexByte(str[15], str[16]);
-    // printMsg("read mac addr: %02x:%02x:%02x:%02x:%02x:%02x\n", addr[0],
-    //      addr[1], addr[2], addr[3], addr[4], addr[5]);
-}
-
-void parseMacOUI(char *str, unsigned char *addr) {
-    addr[0] = parseHexByte(str[0], str[1]);
-    addr[1] = parseHexByte(str[3], str[4]);
-    addr[2] = parseHexByte(str[6], str[7]);
-    printMsg("read mac OUI: %02x:%02x:%02x\n", addr[0],
-            addr[1], addr[2]);
-}
-
-void readTestOptions(int argc, char *argv[]){
-
-    printf("Total number of argc #%d\n", argc);
-    for (int j = 1; j < argc-1; j++) {
-        if (strcmp(argv[j], "-max_ap") == 0 && isdigit(argv[j+1][0])) {
-            stest_max_ap = atoi(argv[++j]);
-            printf(" max_ap #%d\n", stest_max_ap);
-        } else if (strcmp(argv[j], "-base_period") == 0 && isdigit(argv[j+1][0])) {
-            stest_base_period = atoi(argv[++j]);
-            printf(" base_period #%d\n", stest_base_period);
-        } else if (strcmp(argv[j], "-threshold") == 0 && isdigit(argv[j+1][0])) {
-            stest_threshold = atoi(argv[++j]);
-            printf(" threshold #%d\n", stest_threshold);
-        } else if (strcmp(argv[j], "-avg_RSSI") == 0 && isdigit(argv[j+1][0])) {
-            swctest_rssi_sample_size = atoi(argv[++j]);
-            printf(" avg_RSSI #%d\n", swctest_rssi_sample_size);
-        } else if (strcmp(argv[j], "-ap_loss") == 0 && isdigit(argv[j+1][0])) {
-            swctest_rssi_lost_ap = atoi(argv[++j]);
-            printf(" ap_loss #%d\n", swctest_rssi_lost_ap);
-        } else if (strcmp(argv[j], "-ap_breach") == 0 && isdigit(argv[j+1][0])) {
-            swctest_rssi_min_breaching = atoi(argv[++j]);
-            printf(" ap_breach #%d\n", swctest_rssi_min_breaching);
-        } else if (strcmp(argv[j], "-ch_threshold") == 0 && isdigit(argv[j+1][0])) {
-            swctest_rssi_ch_threshold = atoi(argv[++j]);
-            printf(" ch_threshold #%d\n", swctest_rssi_ch_threshold);
-        } else if (strcmp(argv[j], "-wt_event") == 0 && isdigit(argv[j+1][0])) {
-            max_event_wait = atoi(argv[++j]);
-            printf(" wt_event #%d\n", max_event_wait);
-        } else if (strcmp(argv[j], "-low_th") == 0 && isdigit(argv[j+1][0])) {
-            htest_low_threshold = atoi(argv[++j]);
-            printf(" low_threshold #-%d\n", htest_low_threshold);
-        } else if (strcmp(argv[j], "-high_th") == 0 && isdigit(argv[j+1][0])) {
-            htest_high_threshold = atoi(argv[++j]);
-            printf(" high_threshold #-%d\n", htest_high_threshold);
-        } else if (strcmp(argv[j], "-hotlist_bssids") == 0 && isxdigit(argv[j+1][0])) {
-            j++;
-            for (num_hotlist_bssids = 0;
-                        j < argc && isxdigit(argv[j][0]);
-                        j++, num_hotlist_bssids++) {
-                parseMacAddress(argv[j], hotlist_bssids[num_hotlist_bssids]);
-            }
-            j -= 1;
-        } else if (strcmp(argv[j], "-channel_list") == 0 && isxdigit(argv[j+1][0])) {
-            j++;
-            for (num_channels = 0; j < argc && isxdigit(argv[j][0]); j++, num_channels++) {
-                channel_list[num_channels] = atoi(argv[j]);
-            }
-            j -= 1;
-        } else if ((strcmp(argv[j], "-get_ch_list") == 0)) {
-            if(strcmp(argv[j + 1], "a") == 0) {
-                band = WIFI_BAND_A_WITH_DFS;
-            } else if(strcmp(argv[j + 1], "bg") == 0) {
-                band = WIFI_BAND_BG;
-            } else if(strcmp(argv[j + 1], "abg") == 0) {
-                band = WIFI_BAND_ABG_WITH_DFS;
-            } else if(strcmp(argv[j + 1], "a_nodfs") == 0) {
-                band = WIFI_BAND_A;
-            } else if(strcmp(argv[j + 1], "dfs") == 0) {
-                band = WIFI_BAND_A_DFS;
-            } else if(strcmp(argv[j + 1], "abg_nodfs") == 0) {
-                band = WIFI_BAND_ABG;
-            }
-            j++;
-        } else if ((strcmp(argv[j], "-rtt_samples") == 0)) {
-            rtt_samples = atoi(argv[++j]);
-            printf(" rtt_retries #-%d\n", rtt_samples);
-        } else if (strcmp(argv[j], "-scan_mac_oui") == 0 && isxdigit(argv[j+1][0])) {
-            parseMacOUI(argv[++j], mac_oui);
-     }
-    }
-}
-
-wifi_iface_stat link_stat;
-wifi_radio_stat trx_stat;
-wifi_peer_info peer_info;
-wifi_rate_stat rate_stat[32];
-void onLinkStatsResults(wifi_request_id id, wifi_iface_stat *iface_stat,
-         int num_radios, wifi_radio_stat *radio_stat)
-{
-    int num_peer = iface_stat->num_peers;
-    memcpy(&trx_stat, radio_stat, sizeof(wifi_radio_stat));
-    memcpy(&link_stat, iface_stat, sizeof(wifi_iface_stat));
-    memcpy(&peer_info, iface_stat->peer_info, num_peer*sizeof(wifi_peer_info));
-    int num_rate = peer_info.num_rate;
-    memcpy(&rate_stat, iface_stat->peer_info->rate_stats, num_rate*sizeof(wifi_rate_stat));
-}
-
-void printFeatureListBitMask(void)
-{
-    printMsg("WIFI_FEATURE_INFRA              0x0001      - Basic infrastructure mode\n");
-    printMsg("WIFI_FEATURE_INFRA_5G           0x0002      - Support for 5 GHz Band\n");
-    printMsg("WIFI_FEATURE_HOTSPOT            0x0004      - Support for GAS/ANQP\n");
-    printMsg("WIFI_FEATURE_P2P                0x0008      - Wifi-Direct\n");
-    printMsg("WIFI_FEATURE_SOFT_AP            0x0010      - Soft AP\n");
-    printMsg("WIFI_FEATURE_GSCAN              0x0020      - Google-Scan APIs\n");
-    printMsg("WIFI_FEATURE_NAN                0x0040      - Neighbor Awareness Networking\n");
-    printMsg("WIFI_FEATURE_D2D_RTT            0x0080      - Device-to-device RTT\n");
-    printMsg("WIFI_FEATURE_D2AP_RTT           0x0100      - Device-to-AP RTT\n");
-    printMsg("WIFI_FEATURE_BATCH_SCAN         0x0200      - Batched Scan (legacy)\n");
-    printMsg("WIFI_FEATURE_PNO                0x0400      - Preferred network offload\n");
-    printMsg("WIFI_FEATURE_ADDITIONAL_STA     0x0800      - Support for two STAs\n");
-    printMsg("WIFI_FEATURE_TDLS               0x1000      - Tunnel directed link setup\n");
-    printMsg("WIFI_FEATURE_TDLS_OFFCHANNEL    0x2000      - Support for TDLS off channel\n");
-    printMsg("WIFI_FEATURE_EPR                0x4000      - Enhanced power reporting\n");
-    printMsg("WIFI_FEATURE_AP_STA             0x8000      - Support for AP STA Concurrency\n");
-}
-
-char *rates[] = {
-    "1Mbps",
-    "2Mbps",
-	"5.5Mbps",
-	"6Mbps",
-	"9Mbps",
-	"11Mbps",
-	"12Mbps",
-	"18Mbps",
-	"24Mbps",
-	"36Mbps",
-	"48Mbps",
-	"54Mbps",
-	"VHT MCS0 ss1",
-	"VHT MCS1 ss1",
-	"VHT MCS2 ss1",
-	"VHT MCS3 ss1",
-	"VHT MCS4 ss1",
-	"VHT MCS5 ss1",
-	"VHT MCS6 ss1",
-	"VHT MCS7 ss1",
-    "VHT MCS8 ss1",
-	"VHT MCS9 ss1",
-	"VHT MCS0 ss2",
-	"VHT MCS1 ss2",
-	"VHT MCS2 ss2",
-	"VHT MCS3 ss2",
-	"VHT MCS4 ss2",
-	"VHT MCS5 ss2",
-	"VHT MCS6 ss2",
-	"VHT MCS7 ss2",
-	"VHT MCS8 ss2",
-	"VHT MCS9 ss2"
-	};
-
-void printLinkStats(wifi_iface_stat link_stat, wifi_radio_stat trx_stat)
-{
-    printMsg("Printing link layer statistics:\n");
-    printMsg("-------------------------------\n");
-    printMsg("beacon_rx = %d\n", link_stat.beacon_rx);
-    printMsg("RSSI = %d\n", link_stat.rssi_mgmt);
-    printMsg("AC_BE:\n");
-    printMsg("txmpdu = %d\n", link_stat.ac[WIFI_AC_BE].tx_mpdu);
-    printMsg("rxmpdu = %d\n", link_stat.ac[WIFI_AC_BE].rx_mpdu);
-    printMsg("mpdu_lost = %d\n", link_stat.ac[WIFI_AC_BE].mpdu_lost);
-    printMsg("retries = %d\n", link_stat.ac[WIFI_AC_BE].retries);
-    printMsg("AC_BK:\n");
-    printMsg("txmpdu = %d\n", link_stat.ac[WIFI_AC_BK].tx_mpdu);
-    printMsg("rxmpdu = %d\n", link_stat.ac[WIFI_AC_BK].rx_mpdu);
-    printMsg("mpdu_lost = %d\n", link_stat.ac[WIFI_AC_BK].mpdu_lost);
-    printMsg("AC_VI:\n");
-    printMsg("txmpdu = %d\n", link_stat.ac[WIFI_AC_VI].tx_mpdu);
-    printMsg("rxmpdu = %d\n", link_stat.ac[WIFI_AC_VI].rx_mpdu);
-    printMsg("mpdu_lost = %d\n", link_stat.ac[WIFI_AC_VI].mpdu_lost);
-    printMsg("AC_VO:\n");
-    printMsg("txmpdu = %d\n", link_stat.ac[WIFI_AC_VO].tx_mpdu);
-    printMsg("rxmpdu = %d\n", link_stat.ac[WIFI_AC_VO].rx_mpdu);
-    printMsg("mpdu_lost = %d\n", link_stat.ac[WIFI_AC_VO].mpdu_lost);
-    printMsg("\n");
-    printMsg("Printing radio statistics:\n");
-    printMsg("--------------------------\n");
-    printMsg("on time = %d\n", trx_stat.on_time);
-    printMsg("tx time = %d\n", trx_stat.tx_time);
-    printMsg("rx time = %d\n", trx_stat.rx_time);
-    printMsg("\n");
-    printMsg("Printing rate statistics:\n");
-    printMsg("-------------------------\n");
-    printMsg("%27s %12s %14s %15s\n", "TX",  "RX", "LOST", "RETRIES");
-    for (int i=0; i < 32; i++) {
-        printMsg("%-15s  %10d   %10d    %10d    %10d\n",
-	    rates[i], rate_stat[i].tx_mpdu, rate_stat[i].rx_mpdu,
-	    rate_stat[i].mpdu_lost, rate_stat[i].retries);
-    }
-}
-
-void getLinkStats(void)
-{
-    wifi_stats_result_handler handler;
-    memset(&handler, 0, sizeof(handler));
-    handler.on_link_stats_results = &onLinkStatsResults;
-
-    int result = wifi_get_link_stats(0, wlan0Handle, handler);
-    if (result < 0) {
-        printMsg("failed to get link statistics - %d\n", result);
-    } else {
-        printLinkStats(link_stat, trx_stat);
-    }
-}
-
-void getChannelList(void)
-{
-    wifi_channel channel[MAX_CH_BUF_SIZE];
-    int num_channels = 0, i;
-
-    int result = wifi_get_valid_channels(wlan0Handle, band, MAX_CH_BUF_SIZE,
-                     channel, &num_channels);
-    printMsg("Number of channels - %d\nChannel List:\n",num_channels);
-    for (i = 0; i < num_channels; i++) {
-        printMsg("%d MHz\n", channel[i]);
-    }
-}
-
-void getFeatureSet(void)
-{
-    feature_set set;
-    int result = wifi_get_supported_feature_set(wlan0Handle, &set);
-
-    if (result < 0) {
-        printMsg("Error %d\n",result);
-        return;
-    }
-    printFeatureListBitMask();
-    printMsg("Supported feature set bit mask - %x\n", set);
-    return;
-}
-
-void getFeatureSetMatrix(void)
-{
-    feature_set set[MAX_FEATURE_SET];
-    int size;
-
-    int result = wifi_get_concurrency_matrix(wlan0Handle, MAX_FEATURE_SET, set, &size);
-
-    if (result < 0) {
-        printMsg("Error %d\n",result);
-        return;
-    }
-    printFeatureListBitMask();
-    for (int i = 0; i < size; i++)
-        printMsg("Concurrent feature set - %x\n", set[i]);
-    return;
-}
-
-
-
-int main(int argc, char *argv[]) {
-
-    pthread_mutex_init(&printMutex, NULL);
-
-    if (init() != 0) {
-        printMsg("could not initiate HAL");
-        return -1;
-    } else {
-        printMsg("successfully initialized HAL; wlan0 = %p\n", wlan0Handle);
-    }
-
-    pthread_cond_init(&eventCacheCondition, NULL);
-    pthread_mutex_init(&eventCacheMutex, NULL);
-
-    pthread_t tidEvent;
-    pthread_create(&tidEvent, NULL, &eventThreadFunc, NULL);
-
-    sleep(2);     // let the thread start
-
-    if (argc < 2 || argv[1][0] != '-') {
-        printf("Usage:  halutil [OPTION]\n");
-        printf(" -s               start AP scan test\n");
-        printf(" -swc             start Significant Wifi change test\n");
-        printf(" -h               start Hotlist APs scan test\n");
-        printf(" -ss              stop scan test\n");
-        printf(" -max_ap          Max AP for scan \n");
-        printf(" -base_period     Base period for scan \n");
-        printf(" -threshold       Threshold scan test\n");
-        printf(" -avg_RSSI        samples for averaging RSSI\n");
-        printf(" -ap_loss         samples to confirm AP loss\n");
-        printf(" -ap_breach       APs breaching threshold\n");
-        printf(" -ch_threshold    Change in threshold\n");
-        printf(" -wt_event        Waiting event for test\n");
-        printf(" -low_th          Low threshold for hotlist APs\n");
-        printf(" -hight_th        High threshold for hotlist APs\n");
-        printf(" -hotlist_bssids  BSSIDs for hotlist test\n");
-        printf(" -stats       print link layer statistics\n");
-        printf(" -get_ch_list <a/bg/abg/a_nodfs/abg_nodfs/dfs>  Get channel list\n");
-        printf(" -get_feature_set  Get Feature set\n");
-        printf(" -get_feature_matrix  Get concurrent feature matrix\n");
-        printf(" -rtt             Run RTT on nearby APs\n");
-        printf(" -rtt_samples     Run RTT on nearby APs\n");
-        printf(" -scan_mac_oui XY:AB:CD\n");
-        printf(" -nodfs <0|1>     Turn OFF/ON non-DFS locales\n");
-        goto cleanup;
-    }
-    memset(mac_oui, 0, 3);
-
-    if (strcmp(argv[1], "-s") == 0) {
-        readTestOptions(argc, argv);
-        setPnoMacOui();
-        testScan();
-    }else if(strcmp(argv[1], "-swc") == 0){
-        readTestOptions(argc, argv);
-        setPnoMacOui();
-        trackSignificantChange();
-    }else if (strcmp(argv[1], "-ss") == 0) {
-        // Stop scan so clear the OUI too
-        setPnoMacOui();
-        testStopScan();
-    }else if ((strcmp(argv[1], "-h") == 0)  ||
-              (strcmp(argv[1], "-hotlist_bssids") == 0)) {
-        readTestOptions(argc, argv);
-        setPnoMacOui();
-        testHotlistAPs();
-    }else if (strcmp(argv[1], "-stats") == 0) {
-        getLinkStats();
-    } else if ((strcmp(argv[1], "-rtt") == 0)) {
-        readTestOptions(argc, argv);
-        testRTT();
-    } else if ((strcmp(argv[1], "-get_ch_list") == 0)) {
-        readTestOptions(argc, argv);
-        getChannelList();
-    } else if ((strcmp(argv[1], "-get_feature_set") == 0)) {
-        getFeatureSet();
-    } else if ((strcmp(argv[1], "-get_feature_matrix") == 0)) {
-        getFeatureSetMatrix();
-    } else if ((strcmp(argv[1], "-scan_mac_oui") == 0)) {
-        readTestOptions(argc, argv);
-        setPnoMacOui();
-        testScan();
-    } else if (strcmp(argv[1], "-nodfs") == 0) {
-        u32 nodfs = 0;
-        if (argc > 2)
-            nodfs = (u32)atoi(argv[2]);
-        wifi_set_nodfs_flag(wlan0Handle, nodfs);
-    }
-cleanup:
-    cleanup();
-    return 0;
-}