Merge changes from topic "am-35cf956b-e4b9-4d71-8e67-b3f3e90a9e22" into oc-dev am: b012d872de
am: 23d89d1f33

Change-Id: Ib48b137e5881c75e16a314f04b5ba1a547d763f2
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..1ad5831
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,11 @@
+etancohen@google.com
+kuh@google.com
+mett@google.com
+mplass@google.com
+nywang@google.com
+quiche@google.com
+rpius@google.com
+satk@google.com
+silberst@google.com
+sohanirao@google.com
+zqiu@google.com
diff --git a/libwifi_system/Android.bp b/libwifi_system/Android.bp
index 877d05f..f5ea184 100644
--- a/libwifi_system/Android.bp
+++ b/libwifi_system/Android.bp
@@ -29,7 +29,7 @@
 
 // Device independent wifi system logic.
 // ============================================================
-cc_library_shared {
+cc_library {
     name: "libwifi-system",
     defaults: ["libwifi-system-defaults"],
     export_include_dirs: ["include"],
diff --git a/libwifi_system/hostapd_manager.cpp b/libwifi_system/hostapd_manager.cpp
index 68184e9..658eecd 100644
--- a/libwifi_system/hostapd_manager.cpp
+++ b/libwifi_system/hostapd_manager.cpp
@@ -34,6 +34,7 @@
 
 using android::base::ParseInt;
 using android::base::ReadFileToString;
+using android::base::RemoveFileIfExists;
 using android::base::StringPrintf;
 using android::base::WriteStringToFile;
 using std::string;
@@ -103,6 +104,9 @@
 }
 
 bool HostapdManager::WriteHostapdConfig(const string& config) {
+  // Remove hostapd.conf because its file owner might be system
+  // in previous OS and chmod fails in that case.
+  RemoveFileIfExists(kHostapdConfigFilePath);
   if (!WriteStringToFile(config, kHostapdConfigFilePath,
                          S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP,
                          AID_WIFI, AID_WIFI)) {
diff --git a/libwifi_system/tests/hostapd_manager_unittest.cpp b/libwifi_system/tests/hostapd_manager_unittest.cpp
index 048d473..b25dd8e 100644
--- a/libwifi_system/tests/hostapd_manager_unittest.cpp
+++ b/libwifi_system/tests/hostapd_manager_unittest.cpp
@@ -24,6 +24,8 @@
 
 using std::string;
 using std::vector;
+using testing::HasSubstr;
+using testing::Not;
 
 namespace android {
 namespace wifi_system {
@@ -34,46 +36,10 @@
 const char kTestPassphraseStr[] = "yourelookingfor";
 const int kTestChannel = 2;
 
-#define CONFIG_COMMON_PREFIX \
-    "interface=foobar0\n" \
-    "driver=nl80211\n" \
-    "ctrl_interface=/data/misc/wifi/hostapd/ctrl\n" \
-    "ssid2=68656c6c6f" "6973" "6974" "6d65\n" \
-    "channel=2\n" \
-    "ieee80211n=1\n" \
-    "hw_mode=g\n"
-
 // If you generate your config file with both the test ssid
 // and the test passphrase, you'll get this line in the config.
-#define CONFIG_PSK_LINE \
-    "wpa_psk=dffa36815281e5a6eca1910f254717fa2528681335e3bbec5966d2aa9221a66e\n"
-
-#define CONFIG_WPA_SUFFIX \
-    "wpa=3\n" \
-    "wpa_pairwise=TKIP CCMP\n" \
-    CONFIG_PSK_LINE
-
-#define CONFIG_WPA2_SUFFIX \
-    "wpa=2\n" \
-    "rsn_pairwise=CCMP\n" \
-    CONFIG_PSK_LINE
-
-const char kExpectedOpenConfig[] =
-  CONFIG_COMMON_PREFIX
-  "ignore_broadcast_ssid=0\n"
-  "wowlan_triggers=any\n";
-
-const char kExpectedWpaConfig[] =
-    CONFIG_COMMON_PREFIX
-    "ignore_broadcast_ssid=0\n"
-    "wowlan_triggers=any\n"
-    CONFIG_WPA_SUFFIX;
-
-const char kExpectedWpa2Config[] =
-    CONFIG_COMMON_PREFIX
-    "ignore_broadcast_ssid=0\n"
-    "wowlan_triggers=any\n"
-    CONFIG_WPA2_SUFFIX;
+const char kConfigPskLine[] =
+    "wpa_psk=dffa36815281e5a6eca1910f254717fa2528681335e3bbec5966d2aa9221a66e\n";
 
 class HostapdManagerTest : public ::testing::Test {
  protected:
@@ -93,27 +59,44 @@
   }
 };  // class HostapdManagerTest
 
+// This is used to verify config string generated by test helper function
+// |GetConfigForEncryptionType|.
+void VerifyCommonConfigs(const string& config) {
+  EXPECT_THAT(config, HasSubstr("interface=foobar0\n"));
+  EXPECT_THAT(config, HasSubstr("driver=nl80211\n"));
+  EXPECT_THAT(config, HasSubstr("ctrl_interface=/data/misc/wifi/hostapd/ctrl\n"));
+  EXPECT_THAT(config, HasSubstr("ssid2=68656c6c6f" "6973" "6974" "6d65\n"));
+  EXPECT_THAT(config, HasSubstr("channel=2\n"));
+  EXPECT_THAT(config, HasSubstr("hw_mode=g\n"));
+  EXPECT_THAT(config, HasSubstr("wowlan_triggers=any\n"));
+  EXPECT_THAT(config, HasSubstr("ignore_broadcast_ssid=0\n"));
+  EXPECT_THAT(config, Not(HasSubstr("ignore_broadcast_ssid=1\n")));
+}
+
 }  // namespace
 
 TEST_F(HostapdManagerTest, GeneratesCorrectOpenConfig) {
   string config = GetConfigForEncryptionType(
       HostapdManager::EncryptionType::kOpen);
-  EXPECT_FALSE(config.empty());
-  EXPECT_EQ(kExpectedOpenConfig, config);
+  VerifyCommonConfigs(config);
 }
 
 TEST_F(HostapdManagerTest, GeneratesCorrectWpaConfig) {
   string config = GetConfigForEncryptionType(
       HostapdManager::EncryptionType::kWpa);
-  EXPECT_FALSE(config.empty());
-  EXPECT_EQ(kExpectedWpaConfig, config);
+  VerifyCommonConfigs(config);
+  EXPECT_THAT(config, HasSubstr("wpa=3\n"));
+  EXPECT_THAT(config, HasSubstr("wpa_pairwise=TKIP CCMP\n"));
+  EXPECT_THAT(config, HasSubstr(kConfigPskLine));
 }
 
 TEST_F(HostapdManagerTest, GeneratesCorrectWpa2Config) {
   string config = GetConfigForEncryptionType(
       HostapdManager::EncryptionType::kWpa2);
-  EXPECT_FALSE(config.empty());
-  EXPECT_EQ(kExpectedWpa2Config, config);
+  VerifyCommonConfigs(config);
+  EXPECT_THAT(config, HasSubstr("wpa=2\n"));
+  EXPECT_THAT(config, HasSubstr("rsn_pairwise=CCMP\n"));
+  EXPECT_THAT(config, HasSubstr(kConfigPskLine));
 }
 
 TEST_F(HostapdManagerTest, RespectsHiddenSetting) {
@@ -124,8 +107,8 @@
         kTestChannel,
         HostapdManager::EncryptionType::kOpen,
         vector<uint8_t>());
-  EXPECT_FALSE(config.find("ignore_broadcast_ssid=1\n") == string::npos);
-  EXPECT_TRUE(config.find("ignore_broadcast_ssid=0\n") == string::npos);
+  EXPECT_THAT(config, HasSubstr("ignore_broadcast_ssid=1\n"));
+  EXPECT_THAT(config, Not(HasSubstr("ignore_broadcast_ssid=0\n")));
 }
 
 TEST_F(HostapdManagerTest, CorrectlyInfersHwMode) {
@@ -136,8 +119,8 @@
         44,
         HostapdManager::EncryptionType::kOpen,
         vector<uint8_t>());
-  EXPECT_FALSE(config.find("hw_mode=a\n") == string::npos);
-  EXPECT_TRUE(config.find("hw_mode=g\n") == string::npos);
+  EXPECT_THAT(config, HasSubstr("hw_mode=a\n"));
+  EXPECT_THAT(config, Not(HasSubstr("hw_mode=g\n")));
 }
 
 
diff --git a/libwifi_system_iface/Android.bp b/libwifi_system_iface/Android.bp
index 4be0aa0..80249ef 100644
--- a/libwifi_system_iface/Android.bp
+++ b/libwifi_system_iface/Android.bp
@@ -26,9 +26,12 @@
 
 // Device independent wifi system logic.
 // ============================================================
-cc_library_shared {
+cc_library {
     name: "libwifi-system-iface",
     vendor_available: true,
+    vndk: {
+        enabled: true,
+    },
     cflags: wifi_system_iface_cflags,
     local_include_dirs: ["include"],
     export_include_dirs: ["include"],
diff --git a/service/Android.mk b/service/Android.mk
index 27ecf21..e1bac28 100644
--- a/service/Android.mk
+++ b/service/Android.mk
@@ -70,6 +70,10 @@
 LOCAL_MODULE := wifi-service
 LOCAL_INIT_RC := wifi-events.rc
 
+LOCAL_DEX_PREOPT_APP_IMAGE := false
+LOCAL_DEX_PREOPT_GENERATE_PROFILE := true
+LOCAL_DEX_PREOPT_PROFILE_CLASS_LISTING := frameworks/base/services/art-profile
+
 ifeq ($(EMMA_INSTRUMENT_FRAMEWORK),true)
 LOCAL_EMMA_INSTRUMENT := true
 endif
diff --git a/service/java/com/android/server/wifi/CarrierNetworkConfig.java b/service/java/com/android/server/wifi/CarrierNetworkConfig.java
new file mode 100644
index 0000000..c23213e
--- /dev/null
+++ b/service/java/com/android/server/wifi/CarrierNetworkConfig.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2017 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 android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.wifi.EAPConstants;
+import android.net.wifi.WifiEnterpriseConfig;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.util.Base64;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Class for maintaining/caching carrier Wi-Fi network configurations.
+ */
+public class CarrierNetworkConfig {
+    private static final String TAG = "CarrierNetworkConfig";
+
+    private static final String NETWORK_CONFIG_SEPARATOR = ",";
+    private static final int ENCODED_SSID_INDEX = 0;
+    private static final int EAP_TYPE_INDEX = 1;
+    private static final int CONFIG_ELEMENT_SIZE = 2;
+
+    private final Map<String, NetworkInfo> mCarrierNetworkMap;
+
+    public CarrierNetworkConfig(Context context) {
+        mCarrierNetworkMap = new HashMap<>();
+        updateNetworkConfig(context);
+
+        // Monitor for carrier config changes.
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        context.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                updateNetworkConfig(context);
+            }
+        }, filter);
+    }
+
+    /**
+     * @return true if the given SSID is associated with a carrier network
+     */
+    public boolean isCarrierNetwork(String ssid) {
+        return mCarrierNetworkMap.containsKey(ssid);
+    }
+
+    /**
+     * @return the EAP type associated with a carrier AP, or -1 if the specified AP
+     * is not associated with a carrier network
+     */
+    public int getNetworkEapType(String ssid) {
+        NetworkInfo info = mCarrierNetworkMap.get(ssid);
+        return info == null ? -1 : info.mEapType;
+    }
+
+    /**
+     * @return the name of carrier associated with a carrier AP, or null if the specified AP
+     * is not associated with a carrier network.
+     */
+    public String getCarrierName(String ssid) {
+        NetworkInfo info = mCarrierNetworkMap.get(ssid);
+        return info == null ? null : info.mCarrierName;
+    }
+
+    /**
+     * Utility class for storing carrier network information.
+     */
+    private static class NetworkInfo {
+        final int mEapType;
+        final String mCarrierName;
+
+        NetworkInfo(int eapType, String carrierName) {
+            mEapType = eapType;
+            mCarrierName = carrierName;
+        }
+    }
+
+    /**
+     * Update the carrier network map based on the current carrier configuration of the active
+     * subscriptions.
+     *
+     * @param context Current application context
+     */
+    private void updateNetworkConfig(Context context) {
+        // Reset network map.
+        mCarrierNetworkMap.clear();
+
+        CarrierConfigManager carrierConfigManager =
+                (CarrierConfigManager) context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        if (carrierConfigManager == null) {
+            return;
+        }
+
+        SubscriptionManager subscriptionManager = (SubscriptionManager) context.getSystemService(
+                Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+        if (subscriptionManager == null) {
+            return;
+        }
+        List<SubscriptionInfo> subInfoList = subscriptionManager.getActiveSubscriptionInfoList();
+        if (subInfoList == null) {
+            return;
+        }
+
+        // Process the carrier config for each active subscription.
+        for (SubscriptionInfo subInfo : subInfoList) {
+            processNetworkConfig(
+                    carrierConfigManager.getConfigForSubId(subInfo.getSubscriptionId()),
+                    subInfo.getDisplayName().toString());
+        }
+    }
+
+    /**
+     * Process the carrier network config, the network config string is formatted as follow:
+     *
+     * "[Base64 Encoded SSID],[EAP Type]"
+     * Where EAP Type is the standard EAP method number, refer to
+     * http://www.iana.org/assignments/eap-numbers/eap-numbers.xhtml for more info.
+
+     * @param carrierConfig The bundle containing the carrier configuration
+     * @param carrierName The display name of the associated carrier
+     */
+    private void processNetworkConfig(PersistableBundle carrierConfig, String carrierName) {
+        if (carrierConfig == null) {
+            return;
+        }
+        String[] networkConfigs = carrierConfig.getStringArray(
+                CarrierConfigManager.KEY_CARRIER_WIFI_STRING_ARRAY);
+        if (networkConfigs == null) {
+            return;
+        }
+
+        for (String networkConfig : networkConfigs) {
+            String[] configArr = networkConfig.split(NETWORK_CONFIG_SEPARATOR);
+            if (configArr.length != CONFIG_ELEMENT_SIZE) {
+                Log.e(TAG, "Ignore invalid config: " + networkConfig);
+                continue;
+            }
+            try {
+                String ssid = new String(Base64.decode(
+                        configArr[ENCODED_SSID_INDEX], Base64.DEFAULT));
+                int eapType = parseEapType(Integer.parseInt(configArr[EAP_TYPE_INDEX]));
+                // Verify EAP type, must be a SIM based EAP type.
+                if (eapType == -1) {
+                    Log.e(TAG, "Invalid EAP type: " + configArr[EAP_TYPE_INDEX]);
+                    continue;
+                }
+                mCarrierNetworkMap.put(ssid, new NetworkInfo(eapType, carrierName));
+            } catch (NumberFormatException e) {
+                Log.e(TAG, "Failed to parse EAP type: " + e.getMessage());
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Failed to decode SSID: " + e.getMessage());
+            }
+        }
+    }
+
+    /**
+     * Convert a standard SIM-based EAP type (SIM, AKA, AKA') to the internal EAP type as defined in
+     * {@link WifiEnterpriseConfig.Eap}. -1 will be returned if the given EAP type is not
+     * SIM-based.
+     *
+     * @return SIM-based EAP type as defined in {@link WifiEnterpriseConfig.Eap}, or -1 if not
+     * SIM-based EAP type
+     */
+    private static int parseEapType(int eapType) {
+        if (eapType == EAPConstants.EAP_SIM) {
+            return WifiEnterpriseConfig.Eap.SIM;
+        } else if (eapType == EAPConstants.EAP_AKA) {
+            return WifiEnterpriseConfig.Eap.AKA;
+        } else if (eapType == EAPConstants.EAP_AKA_PRIME) {
+            return WifiEnterpriseConfig.Eap.AKA_PRIME;
+        }
+        return -1;
+    }
+}
diff --git a/service/java/com/android/server/wifi/ConnectToNetworkNotificationBuilder.java b/service/java/com/android/server/wifi/ConnectToNetworkNotificationBuilder.java
new file mode 100644
index 0000000..38c0ad0
--- /dev/null
+++ b/service/java/com/android/server/wifi/ConnectToNetworkNotificationBuilder.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2017 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 android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.net.wifi.ScanResult;
+
+import com.android.internal.R;
+import com.android.internal.notification.SystemNotificationChannels;
+
+/**
+ * Helper to create notifications for {@link OpenNetworkNotifier}.
+ */
+public class ConnectToNetworkNotificationBuilder {
+
+    /** Intent when user dismissed the "Connect to Network" notification. */
+    public static final String ACTION_USER_DISMISSED_NOTIFICATION =
+            "com.android.server.wifi.ConnectToNetworkNotification.USER_DISMISSED_NOTIFICATION";
+
+    /** Intent when user tapped action button to connect to recommended network. */
+    public static final String ACTION_CONNECT_TO_NETWORK =
+            "com.android.server.wifi.ConnectToNetworkNotification.CONNECT_TO_NETWORK";
+
+    /** Intent when user tapped action button to open Wi-Fi Settings. */
+    public static final String ACTION_PICK_WIFI_NETWORK =
+            "com.android.server.wifi.ConnectToNetworkNotification.PICK_WIFI_NETWORK";
+
+    /** Intent when user tapped "Failed to connect" notification to open Wi-Fi Settings. */
+    public static final String ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE =
+            "com.android.server.wifi.ConnectToNetworkNotification.PICK_NETWORK_AFTER_FAILURE";
+
+    private Context mContext;
+    private Resources mResources;
+    private FrameworkFacade mFrameworkFacade;
+
+    public ConnectToNetworkNotificationBuilder(
+            Context context,
+            FrameworkFacade framework) {
+        mContext = context;
+        mResources = context.getResources();
+        mFrameworkFacade = framework;
+    }
+
+    /**
+     * Creates the connect to network notification that alerts users of a recommended connectable
+     * network.
+     *
+     * There are two actions - "Options" link to the Wi-Fi picker activity, and "Connect" prompts
+     * the connection to the recommended network.
+     *
+     * @param network The network to be recommended
+     */
+    public Notification createConnectToNetworkNotification(ScanResult network) {
+        Notification.Action connectAction = new Notification.Action.Builder(
+                null /* icon */,
+                mResources.getText(R.string.wifi_available_action_connect),
+                getPrivateBroadcast(ACTION_CONNECT_TO_NETWORK)).build();
+        Notification.Action allNetworksAction = new Notification.Action.Builder(
+                null /* icon */,
+                mResources.getText(R.string.wifi_available_action_all_networks),
+                getPrivateBroadcast(ACTION_PICK_WIFI_NETWORK)).build();
+        return createNotificationBuilder(
+                mContext.getText(R.string.wifi_available_title), network.SSID)
+                .addAction(connectAction)
+                .addAction(allNetworksAction)
+                .build();
+    }
+
+    /**
+     * Creates the notification that indicates the controller is attempting to connect to the
+     * recommended network.
+     *
+     * @param network The network to be recommended
+     */
+    public Notification createNetworkConnectingNotification(ScanResult network) {
+        return createNotificationBuilder(
+                mContext.getText(R.string.wifi_available_title_connecting), network.SSID)
+                .setProgress(0 /* max */, 0 /* progress */, true /* indeterminate */)
+                .build();
+    }
+
+    /**
+     * Creates the notification that indicates the controller successfully connected to the
+     * recommended network.
+     *
+     * @param network The network to be recommended
+     */
+    public Notification createNetworkConnectedNotification(ScanResult network) {
+        return createNotificationBuilder(
+                mContext.getText(R.string.wifi_available_title_connected), network.SSID)
+                .build();
+    }
+
+    /**
+     * Creates the notification that indicates the controller failed to connect to the recommended
+     * network. Tapping this notification opens the wifi picker.
+     */
+    public Notification createNetworkFailedNotification() {
+        return createNotificationBuilder(
+                mContext.getText(R.string.wifi_available_title_failed_to_connect),
+                mContext.getText(R.string.wifi_available_content_failed_to_connect))
+                .setContentIntent(
+                        getPrivateBroadcast(ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE))
+                .setAutoCancel(true)
+                .build();
+    }
+
+    private Notification.Builder createNotificationBuilder(
+            CharSequence title, CharSequence content) {
+        return mFrameworkFacade.makeNotificationBuilder(mContext,
+                SystemNotificationChannels.NETWORK_AVAILABLE)
+                .setSmallIcon(R.drawable.stat_notify_wifi_in_range)
+                .setTicker(title)
+                .setContentTitle(title)
+                .setContentText(content)
+                .setDeleteIntent(getPrivateBroadcast(ACTION_USER_DISMISSED_NOTIFICATION))
+                .setShowWhen(false)
+                .setLocalOnly(true)
+                .setColor(mResources.getColor(R.color.system_notification_accent_color,
+                        mContext.getTheme()));
+    }
+
+    private PendingIntent getPrivateBroadcast(String action) {
+        Intent intent = new Intent(action).setPackage("android");
+        return mFrameworkFacade.getBroadcast(
+                mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+    }
+}
diff --git a/service/java/com/android/server/wifi/NetworkListStoreData.java b/service/java/com/android/server/wifi/NetworkListStoreData.java
index 5ddfd4d..f287d4b 100644
--- a/service/java/com/android/server/wifi/NetworkListStoreData.java
+++ b/service/java/com/android/server/wifi/NetworkListStoreData.java
@@ -16,10 +16,12 @@
 
 package com.android.server.wifi;
 
+import android.content.Context;
 import android.net.IpConfiguration;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
 import android.net.wifi.WifiEnterpriseConfig;
+import android.os.Process;
 import android.util.Log;
 import android.util.Pair;
 
@@ -52,6 +54,8 @@
     private static final String XML_TAG_SECTION_HEADER_WIFI_ENTERPRISE_CONFIGURATION =
             "WifiEnterpriseConfiguration";
 
+    private final Context mContext;
+
     /**
      * List of saved shared networks visible to all the users to be stored in the shared store file.
      */
@@ -62,7 +66,9 @@
      */
     private List<WifiConfiguration> mUserConfigurations;
 
-    NetworkListStoreData() {}
+    NetworkListStoreData(Context context) {
+        mContext = context;
+    }
 
     @Override
     public void serializeData(XmlSerializer out, boolean shared)
@@ -282,6 +288,19 @@
                     "Configuration key does not match. Retrieved: " + configKeyParsed
                             + ", Calculated: " + configKeyCalculated);
         }
+        // Set creatorUid/creatorName for networks which don't have it set to valid value.
+        String creatorName = mContext.getPackageManager().getNameForUid(configuration.creatorUid);
+        if (creatorName == null) {
+            Log.e(TAG, "Invalid creatorUid for saved network " + configuration.configKey()
+                    + ", creatorUid=" + configuration.creatorUid);
+            configuration.creatorUid = Process.SYSTEM_UID;
+            configuration.creatorName = creatorName;
+        } else if (!creatorName.equals(configuration.creatorName)) {
+            Log.w(TAG, "Invalid creatorName for saved network " + configuration.configKey()
+                    + ", creatorUid=" + configuration.creatorUid
+                    + ", creatorName=" + configuration.creatorName);
+            configuration.creatorName = creatorName;
+        }
 
         configuration.setNetworkSelectionStatus(status);
         configuration.setIpConfiguration(ipConfiguration);
diff --git a/service/java/com/android/server/wifi/OpenNetworkNotifier.java b/service/java/com/android/server/wifi/OpenNetworkNotifier.java
new file mode 100644
index 0000000..eee4ac5
--- /dev/null
+++ b/service/java/com/android/server/wifi/OpenNetworkNotifier.java
@@ -0,0 +1,498 @@
+/*
+ * Copyright (C) 2013 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 static com.android.server.wifi.ConnectToNetworkNotificationBuilder.ACTION_CONNECT_TO_NETWORK;
+import static com.android.server.wifi.ConnectToNetworkNotificationBuilder.ACTION_PICK_WIFI_NETWORK;
+import static com.android.server.wifi.ConnectToNetworkNotificationBuilder.ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE;
+import static com.android.server.wifi.ConnectToNetworkNotificationBuilder.ACTION_USER_DISMISSED_NOTIFICATION;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+import com.android.server.wifi.nano.WifiMetricsProto.ConnectToNetworkNotificationAndActionCount;
+import com.android.server.wifi.util.ScanResultUtil;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Takes care of handling the "open wi-fi network available" notification
+ *
+ * NOTE: These API's are not thread safe and should only be used from WifiStateMachine thread.
+ * @hide
+ */
+public class OpenNetworkNotifier {
+
+    private static final String TAG = "OpenNetworkNotifier";
+
+    /** Time in milliseconds to display the Connecting notification. */
+    private static final int TIME_TO_SHOW_CONNECTING_MILLIS = 10000;
+
+    /** Time in milliseconds to display the Connected notification. */
+    private static final int TIME_TO_SHOW_CONNECTED_MILLIS = 5000;
+
+    /** Time in milliseconds to display the Failed To Connect notification. */
+    private static final int TIME_TO_SHOW_FAILED_MILLIS = 5000;
+
+    /** The state of the notification */
+    @IntDef({
+            STATE_NO_NOTIFICATION,
+            STATE_SHOWING_RECOMMENDATION_NOTIFICATION,
+            STATE_CONNECTING_IN_NOTIFICATION,
+            STATE_CONNECTED_NOTIFICATION,
+            STATE_CONNECT_FAILED_NOTIFICATION
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface State {}
+
+    /** No recommendation is made and no notifications are shown. */
+    private static final int STATE_NO_NOTIFICATION = 0;
+    /** The initial notification recommending an open network to connect to is shown. */
+    private static final int STATE_SHOWING_RECOMMENDATION_NOTIFICATION = 1;
+    /** The notification of status of connecting to the recommended network is shown. */
+    private static final int STATE_CONNECTING_IN_NOTIFICATION = 2;
+    /** The notification that the connection to the recommended network was successful is shown. */
+    private static final int STATE_CONNECTED_NOTIFICATION = 3;
+    /** The notification to show that connection to the recommended network failed is shown. */
+    private static final int STATE_CONNECT_FAILED_NOTIFICATION = 4;
+
+    /** Current state of the notification. */
+    @State private int mState = STATE_NO_NOTIFICATION;
+
+    /** Identifier of the {@link SsidSetStoreData}. */
+    private static final String STORE_DATA_IDENTIFIER = "OpenNetworkNotifierBlacklist";
+    /**
+     * The {@link Clock#getWallClockMillis()} must be at least this value for us
+     * to show the notification again.
+     */
+    private long mNotificationRepeatTime;
+    /**
+     * When a notification is shown, we wait this amount before possibly showing it again.
+     */
+    private final long mNotificationRepeatDelay;
+    /** Default repeat delay in seconds. */
+    @VisibleForTesting
+    static final int DEFAULT_REPEAT_DELAY_SEC = 900;
+
+    /** Whether the user has set the setting to show the 'available networks' notification. */
+    private boolean mSettingEnabled;
+    /** Whether the screen is on or not. */
+    private boolean mScreenOn;
+
+    /** List of SSIDs blacklisted from recommendation. */
+    private final Set<String> mBlacklistedSsids;
+
+    private final Context mContext;
+    private final Handler mHandler;
+    private final FrameworkFacade mFrameworkFacade;
+    private final WifiMetrics mWifiMetrics;
+    private final Clock mClock;
+    private final WifiConfigManager mConfigManager;
+    private final WifiStateMachine mWifiStateMachine;
+    private final Messenger mSrcMessenger;
+    private final OpenNetworkRecommender mOpenNetworkRecommender;
+    private final ConnectToNetworkNotificationBuilder mNotificationBuilder;
+
+    private ScanResult mRecommendedNetwork;
+
+    OpenNetworkNotifier(
+            Context context,
+            Looper looper,
+            FrameworkFacade framework,
+            Clock clock,
+            WifiMetrics wifiMetrics,
+            WifiConfigManager wifiConfigManager,
+            WifiConfigStore wifiConfigStore,
+            WifiStateMachine wifiStateMachine,
+            OpenNetworkRecommender openNetworkRecommender,
+            ConnectToNetworkNotificationBuilder connectToNetworkNotificationBuilder) {
+        mContext = context;
+        mHandler = new Handler(looper);
+        mFrameworkFacade = framework;
+        mWifiMetrics = wifiMetrics;
+        mClock = clock;
+        mConfigManager = wifiConfigManager;
+        mWifiStateMachine = wifiStateMachine;
+        mOpenNetworkRecommender = openNetworkRecommender;
+        mNotificationBuilder = connectToNetworkNotificationBuilder;
+        mScreenOn = false;
+        mSrcMessenger = new Messenger(new Handler(looper, mConnectionStateCallback));
+
+        mBlacklistedSsids = new ArraySet<>();
+        wifiConfigStore.registerStoreData(new SsidSetStoreData(
+                STORE_DATA_IDENTIFIER, new OpenNetworkNotifierStoreData()));
+
+        // Setting is in seconds
+        mNotificationRepeatDelay = mFrameworkFacade.getIntegerSetting(context,
+                Settings.Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY,
+                DEFAULT_REPEAT_DELAY_SEC) * 1000L;
+        NotificationEnabledSettingObserver settingObserver = new NotificationEnabledSettingObserver(
+                mHandler);
+        settingObserver.register();
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ACTION_USER_DISMISSED_NOTIFICATION);
+        filter.addAction(ACTION_CONNECT_TO_NETWORK);
+        filter.addAction(ACTION_PICK_WIFI_NETWORK);
+        filter.addAction(ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE);
+        mContext.registerReceiver(
+                mBroadcastReceiver, filter, null /* broadcastPermission */, mHandler);
+    }
+
+    private final BroadcastReceiver mBroadcastReceiver =
+            new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    switch (intent.getAction()) {
+                        case ACTION_USER_DISMISSED_NOTIFICATION:
+                            handleUserDismissedAction();
+                            break;
+                        case ACTION_CONNECT_TO_NETWORK:
+                            handleConnectToNetworkAction();
+                            break;
+                        case ACTION_PICK_WIFI_NETWORK:
+                            handleSeeAllNetworksAction();
+                            break;
+                        case ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE:
+                            handlePickWifiNetworkAfterConnectFailure();
+                            break;
+                        default:
+                            Log.e(TAG, "Unknown action " + intent.getAction());
+                    }
+                }
+            };
+
+    private final Handler.Callback mConnectionStateCallback = (Message msg) -> {
+        switch (msg.what) {
+            // Success here means that an attempt to connect to the network has been initiated.
+            // Successful connection updates are received via the
+            // WifiConnectivityManager#handleConnectionStateChanged() callback.
+            case WifiManager.CONNECT_NETWORK_SUCCEEDED:
+                break;
+            case WifiManager.CONNECT_NETWORK_FAILED:
+                handleConnectionAttemptFailedToSend();
+                break;
+            default:
+                Log.e(TAG, "Unknown message " + msg.what);
+        }
+        return true;
+    };
+
+    /**
+     * Clears the pending notification. This is called by {@link WifiConnectivityManager} on stop.
+     *
+     * @param resetRepeatTime resets the time delay for repeated notification if true.
+     */
+    public void clearPendingNotification(boolean resetRepeatTime) {
+        if (resetRepeatTime) {
+            mNotificationRepeatTime = 0;
+        }
+
+        if (mState != STATE_NO_NOTIFICATION) {
+            getNotificationManager().cancel(SystemMessage.NOTE_NETWORK_AVAILABLE);
+
+            if (mRecommendedNetwork != null) {
+                Log.d(TAG, "Notification with state="
+                        + mState
+                        + " was cleared for recommended network: "
+                        + mRecommendedNetwork.SSID);
+            }
+            mState = STATE_NO_NOTIFICATION;
+            mRecommendedNetwork = null;
+        }
+    }
+
+    private boolean isControllerEnabled() {
+        return mSettingEnabled && !UserManager.get(mContext)
+                .hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, UserHandle.CURRENT);
+    }
+
+    /**
+     * If there are open networks, attempt to post an open network notification.
+     *
+     * @param availableNetworks Available networks from
+     * {@link WifiNetworkSelector.NetworkEvaluator#getFilteredScanDetailsForOpenUnsavedNetworks()}.
+     */
+    public void handleScanResults(@NonNull List<ScanDetail> availableNetworks) {
+        if (!isControllerEnabled()) {
+            clearPendingNotification(true /* resetRepeatTime */);
+            return;
+        }
+        if (availableNetworks.isEmpty()) {
+            clearPendingNotification(false /* resetRepeatTime */);
+            return;
+        }
+
+        // Not enough time has passed to show a recommendation notification again
+        if (mState == STATE_NO_NOTIFICATION
+                && mClock.getWallClockMillis() < mNotificationRepeatTime) {
+            return;
+        }
+
+        // Do nothing when the screen is off and no notification is showing.
+        if (mState == STATE_NO_NOTIFICATION && !mScreenOn) {
+            return;
+        }
+
+        // Only show a new or update an existing recommendation notification.
+        if (mState == STATE_NO_NOTIFICATION
+                || mState == STATE_SHOWING_RECOMMENDATION_NOTIFICATION) {
+            ScanResult recommendation = mOpenNetworkRecommender.recommendNetwork(
+                    availableNetworks, new ArraySet<>(mBlacklistedSsids));
+
+            if (recommendation != null) {
+                postInitialNotification(recommendation);
+            } else {
+                clearPendingNotification(false /* resetRepeatTime */);
+            }
+        }
+    }
+
+    /** Handles screen state changes. */
+    public void handleScreenStateChanged(boolean screenOn) {
+        mScreenOn = screenOn;
+    }
+
+    /**
+     * Called by {@link WifiConnectivityManager} when Wi-Fi is connected. If the notification
+     * was in the connecting state, update the notification to show that it has connected to the
+     * recommended network.
+     */
+    public void handleWifiConnected() {
+        if (mState != STATE_CONNECTING_IN_NOTIFICATION) {
+            clearPendingNotification(true /* resetRepeatTime */);
+            return;
+        }
+
+        postNotification(mNotificationBuilder.createNetworkConnectedNotification(
+                mRecommendedNetwork));
+
+        Log.d(TAG, "User connected to recommended network: " + mRecommendedNetwork.SSID);
+        mWifiMetrics.incrementConnectToNetworkNotification(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_CONNECTED_TO_NETWORK);
+        mState = STATE_CONNECTED_NOTIFICATION;
+        mHandler.postDelayed(
+                () -> {
+                    if (mState == STATE_CONNECTED_NOTIFICATION) {
+                        clearPendingNotification(true /* resetRepeatTime */);
+                    }
+                },
+                TIME_TO_SHOW_CONNECTED_MILLIS);
+    }
+
+    /**
+     * Handles when a Wi-Fi connection attempt failed.
+     */
+    public void handleConnectionFailure() {
+        if (mState != STATE_CONNECTING_IN_NOTIFICATION) {
+            return;
+        }
+        postNotification(mNotificationBuilder.createNetworkFailedNotification());
+
+        Log.d(TAG, "User failed to connect to recommended network: " + mRecommendedNetwork.SSID);
+        mWifiMetrics.incrementConnectToNetworkNotification(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_FAILED_TO_CONNECT);
+        mState = STATE_CONNECT_FAILED_NOTIFICATION;
+        mHandler.postDelayed(
+                () -> {
+                    if (mState == STATE_CONNECT_FAILED_NOTIFICATION) {
+                        clearPendingNotification(false /* resetRepeatTime */);
+                    }
+                },
+                TIME_TO_SHOW_FAILED_MILLIS);
+    }
+
+    private NotificationManager getNotificationManager() {
+        return (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+    }
+
+    private void postInitialNotification(ScanResult recommendedNetwork) {
+        if (mRecommendedNetwork != null
+                && TextUtils.equals(mRecommendedNetwork.SSID, recommendedNetwork.SSID)) {
+            return;
+        }
+        postNotification(mNotificationBuilder.createConnectToNetworkNotification(
+                recommendedNetwork));
+        if (mState == STATE_NO_NOTIFICATION) {
+            mWifiMetrics.incrementConnectToNetworkNotification(
+                    ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
+        } else {
+            mWifiMetrics.incrementNumOpenNetworkRecommendationUpdates();
+        }
+        mState = STATE_SHOWING_RECOMMENDATION_NOTIFICATION;
+        mRecommendedNetwork = recommendedNetwork;
+        mNotificationRepeatTime = mClock.getWallClockMillis() + mNotificationRepeatDelay;
+    }
+
+    private void postNotification(Notification notification) {
+        getNotificationManager().notify(SystemMessage.NOTE_NETWORK_AVAILABLE, notification);
+    }
+
+    private void handleConnectToNetworkAction() {
+        mWifiMetrics.incrementConnectToNetworkNotificationAction(mState,
+                ConnectToNetworkNotificationAndActionCount.ACTION_CONNECT_TO_NETWORK);
+        if (mState != STATE_SHOWING_RECOMMENDATION_NOTIFICATION) {
+            return;
+        }
+        postNotification(mNotificationBuilder.createNetworkConnectingNotification(
+                mRecommendedNetwork));
+        mWifiMetrics.incrementConnectToNetworkNotification(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_CONNECTING_TO_NETWORK);
+
+        Log.d(TAG, "User initiated connection to recommended network: " + mRecommendedNetwork.SSID);
+        WifiConfiguration network = ScanResultUtil.createNetworkFromScanResult(mRecommendedNetwork);
+        Message msg = Message.obtain();
+        msg.what = WifiManager.CONNECT_NETWORK;
+        msg.arg1 = WifiConfiguration.INVALID_NETWORK_ID;
+        msg.obj = network;
+        msg.replyTo = mSrcMessenger;
+        mWifiStateMachine.sendMessage(msg);
+
+        mState = STATE_CONNECTING_IN_NOTIFICATION;
+        mHandler.postDelayed(
+                () -> {
+                    if (mState == STATE_CONNECTING_IN_NOTIFICATION) {
+                        handleConnectionFailure();
+                    }
+                },
+                TIME_TO_SHOW_CONNECTING_MILLIS);
+    }
+
+    private void handleSeeAllNetworksAction() {
+        mWifiMetrics.incrementConnectToNetworkNotificationAction(mState,
+                ConnectToNetworkNotificationAndActionCount.ACTION_PICK_WIFI_NETWORK);
+        startWifiSettings();
+    }
+
+    private void startWifiSettings() {
+        // Close notification drawer before opening the picker.
+        mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+        mContext.startActivity(
+                new Intent(Settings.ACTION_WIFI_SETTINGS)
+                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+        clearPendingNotification(false /* resetRepeatTime */);
+    }
+
+    private void handleConnectionAttemptFailedToSend() {
+        handleConnectionFailure();
+        mWifiMetrics.incrementNumOpenNetworkConnectMessageFailedToSend();
+    }
+
+    private void handlePickWifiNetworkAfterConnectFailure() {
+        mWifiMetrics.incrementConnectToNetworkNotificationAction(mState,
+                ConnectToNetworkNotificationAndActionCount
+                        .ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE);
+        startWifiSettings();
+    }
+
+    private void handleUserDismissedAction() {
+        Log.d(TAG, "User dismissed notification with state=" + mState);
+        mWifiMetrics.incrementConnectToNetworkNotificationAction(mState,
+                ConnectToNetworkNotificationAndActionCount.ACTION_USER_DISMISSED_NOTIFICATION);
+        if (mState == STATE_SHOWING_RECOMMENDATION_NOTIFICATION) {
+            // blacklist dismissed network
+            mBlacklistedSsids.add(mRecommendedNetwork.SSID);
+            mWifiMetrics.setOpenNetworkRecommenderBlacklistSize(mBlacklistedSsids.size());
+            mConfigManager.saveToStore(false /* forceWrite */);
+            Log.d(TAG, "Network is added to the open network notification blacklist: "
+                    + mRecommendedNetwork.SSID);
+        }
+        resetStateAndDelayNotification();
+    }
+
+    private void resetStateAndDelayNotification() {
+        mState = STATE_NO_NOTIFICATION;
+        mNotificationRepeatTime = System.currentTimeMillis() + mNotificationRepeatDelay;
+        mRecommendedNetwork = null;
+    }
+
+    /** Dump ONA controller state. */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("OpenNetworkNotifier: ");
+        pw.println("mSettingEnabled " + mSettingEnabled);
+        pw.println("currentTime: " + mClock.getWallClockMillis());
+        pw.println("mNotificationRepeatTime: " + mNotificationRepeatTime);
+        pw.println("mState: " + mState);
+        pw.println("mBlacklistedSsids: " + mBlacklistedSsids.toString());
+    }
+
+    private class OpenNetworkNotifierStoreData implements SsidSetStoreData.DataSource {
+        @Override
+        public Set<String> getSsids() {
+            return new ArraySet<>(mBlacklistedSsids);
+        }
+
+        @Override
+        public void setSsids(Set<String> ssidList) {
+            mBlacklistedSsids.addAll(ssidList);
+            mWifiMetrics.setOpenNetworkRecommenderBlacklistSize(mBlacklistedSsids.size());
+        }
+    }
+
+    private class NotificationEnabledSettingObserver extends ContentObserver {
+        NotificationEnabledSettingObserver(Handler handler) {
+            super(handler);
+        }
+
+        public void register() {
+            mFrameworkFacade.registerContentObserver(mContext, Settings.Global.getUriFor(
+                    Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON), true, this);
+            mSettingEnabled = getValue();
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            super.onChange(selfChange);
+            mSettingEnabled = getValue();
+            clearPendingNotification(true /* resetRepeatTime */);
+        }
+
+        private boolean getValue() {
+            boolean enabled = mFrameworkFacade.getIntegerSetting(mContext,
+                    Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1) == 1;
+            mWifiMetrics.setIsWifiNetworksAvailableNotificationEnabled(enabled);
+            return enabled;
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/OpenNetworkRecommender.java b/service/java/com/android/server/wifi/OpenNetworkRecommender.java
new file mode 100644
index 0000000..5ceeddd
--- /dev/null
+++ b/service/java/com/android/server/wifi/OpenNetworkRecommender.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 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 android.annotation.NonNull;
+import android.net.wifi.ScanResult;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Helps recommend the best available network for {@link OpenNetworkNotifier}.
+ *
+ * NOTE: These API's are not thread safe and should only be used from WifiStateMachine thread.
+ * @hide
+ */
+public class OpenNetworkRecommender {
+
+    /**
+     * Recommends the network with the best signal strength.
+     *
+     * @param networks List of scan details to pick a recommendation. This list should not be null
+     *                 or empty.
+     * @param blacklistedSsids The list of SSIDs that should not be recommended.
+     */
+    public ScanResult recommendNetwork(@NonNull List<ScanDetail> networks,
+                                       @NonNull Set<String> blacklistedSsids) {
+        ScanResult result = null;
+        int highestRssi = Integer.MIN_VALUE;
+        for (ScanDetail scanDetail : networks) {
+            ScanResult scanResult = scanDetail.getScanResult();
+
+            if (scanResult.level > highestRssi) {
+                result = scanResult;
+                highestRssi = scanResult.level;
+            }
+        }
+
+        if (result != null && blacklistedSsids.contains(result.SSID)) {
+            result = null;
+        }
+        return result;
+    }
+}
diff --git a/service/java/com/android/server/wifi/RttService.java b/service/java/com/android/server/wifi/RttService.java
index 7e4648b..bd27367 100644
--- a/service/java/com/android/server/wifi/RttService.java
+++ b/service/java/com/android/server/wifi/RttService.java
@@ -6,11 +6,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
-import android.net.wifi.IApInterface;
-import android.net.wifi.IClientInterface;
-import android.net.wifi.IInterfaceEventCallback;
 import android.net.wifi.IRttManager;
-import android.net.wifi.IWificond;
 import android.net.wifi.RttManager;
 import android.net.wifi.RttManager.ResponderConfig;
 import android.net.wifi.WifiManager;
@@ -26,6 +22,7 @@
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Slog;
+import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.AsyncChannel;
@@ -40,22 +37,64 @@
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedList;
-import java.util.List;
 import java.util.Queue;
 import java.util.Set;
 
 public final class RttService extends SystemService {
 
     public static final boolean DBG = true;
-    private static final String WIFICOND_SERVICE_NAME = "wificond";
 
     static class RttServiceImpl extends IRttManager.Stub {
+        private int mCurrentKey = 100; // increment on each usage
+        private final SparseArray<IBinder> mBinderByKey = new SparseArray<>();
 
         @Override
-        public Messenger getMessenger() {
+        public Messenger getMessenger(IBinder binder, int[] key) {
+            if (key != null && key.length != 0) {
+                final int keyToUse = mCurrentKey++;
+                if (binder != null) {
+                    try {
+                        binder.linkToDeath(() -> {
+                            // clean-up here if didn't get final registration
+                            Slog.d(TAG, "Binder death on key=" + keyToUse);
+                            mBinderByKey.delete(keyToUse);
+                        }, 0);
+                        mBinderByKey.put(keyToUse, binder);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "getMessenger: can't link to death on binder: " + e);
+                        return null;
+                    }
+                }
+
+                key[0] = keyToUse;
+            }
             return new Messenger(mClientHandler);
         }
 
+        private class RttDeathListener implements IBinder.DeathRecipient {
+            private final IBinder mBinder;
+            private final Messenger mReplyTo;
+
+            RttDeathListener(IBinder binder, Messenger replyTo) {
+                mBinder = binder;
+                mReplyTo = replyTo;
+            }
+
+            @Override
+            public void binderDied() {
+                if (DBG) Slog.d(TAG, "binder death for client mReplyTo=" + mReplyTo);
+                synchronized (mLock) {
+                    ClientInfo ci = mClients.remove(mReplyTo);
+                    if (ci != null) {
+                        ci.cleanup();
+                    } else {
+                        Slog.w(TAG,
+                                "ClientInfo not found for terminated app -- mReplyTo=" + mReplyTo);
+                    }
+                }
+            }
+        }
+
         private class ClientHandler extends Handler {
 
             ClientHandler(android.os.Looper looper) {
@@ -86,13 +125,30 @@
                     case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
                         AsyncChannel ac = new AsyncChannel();
                         ac.connected(mContext, this, msg.replyTo);
-                        ClientInfo client = new ClientInfo(ac, msg.sendingUid);
+                        String packageName = msg.obj != null
+                                ? ((RttManager.RttClient) msg.obj).getPackageName() : null;
+                        ClientInfo client = new ClientInfo(ac, msg.sendingUid, packageName);
                         synchronized (mLock) {
                             mClients.put(msg.replyTo, client);
                         }
                         ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
                                 AsyncChannel.STATUS_SUCCESSFUL);
                         return;
+                    case RttManager.CMD_OP_REG_BINDER: {
+                        int key = msg.arg1;
+                        IBinder binder = mBinderByKey.get(key);
+                        if (binder == null) {
+                            Slog.e(TAG, "Can't find binder registered with key=" + key + " - no "
+                                    + "death listener!");
+                            return;
+                        }
+                        try {
+                            binder.linkToDeath(new RttDeathListener(binder, msg.replyTo), 0);
+                        } catch (RemoteException e) {
+                            Slog.e(TAG, "Can't link to death for binder on key=" + key);
+                        }
+                        return;
+                    }
                 }
 
                 ClientInfo ci;
@@ -109,6 +165,12 @@
                             "Client doesn't have LOCATION_HARDWARE permission");
                     return;
                 }
+                if (!checkLocationPermission(ci)) {
+                    replyFailed(msg, RttManager.REASON_PERMISSION_DENIED,
+                            "Client doesn't have ACCESS_COARSE_LOCATION or "
+                                    + "ACCESS_FINE_LOCATION permission");
+                    return;
+                }
                 final int validCommands[] = {
                         RttManager.CMD_OP_START_RANGING,
                         RttManager.CMD_OP_STOP_RANGING,
@@ -141,9 +203,10 @@
         private final WifiNative mWifiNative;
         private final Context mContext;
         private final Looper mLooper;
+        private final WifiInjector mWifiInjector;
+
         private RttStateMachine mStateMachine;
         private ClientHandler mClientHandler;
-        private WifiInjector mWifiInjector;
 
         RttServiceImpl(Context context, Looper looper, WifiInjector wifiInjector) {
             mContext = context;
@@ -192,14 +255,16 @@
         private class ClientInfo {
             private final AsyncChannel mChannel;
             private final int mUid;
+            private final String mPackageName;
 
             ArrayMap<Integer, RttRequest> mRequests = new ArrayMap<>();
             // Client keys of all outstanding responders.
             Set<Integer> mResponderRequests = new HashSet<>();
 
-            ClientInfo(AsyncChannel channel, int uid) {
+            ClientInfo(AsyncChannel channel, int uid, String packageName) {
                 mChannel = channel;
                 mUid = uid;
+                mPackageName = packageName;
             }
 
             void addResponderRequest(int key) {
@@ -293,37 +358,11 @@
         private static final int CMD_DRIVER_UNLOADED                     = BASE + 1;
         private static final int CMD_ISSUE_NEXT_REQUEST                  = BASE + 2;
         private static final int CMD_RTT_RESPONSE                        = BASE + 3;
-        private static final int CMD_CLIENT_INTERFACE_READY              = BASE + 4;
-        private static final int CMD_CLIENT_INTERFACE_DOWN               = BASE + 5;
 
         // Maximum duration for responder role.
         private static final int MAX_RESPONDER_DURATION_SECONDS = 60 * 10;
 
-        private static class InterfaceEventHandler extends IInterfaceEventCallback.Stub {
-            InterfaceEventHandler(RttStateMachine rttStateMachine) {
-                mRttStateMachine = rttStateMachine;
-            }
-            @Override
-            public void OnClientTorndownEvent(IClientInterface networkInterface) {
-                mRttStateMachine.sendMessage(CMD_CLIENT_INTERFACE_DOWN, networkInterface);
-            }
-            @Override
-            public void OnClientInterfaceReady(IClientInterface networkInterface) {
-                mRttStateMachine.sendMessage(CMD_CLIENT_INTERFACE_READY, networkInterface);
-            }
-            @Override
-            public void OnApTorndownEvent(IApInterface networkInterface) { }
-            @Override
-            public void OnApInterfaceReady(IApInterface networkInterface) { }
-
-            private RttStateMachine mRttStateMachine;
-        }
-
         class RttStateMachine extends StateMachine {
-            private IWificond mWificond;
-            private InterfaceEventHandler mInterfaceEventHandler;
-            private IClientInterface mClientInterface;
-
             DefaultState mDefaultState = new DefaultState();
             EnabledState mEnabledState = new EnabledState();
             InitiatorEnabledState mInitiatorEnabledState = new InitiatorEnabledState();
@@ -385,39 +424,9 @@
             class EnabledState extends State {
                 @Override
                 public void enter() {
-                    // This allows us to tolerate wificond restarts.
-                    // When wificond restarts WifiStateMachine is supposed to go
-                    // back to initial state and restart.
-                    // 1) RttService watches for WIFI_STATE_ENABLED broadcasts
-                    // 2) WifiStateMachine sends these broadcasts in the SupplicantStarted state
-                    // 3) Since WSM will only be in SupplicantStarted for as long as wificond is
-                    // alive, we refresh our wificond handler here and we don't subscribe to
-                    // wificond's death explicitly.
-                    mWificond = mWifiInjector.makeWificond();
-                    if (mWificond == null) {
-                        Log.w(TAG, "Failed to get wificond binder handler");
-                        transitionTo(mDefaultState);
-                    }
-                    mInterfaceEventHandler = new InterfaceEventHandler(mStateMachine);
-                    try {
-                        mWificond.RegisterCallback(mInterfaceEventHandler);
-                        // Get the current client interface, assuming there is at most
-                        // one client interface for now.
-                        List<IBinder> interfaces = mWificond.GetClientInterfaces();
-                        if (interfaces.size() > 0) {
-                            mStateMachine.sendMessage(
-                                    CMD_CLIENT_INTERFACE_READY,
-                                    IClientInterface.Stub.asInterface(interfaces.get(0)));
-                        }
-                    } catch (RemoteException e1) { }
-
                 }
                 @Override
                 public void exit() {
-                    try {
-                        mWificond.UnregisterCallback(mInterfaceEventHandler);
-                    } catch (RemoteException e1) { }
-                    mInterfaceEventHandler = null;
                 }
                 @Override
                 public boolean processMessage(Message msg) {
@@ -481,14 +490,6 @@
                             break;
                         case RttManager.CMD_OP_DISABLE_RESPONDER:
                             break;
-                        case CMD_CLIENT_INTERFACE_DOWN:
-                            if (mClientInterface == (IClientInterface) msg.obj) {
-                                mClientInterface = null;
-                            }
-                            break;
-                        case CMD_CLIENT_INTERFACE_READY:
-                            mClientInterface = (IClientInterface) msg.obj;
-                            break;
                         default:
                             return NOT_HANDLED;
                     }
@@ -534,8 +535,10 @@
                             break;
                         case CMD_RTT_RESPONSE:
                             if (DBG) Log.d(TAG, "Received an RTT response from: " + msg.arg2);
-                            mOutstandingRequest.ci.reportResult(
-                                    mOutstandingRequest, (RttManager.RttResult[])msg.obj);
+                            if (checkLocationPermission(mOutstandingRequest.ci)) {
+                                mOutstandingRequest.ci.reportResult(
+                                        mOutstandingRequest, (RttManager.RttResult[]) msg.obj);
+                            }
                             mOutstandingRequest = null;
                             sendMessage(CMD_ISSUE_NEXT_REQUEST);
                             break;
@@ -659,7 +662,7 @@
             }
         }
 
-        boolean enforcePermissionCheck(Message msg) {
+        private boolean enforcePermissionCheck(Message msg) {
             try {
                 mContext.enforcePermission(Manifest.permission.LOCATION_HARDWARE,
                          -1, msg.sendingUid, "LocationRTT");
@@ -670,6 +673,12 @@
             return true;
         }
 
+        // Returns whether the client has location permission.
+        private boolean checkLocationPermission(ClientInfo clientInfo) {
+            return mWifiInjector.getWifiPermissionsUtil().checkCallersLocationPermission(
+                    clientInfo.mPackageName, clientInfo.mUid);
+        }
+
         @Override
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
@@ -717,8 +726,11 @@
             if (DBG) Log.d(TAG, "No more requests left");
             return null;
         }
+
         @Override
         public RttManager.RttCapabilities getRttCapabilities() {
+            mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE,
+                    "Location Hardware permission not granted to access rtt capabilities");
             return mWifiNative.getRttCapabilities();
         }
     }
diff --git a/service/java/com/android/server/wifi/ScanDetailCache.java b/service/java/com/android/server/wifi/ScanDetailCache.java
index 3b69a64..abb6ad8 100644
--- a/service/java/com/android/server/wifi/ScanDetailCache.java
+++ b/service/java/com/android/server/wifi/ScanDetailCache.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wifi;
 
+import android.annotation.NonNull;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
 import android.os.SystemClock;
@@ -73,7 +74,7 @@
      * @param bssid provided BSSID
      * @return {@code null} if no match ScanResult is found.
      */
-    public ScanResult get(String bssid) {
+    public ScanResult getScanResult(String bssid) {
         ScanDetail scanDetail = getScanDetail(bssid);
         return scanDetail == null ? null : scanDetail.getScanResult();
     }
@@ -84,11 +85,11 @@
      * @param bssid provided BSSID
      * @return {@code null} if no match ScanDetail is found.
      */
-    public ScanDetail getScanDetail(String bssid) {
+    public ScanDetail getScanDetail(@NonNull String bssid) {
         return mMap.get(bssid);
     }
 
-    void remove(String bssid) {
+    void remove(@NonNull String bssid) {
         mMap.remove(bssid);
     }
 
diff --git a/service/java/com/android/server/wifi/SelfRecovery.java b/service/java/com/android/server/wifi/SelfRecovery.java
index 21a3e0a..e39e0d5 100644
--- a/service/java/com/android/server/wifi/SelfRecovery.java
+++ b/service/java/com/android/server/wifi/SelfRecovery.java
@@ -72,7 +72,7 @@
             Log.e(TAG, "Invalid trigger reason. Ignoring...");
             return;
         }
-        Log.wtf(TAG, "Triggering recovery for reason: " + REASON_STRINGS[reason]);
+        Log.e(TAG, "Triggering recovery for reason: " + REASON_STRINGS[reason]);
         if (reason == REASON_WIFICOND_CRASH || reason == REASON_HAL_CRASH) {
             trimPastRestartTimes();
             // Ensure there haven't been too many restarts within MAX_RESTARTS_TIME_WINDOW
diff --git a/service/java/com/android/server/wifi/SsidSetStoreData.java b/service/java/com/android/server/wifi/SsidSetStoreData.java
new file mode 100644
index 0000000..daed26a
--- /dev/null
+++ b/service/java/com/android/server/wifi/SsidSetStoreData.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2017 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 android.text.TextUtils;
+
+import com.android.server.wifi.util.XmlUtil;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Store data for network notifiers.
+ *
+ * Below are the current configuration data for each respective store file:
+ *
+ * Share Store (system wide configurations)
+ * - No data
+ *
+ * User Store (user specific configurations)
+ * - Set of blacklisted SSIDs
+ */
+public class SsidSetStoreData implements WifiConfigStore.StoreData {
+    private static final String XML_TAG_SECTION_HEADER_SUFFIX = "ConfigData";
+    private static final String XML_TAG_SSID_SET = "SSIDSet";
+
+    private final String mTagName;
+    private final DataSource mDataSource;
+
+    /**
+     * Interface define the data source for the notifier store data.
+     */
+    public interface DataSource {
+        /**
+         * Retrieve the SSID set from the data source.
+         *
+         * @return Set of SSIDs
+         */
+        Set<String> getSsids();
+
+        /**
+         * Update the SSID set in the data source.
+         *
+         * @param ssidSet The set of SSIDs
+         */
+        void setSsids(Set<String> ssidSet);
+    }
+
+    /**
+     * Creates the SSID Set store data.
+     *
+     * @param name Identifier of the SSID set.
+     * @param dataSource The DataSource that implements the update and retrieval of the SSID set.
+     */
+    SsidSetStoreData(String name, DataSource dataSource) {
+        mTagName = name + XML_TAG_SECTION_HEADER_SUFFIX;
+        mDataSource = dataSource;
+    }
+
+    @Override
+    public void serializeData(XmlSerializer out, boolean shared)
+            throws XmlPullParserException, IOException {
+        if (shared) {
+            throw new XmlPullParserException("Share data not supported");
+        }
+        Set<String> ssidSet = mDataSource.getSsids();
+        if (ssidSet != null && !ssidSet.isEmpty()) {
+            XmlUtil.writeNextValue(out, XML_TAG_SSID_SET, mDataSource.getSsids());
+        }
+    }
+
+    @Override
+    public void deserializeData(XmlPullParser in, int outerTagDepth, boolean shared)
+            throws XmlPullParserException, IOException {
+        if (shared) {
+            throw new XmlPullParserException("Share data not supported");
+        }
+
+        while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) {
+            String[] valueName = new String[1];
+            Object value = XmlUtil.readCurrentValue(in, valueName);
+            if (TextUtils.isEmpty(valueName[0])) {
+                throw new XmlPullParserException("Missing value name");
+            }
+            switch (valueName[0]) {
+                case XML_TAG_SSID_SET:
+                    mDataSource.setSsids((Set<String>) value);
+                    break;
+                default:
+                    throw new XmlPullParserException("Unknown tag under "
+                            + mTagName + ": " + valueName[0]);
+            }
+        }
+    }
+
+    @Override
+    public void resetData(boolean shared) {
+        if (!shared) {
+            mDataSource.setSsids(new HashSet<>());
+        }
+    }
+
+    @Override
+    public String getName() {
+        return mTagName;
+    }
+
+    @Override
+    public boolean supportShareData() {
+        return false;
+    }
+}
diff --git a/service/java/com/android/server/wifi/SupplicantStaNetworkHal.java b/service/java/com/android/server/wifi/SupplicantStaNetworkHal.java
index 61ec9b3..b359897 100644
--- a/service/java/com/android/server/wifi/SupplicantStaNetworkHal.java
+++ b/service/java/com/android/server/wifi/SupplicantStaNetworkHal.java
@@ -199,7 +199,7 @@
             for (int i = 0; i < 4; i++) {
                 config.wepKeys[i] = null;
                 if (getWepKey(i) && !ArrayUtils.isEmpty(mWepKey)) {
-                    config.wepKeys[i] = NativeUtil.bytesToHexOrQuotedAsciiString(mWepKey);
+                    config.wepKeys[i] = NativeUtil.bytesToHexOrQuotedString(mWepKey);
                 }
             }
             /** PSK pass phrase */
@@ -293,7 +293,7 @@
                 for (int i = 0; i < config.wepKeys.length; i++) {
                     if (config.wepKeys[i] != null) {
                         if (!setWepKey(
-                                i, NativeUtil.hexOrQuotedAsciiStringToBytes(config.wepKeys[i]))) {
+                                i, NativeUtil.hexOrQuotedStringToBytes(config.wepKeys[i]))) {
                             Log.e(TAG, "failed to set wep_key " + i);
                             return false;
                         }
diff --git a/service/java/com/android/server/wifi/WifiConfigManager.java b/service/java/com/android/server/wifi/WifiConfigManager.java
index d8b4237..02f8302 100644
--- a/service/java/com/android/server/wifi/WifiConfigManager.java
+++ b/service/java/com/android/server/wifi/WifiConfigManager.java
@@ -47,7 +47,6 @@
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalServices;
-import com.android.server.wifi.WifiConfigStoreLegacy.WifiConfigStoreDataLegacy;
 import com.android.server.wifi.hotspot2.PasspointManager;
 import com.android.server.wifi.util.TelephonyUtil;
 import com.android.server.wifi.util.WifiPermissionsUtil;
@@ -248,7 +247,6 @@
     private final TelephonyManager mTelephonyManager;
     private final WifiKeyStore mWifiKeyStore;
     private final WifiConfigStore mWifiConfigStore;
-    private final WifiConfigStoreLegacy mWifiConfigStoreLegacy;
     private final WifiPermissionsUtil mWifiPermissionsUtil;
     private final WifiPermissionsWrapper mWifiPermissionsWrapper;
     /**
@@ -342,7 +340,7 @@
     WifiConfigManager(
             Context context, Clock clock, UserManager userManager,
             TelephonyManager telephonyManager, WifiKeyStore wifiKeyStore,
-            WifiConfigStore wifiConfigStore, WifiConfigStoreLegacy wifiConfigStoreLegacy,
+            WifiConfigStore wifiConfigStore,
             WifiPermissionsUtil wifiPermissionsUtil,
             WifiPermissionsWrapper wifiPermissionsWrapper,
             NetworkListStoreData networkListStoreData,
@@ -354,7 +352,6 @@
         mTelephonyManager = telephonyManager;
         mWifiKeyStore = wifiKeyStore;
         mWifiConfigStore = wifiConfigStore;
-        mWifiConfigStoreLegacy = wifiConfigStoreLegacy;
         mWifiPermissionsUtil = wifiPermissionsUtil;
         mWifiPermissionsWrapper = wifiPermissionsWrapper;
 
@@ -652,6 +649,12 @@
      * @param ignoreLockdown Ignore the configuration lockdown checks for connection attempts.
      */
     private boolean canModifyNetwork(WifiConfiguration config, int uid, boolean ignoreLockdown) {
+        // System internals can always update networks; they're typically only
+        // making meteredHint or meteredOverride changes
+        if (uid == Process.SYSTEM_UID) {
+            return true;
+        }
+
         // Passpoint configurations are generated and managed by PasspointManager. They can be
         // added by either PasspointNetworkEvaluator (for auto connection) or Settings app
         // (for manual connection), and need to be removed once the connection is completed.
@@ -711,16 +714,24 @@
     }
 
     /**
-     * Method to check if the provided UID belongs to the current foreground user or some other
-     * app (only SysUI today) running on behalf of the user.
-     * This is used to prevent any background user apps from modifying network configurations.
+     * Check if the given UID belongs to the current foreground user. This is
+     * used to prevent apps running in background users from modifying network
+     * configurations.
+     * <p>
+     * UIDs belonging to system internals (such as SystemUI) are always allowed,
+     * since they always run as {@link UserHandle#USER_SYSTEM}.
      *
      * @param uid uid of the app.
-     * @return true if the UID belongs to the current foreground app or SystemUI, false otherwise.
+     * @return true if the given UID belongs to the current foreground user,
+     *         otherwise false.
      */
     private boolean doesUidBelongToCurrentUser(int uid) {
-        return (WifiConfigurationUtil.doesUidBelongToAnyProfile(
-                uid, mUserManager.getProfiles(mCurrentUserId)) || (uid == mSystemUiUid));
+        if (uid == android.os.Process.SYSTEM_UID || uid == mSystemUiUid) {
+            return true;
+        } else {
+            return WifiConfigurationUtil.doesUidBelongToAnyProfile(
+                    uid, mUserManager.getProfiles(mCurrentUserId));
+        }
     }
 
     /**
@@ -829,6 +840,10 @@
             internalConfig.enterpriseConfig.copyFromExternal(
                     externalConfig.enterpriseConfig, PASSWORD_MASK);
         }
+
+        // Copy over any metered information.
+        internalConfig.meteredHint = externalConfig.meteredHint;
+        internalConfig.meteredOverride = externalConfig.meteredOverride;
     }
 
     /**
@@ -891,7 +906,6 @@
         newInternalConfig.requirePMF = externalConfig.requirePMF;
         newInternalConfig.noInternetAccessExpected = externalConfig.noInternetAccessExpected;
         newInternalConfig.ephemeral = externalConfig.ephemeral;
-        newInternalConfig.meteredHint = externalConfig.meteredHint;
         newInternalConfig.useExternalScores = externalConfig.useExternalScores;
         newInternalConfig.shared = externalConfig.shared;
 
@@ -1910,7 +1924,7 @@
         }
 
         // Adding a new BSSID
-        ScanResult result = scanDetailCache.get(scanResult.BSSID);
+        ScanResult result = scanDetailCache.getScanResult(scanResult.BSSID);
         if (result != null) {
             // transfer the black list status
             scanResult.blackListTimestamp = result.blackListTimestamp;
@@ -2316,12 +2330,13 @@
     public List<WifiScanner.PnoSettings.PnoNetwork> retrievePnoNetworkList() {
         List<WifiScanner.PnoSettings.PnoNetwork> pnoList = new ArrayList<>();
         List<WifiConfiguration> networks = new ArrayList<>(getInternalConfiguredNetworks());
-        // Remove any permanently disabled networks.
+        // Remove any permanently or temporarily disabled networks.
         Iterator<WifiConfiguration> iter = networks.iterator();
         while (iter.hasNext()) {
             WifiConfiguration config = iter.next();
             if (config.ephemeral || config.isPasspoint()
-                    || config.getNetworkSelectionStatus().isNetworkPermanentlyDisabled()) {
+                    || config.getNetworkSelectionStatus().isNetworkPermanentlyDisabled()
+                    || config.getNetworkSelectionStatus().isNetworkTemporaryDisabled()) {
                 iter.remove();
             }
         }
@@ -2557,10 +2572,12 @@
      * @param userId The identifier of the user that stopped.
      */
     public void handleUserStop(int userId) {
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "Handling user stop for " + userId);
+        }
         if (userId == mCurrentUserId && mUserManager.isUserUnlockingOrUnlocked(mCurrentUserId)) {
             saveToStore(true);
-            clearInternalData();
-            mCurrentUserId = UserHandle.USER_SYSTEM;
+            clearInternalUserData(mCurrentUserId);
         }
     }
 
@@ -2572,6 +2589,7 @@
      *  - List of deleted ephemeral networks.
      */
     private void clearInternalData() {
+        localLog("clearInternalData: Clearing all internal data");
         mConfiguredNetworks.clear();
         mDeletedEphemeralSSIDs.clear();
         mScanDetailCaches.clear();
@@ -2590,12 +2608,16 @@
      * removed from memory.
      */
     private Set<Integer> clearInternalUserData(int userId) {
+        localLog("clearInternalUserData: Clearing user internal data for " + userId);
         Set<Integer> removedNetworkIds = new HashSet<>();
         // Remove any private networks of the old user before switching the userId.
         for (WifiConfiguration config : getInternalConfiguredNetworks()) {
             if (!config.shared && WifiConfigurationUtil.doesUidBelongToAnyProfile(
                     config.creatorUid, mUserManager.getProfiles(userId))) {
                 removedNetworkIds.add(config.networkId);
+                localLog("clearInternalUserData: removed config."
+                        + " netId=" + config.networkId
+                        + " configKey=" + config.configKey());
                 mConfiguredNetworks.remove(config.networkId);
             }
         }
@@ -2680,40 +2702,6 @@
     }
 
     /**
-     * Migrate data from legacy store files. The function performs the following operations:
-     * 1. Check if the legacy store files are present.
-     * 2. If yes, read all the data from the store files.
-     * 3. Save it to the new store files.
-     * 4. Delete the legacy store file.
-     *
-     * @return true if migration was successful or not needed (fresh install), false if it failed.
-     */
-    public boolean migrateFromLegacyStore() {
-        if (!mWifiConfigStoreLegacy.areStoresPresent()) {
-            Log.d(TAG, "Legacy store files not found. No migration needed!");
-            return true;
-        }
-        WifiConfigStoreDataLegacy storeData = mWifiConfigStoreLegacy.read();
-        Log.d(TAG, "Reading from legacy store completed");
-        loadInternalData(storeData.getConfigurations(), new ArrayList<WifiConfiguration>(),
-                storeData.getDeletedEphemeralSSIDs());
-
-        // Setup user store for the current user in case it have not setup yet, so that data
-        // owned by the current user will be backed to the user store.
-        if (mDeferredUserUnlockRead) {
-            mWifiConfigStore.setUserStore(WifiConfigStore.createUserFile(mCurrentUserId));
-            mDeferredUserUnlockRead = false;
-        }
-
-        if (!saveToStore(true)) {
-            return false;
-        }
-        mWifiConfigStoreLegacy.removeStores();
-        Log.d(TAG, "Migration from legacy store completed");
-        return true;
-    }
-
-    /**
      * Read the config store and load the in-memory lists from the store data retrieved and sends
      * out the networks changed broadcast.
      *
@@ -2727,10 +2715,7 @@
     public boolean loadFromStore() {
         if (!mWifiConfigStore.areStoresPresent()) {
             Log.d(TAG, "New store files not found. No saved networks loaded!");
-            if (!mWifiConfigStoreLegacy.areStoresPresent()) {
-                // No legacy store files either, so reset the pending store read flag.
-                mPendingStoreRead = false;
-            }
+            mPendingStoreRead = false;
             return true;
         }
         // If the user unlock comes in before we load from store, which means the user store have
@@ -2908,4 +2893,29 @@
     public void setOnSavedNetworkUpdateListener(OnSavedNetworkUpdateListener listener) {
         mListener = listener;
     }
+
+    /**
+     * Set extra failure reason for given config. Used to surface extra failure details to the UI
+     * @param netId The network ID of the config to set the extra failure reason for
+     * @param reason the WifiConfiguration.ExtraFailureReason failure code representing the most
+     *               recent failure reason
+     */
+    public void setRecentFailureAssociationStatus(int netId, int reason) {
+        WifiConfiguration config = getInternalConfiguredNetwork(netId);
+        if (config == null) {
+            return;
+        }
+        config.recentFailure.setAssociationStatus(reason);
+    }
+
+    /**
+     * @param netId The network ID of the config to clear the extra failure reason from
+     */
+    public void clearRecentFailureReason(int netId) {
+        WifiConfiguration config = getInternalConfiguredNetwork(netId);
+        if (config == null) {
+            return;
+        }
+        config.recentFailure.clear();
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiConfigStoreLegacy.java b/service/java/com/android/server/wifi/WifiConfigStoreLegacy.java
deleted file mode 100644
index 8677755..0000000
--- a/service/java/com/android/server/wifi/WifiConfigStoreLegacy.java
+++ /dev/null
@@ -1,358 +0,0 @@
-/*
- * Copyright (C) 2016 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 android.net.IpConfiguration;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiEnterpriseConfig;
-import android.os.Environment;
-import android.util.Log;
-import android.util.SparseArray;
-
-import com.android.server.net.IpConfigStore;
-import com.android.server.wifi.hotspot2.LegacyPasspointConfig;
-import com.android.server.wifi.hotspot2.LegacyPasspointConfigParser;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * This class provides the API's to load network configurations from legacy store
- * mechanism (Pre O release).
- * This class loads network configurations from:
- * 1. /data/misc/wifi/networkHistory.txt
- * 2. /data/misc/wifi/wpa_supplicant.conf
- * 3. /data/misc/wifi/ipconfig.txt
- * 4. /data/misc/wifi/PerProviderSubscription.conf
- *
- * The order of invocation of the public methods during migration is the following:
- * 1. Check if legacy stores are present using {@link #areStoresPresent()}.
- * 2. Load all the store data using {@link #read()}
- * 3. Write the store data to the new store.
- * 4. Remove all the legacy stores using {@link #removeStores()}
- *
- * NOTE: This class should only be used from WifiConfigManager and is not thread-safe!
- *
- * TODO(b/31065385): Passpoint config store data migration & deletion.
- */
-public class WifiConfigStoreLegacy {
-    /**
-     * Log tag.
-     */
-    private static final String TAG = "WifiConfigStoreLegacy";
-    /**
-     * NetworkHistory config store file path.
-     */
-    private static final File NETWORK_HISTORY_FILE =
-            new File(WifiNetworkHistory.NETWORK_HISTORY_CONFIG_FILE);
-    /**
-     * Passpoint config store file path.
-     */
-    private static final File PPS_FILE =
-            new File(Environment.getDataMiscDirectory(), "wifi/PerProviderSubscription.conf");
-    /**
-     * IpConfig config store file path.
-     */
-    private static final File IP_CONFIG_FILE =
-            new File(Environment.getDataMiscDirectory(), "wifi/ipconfig.txt");
-    /**
-     * List of external dependencies for WifiConfigManager.
-     */
-    private final WifiNetworkHistory mWifiNetworkHistory;
-    private final WifiNative mWifiNative;
-    private final IpConfigStore mIpconfigStore;
-
-    private final LegacyPasspointConfigParser mPasspointConfigParser;
-
-    WifiConfigStoreLegacy(WifiNetworkHistory wifiNetworkHistory,
-            WifiNative wifiNative, IpConfigStore ipConfigStore,
-            LegacyPasspointConfigParser passpointConfigParser) {
-        mWifiNetworkHistory = wifiNetworkHistory;
-        mWifiNative = wifiNative;
-        mIpconfigStore = ipConfigStore;
-        mPasspointConfigParser = passpointConfigParser;
-    }
-
-    /**
-     * Helper function to lookup the WifiConfiguration object from configKey to WifiConfiguration
-     * object map using the hashcode of the configKey.
-     *
-     * @param configurationMap Map of configKey to WifiConfiguration object.
-     * @param hashCode         hash code of the configKey to match.
-     * @return
-     */
-    private static WifiConfiguration lookupWifiConfigurationUsingConfigKeyHash(
-            Map<String, WifiConfiguration> configurationMap, int hashCode) {
-        for (Map.Entry<String, WifiConfiguration> entry : configurationMap.entrySet()) {
-            if (entry.getKey().hashCode() == hashCode) {
-                return entry.getValue();
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Helper function to load {@link IpConfiguration} data from the ip config store file and
-     * populate the provided configuration map.
-     *
-     * @param configurationMap Map of configKey to WifiConfiguration object.
-     */
-    private void loadFromIpConfigStore(Map<String, WifiConfiguration> configurationMap) {
-        // This is a map of the hash code of the network's configKey to the corresponding
-        // IpConfiguration.
-        SparseArray<IpConfiguration> ipConfigurations =
-                mIpconfigStore.readIpAndProxyConfigurations(IP_CONFIG_FILE.getAbsolutePath());
-        if (ipConfigurations == null || ipConfigurations.size() == 0) {
-            Log.w(TAG, "No ip configurations found in ipconfig store");
-            return;
-        }
-        for (int i = 0; i < ipConfigurations.size(); i++) {
-            int id = ipConfigurations.keyAt(i);
-            WifiConfiguration config =
-                    lookupWifiConfigurationUsingConfigKeyHash(configurationMap, id);
-            // This is the only place the map is looked up through a (dangerous) hash-value!
-            if (config == null || config.ephemeral) {
-                Log.w(TAG, "configuration found for missing network, nid=" + id
-                        + ", ignored, networks.size=" + Integer.toString(ipConfigurations.size()));
-            } else {
-                config.setIpConfiguration(ipConfigurations.valueAt(i));
-            }
-        }
-    }
-
-    /**
-     * Helper function to load {@link WifiConfiguration} data from networkHistory file and populate
-     * the provided configuration map and deleted ephemeral ssid list.
-     *
-     * @param configurationMap      Map of configKey to WifiConfiguration object.
-     * @param deletedEphemeralSSIDs Map of configKey to WifiConfiguration object.
-     */
-    private void loadFromNetworkHistory(
-            Map<String, WifiConfiguration> configurationMap, Set<String> deletedEphemeralSSIDs) {
-        // TODO: Need  to revisit the scan detail cache persistance. We're not doing it in the new
-        // config store, so ignore it here as well.
-        Map<Integer, ScanDetailCache> scanDetailCaches = new HashMap<>();
-        mWifiNetworkHistory.readNetworkHistory(
-                configurationMap, scanDetailCaches, deletedEphemeralSSIDs);
-    }
-
-    /**
-     * Helper function to load {@link WifiConfiguration} data from wpa_supplicant and populate
-     * the provided configuration map and network extras.
-     *
-     * This method needs to manually parse the wpa_supplicant.conf file to retrieve some of the
-     * password fields like psk, wep_keys. password, etc.
-     *
-     * @param configurationMap Map of configKey to WifiConfiguration object.
-     * @param networkExtras    Map of network extras parsed from wpa_supplicant.
-     */
-    private void loadFromWpaSupplicant(
-            Map<String, WifiConfiguration> configurationMap,
-            SparseArray<Map<String, String>> networkExtras) {
-        if (!mWifiNative.migrateNetworksFromSupplicant(configurationMap, networkExtras)) {
-            Log.wtf(TAG, "Failed to load wifi configurations from wpa_supplicant");
-            return;
-        }
-        if (configurationMap.isEmpty()) {
-            Log.w(TAG, "No wifi configurations found in wpa_supplicant");
-            return;
-        }
-    }
-
-    /**
-     * Helper function to update {@link WifiConfiguration} that represents a Passpoint
-     * configuration.
-     *
-     * This method will manually parse PerProviderSubscription.conf file to retrieve missing
-     * fields: provider friendly name, roaming consortium OIs, realm, IMSI.
-     *
-     * @param configurationMap Map of configKey to WifiConfiguration object.
-     * @param networkExtras    Map of network extras parsed from wpa_supplicant.
-     */
-    private void loadFromPasspointConfigStore(
-            Map<String, WifiConfiguration> configurationMap,
-            SparseArray<Map<String, String>> networkExtras) {
-        Map<String, LegacyPasspointConfig> passpointConfigMap = null;
-        try {
-            passpointConfigMap = mPasspointConfigParser.parseConfig(PPS_FILE.getAbsolutePath());
-        } catch (IOException e) {
-            Log.w(TAG, "Failed to read/parse Passpoint config file: " + e.getMessage());
-        }
-
-        List<String> entriesToBeRemoved = new ArrayList<>();
-        for (Map.Entry<String, WifiConfiguration> entry : configurationMap.entrySet()) {
-            WifiConfiguration wifiConfig = entry.getValue();
-            // Ignore non-Enterprise network since enterprise configuration is required for
-            // Passpoint.
-            if (wifiConfig.enterpriseConfig == null || wifiConfig.enterpriseConfig.getEapMethod()
-                    == WifiEnterpriseConfig.Eap.NONE) {
-                continue;
-            }
-            // Ignore configuration without FQDN.
-            Map<String, String> extras = networkExtras.get(wifiConfig.networkId);
-            if (extras == null || !extras.containsKey(SupplicantStaNetworkHal.ID_STRING_KEY_FQDN)) {
-                continue;
-            }
-            String fqdn = networkExtras.get(wifiConfig.networkId).get(
-                    SupplicantStaNetworkHal.ID_STRING_KEY_FQDN);
-
-            // Remove the configuration if failed to find the matching configuration in the
-            // Passpoint configuration file.
-            if (passpointConfigMap == null || !passpointConfigMap.containsKey(fqdn)) {
-                entriesToBeRemoved.add(entry.getKey());
-                continue;
-            }
-
-            // Update the missing Passpoint configuration fields to this WifiConfiguration.
-            LegacyPasspointConfig passpointConfig = passpointConfigMap.get(fqdn);
-            wifiConfig.isLegacyPasspointConfig = true;
-            wifiConfig.FQDN = fqdn;
-            wifiConfig.providerFriendlyName = passpointConfig.mFriendlyName;
-            if (passpointConfig.mRoamingConsortiumOis != null) {
-                wifiConfig.roamingConsortiumIds = Arrays.copyOf(
-                        passpointConfig.mRoamingConsortiumOis,
-                        passpointConfig.mRoamingConsortiumOis.length);
-            }
-            if (passpointConfig.mImsi != null) {
-                wifiConfig.enterpriseConfig.setPlmn(passpointConfig.mImsi);
-            }
-            if (passpointConfig.mRealm != null) {
-                wifiConfig.enterpriseConfig.setRealm(passpointConfig.mRealm);
-            }
-        }
-
-        // Remove any incomplete Passpoint configurations. Should never happen, in case it does
-        // remove them to avoid maintaining any invalid Passpoint configurations.
-        for (String key : entriesToBeRemoved) {
-            Log.w(TAG, "Remove incomplete Passpoint configuration: " + key);
-            configurationMap.remove(key);
-        }
-    }
-
-    /**
-     * Helper function to load from the different legacy stores:
-     * 1. Read the network configurations from wpa_supplicant using {@link WifiNative}.
-     * 2. Read the network configurations from networkHistory.txt using {@link WifiNetworkHistory}.
-     * 3. Read the Ip configurations from ipconfig.txt using {@link IpConfigStore}.
-     * 4. Read all the passpoint info from PerProviderSubscription.conf using
-     * {@link LegacyPasspointConfigParser}.
-     */
-    public WifiConfigStoreDataLegacy read() {
-        final Map<String, WifiConfiguration> configurationMap = new HashMap<>();
-        final SparseArray<Map<String, String>> networkExtras = new SparseArray<>();
-        final Set<String> deletedEphemeralSSIDs = new HashSet<>();
-
-        loadFromWpaSupplicant(configurationMap, networkExtras);
-        loadFromNetworkHistory(configurationMap, deletedEphemeralSSIDs);
-        loadFromIpConfigStore(configurationMap);
-        loadFromPasspointConfigStore(configurationMap, networkExtras);
-
-        // Now create config store data instance to be returned.
-        return new WifiConfigStoreDataLegacy(
-                new ArrayList<>(configurationMap.values()), deletedEphemeralSSIDs);
-    }
-
-    /**
-     * Function to check if the legacy store files are present and hence load from those stores and
-     * then delete them.
-     *
-     * @return true if legacy store files are present, false otherwise.
-     */
-    public boolean areStoresPresent() {
-        // We may have to keep the wpa_supplicant.conf file around. So, just use networkhistory.txt
-        // as a check to see if we have not yet migrated or not. This should be the last file
-        // that is deleted after migration.
-        File file = new File(WifiNetworkHistory.NETWORK_HISTORY_CONFIG_FILE);
-        return file.exists();
-    }
-
-    /**
-     * Method to remove all the legacy store files. This should only be invoked once all
-     * the data has been migrated to the new store file.
-     * 1. Removes all networks from wpa_supplicant and saves it to wpa_supplicant.conf
-     * 2. Deletes ipconfig.txt
-     * 3. Deletes networkHistory.txt
-     *
-     * @return true if all the store files were deleted successfully, false otherwise.
-     */
-    public boolean removeStores() {
-        // TODO(b/29352330): Delete wpa_supplicant.conf file instead.
-        // First remove all networks from wpa_supplicant and save configuration.
-        if (!mWifiNative.removeAllNetworks()) {
-            Log.e(TAG, "Removing networks from wpa_supplicant failed");
-            return false;
-        }
-
-        // Now remove the ipconfig.txt file.
-        if (!IP_CONFIG_FILE.delete()) {
-            Log.e(TAG, "Removing ipconfig.txt failed");
-            return false;
-        }
-
-        // Now finally remove network history.txt
-        if (!NETWORK_HISTORY_FILE.delete()) {
-            Log.e(TAG, "Removing networkHistory.txt failed");
-            return false;
-        }
-
-        if (!PPS_FILE.delete()) {
-            Log.e(TAG, "Removing PerProviderSubscription.conf failed");
-            return false;
-        }
-
-        Log.i(TAG, "All legacy stores removed!");
-        return true;
-    }
-
-    /**
-     * Interface used to set a masked value in the provided configuration. The masked value is
-     * retrieved by parsing the wpa_supplicant.conf file.
-     */
-    private interface MaskedWpaSupplicantFieldSetter {
-        void setValue(WifiConfiguration config, String value);
-    }
-
-    /**
-     * Class used to encapsulate all the store data retrieved from the legacy (Pre O) store files.
-     */
-    public static class WifiConfigStoreDataLegacy {
-        private List<WifiConfiguration> mConfigurations;
-        private Set<String> mDeletedEphemeralSSIDs;
-        // private List<HomeSP> mHomeSps;
-
-        WifiConfigStoreDataLegacy(List<WifiConfiguration> configurations,
-                Set<String> deletedEphemeralSSIDs) {
-            mConfigurations = configurations;
-            mDeletedEphemeralSSIDs = deletedEphemeralSSIDs;
-        }
-
-        public List<WifiConfiguration> getConfigurations() {
-            return mConfigurations;
-        }
-
-        public Set<String> getDeletedEphemeralSSIDs() {
-            return mDeletedEphemeralSSIDs;
-        }
-    }
-}
diff --git a/service/java/com/android/server/wifi/WifiConfigurationUtil.java b/service/java/com/android/server/wifi/WifiConfigurationUtil.java
index fef78aa..dadc8a4 100644
--- a/service/java/com/android/server/wifi/WifiConfigurationUtil.java
+++ b/service/java/com/android/server/wifi/WifiConfigurationUtil.java
@@ -345,7 +345,7 @@
             }
         }
         try {
-            NativeUtil.hexOrQuotedAsciiStringToBytes(psk);
+            NativeUtil.hexOrQuotedStringToBytes(psk);
         } catch (IllegalArgumentException e) {
             Log.e(TAG, "validatePsk failed: malformed string: " + psk);
             return false;
diff --git a/service/java/com/android/server/wifi/WifiConnectivityManager.java b/service/java/com/android/server/wifi/WifiConnectivityManager.java
index 344242a..458f73a 100644
--- a/service/java/com/android/server/wifi/WifiConnectivityManager.java
+++ b/service/java/com/android/server/wifi/WifiConnectivityManager.java
@@ -30,6 +30,8 @@
 import android.net.wifi.WifiScanner.ScanSettings;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.Process;
+import android.os.WorkSource;
 import android.util.LocalLog;
 import android.util.Log;
 
@@ -133,7 +135,7 @@
     private final WifiConnectivityHelper mConnectivityHelper;
     private final WifiNetworkSelector mNetworkSelector;
     private final WifiLastResortWatchdog mWifiLastResortWatchdog;
-    private final WifiNotificationController mWifiNotificationController;
+    private final OpenNetworkNotifier mOpenNetworkNotifier;
     private final WifiMetrics mWifiMetrics;
     private final AlarmManager mAlarmManager;
     private final Handler mEventHandler;
@@ -214,7 +216,7 @@
 
         @Override
         public void onAlarm() {
-            startSingleScan(mIsFullBandScan);
+            startSingleScan(mIsFullBandScan, WIFI_WORK_SOURCE);
         }
     }
 
@@ -270,7 +272,7 @@
             return true;
         } else {
             if (mWifiState == WIFI_STATE_DISCONNECTED) {
-                mWifiNotificationController.handleScanResults(
+                mOpenNetworkNotifier.handleScanResults(
                         mNetworkSelector.getFilteredScanDetailsForOpenUnsavedNetworks());
             }
             return false;
@@ -467,6 +469,10 @@
         @Override
         public void onPnoNetworkFound(ScanResult[] results) {
             for (ScanResult result: results) {
+                if (result.informationElements == null) {
+                    localLog("Skipping scan result with null information elements");
+                    continue;
+                }
                 mScanDetails.add(ScanResultUtil.toScanDetail(result));
             }
 
@@ -508,6 +514,8 @@
         }
         @Override
         public void onSavedNetworkUpdated(int networkId) {
+            // User might have changed meteredOverride, so update capabilties
+            mStateMachine.updateCapabilities();
             updatePnoScan();
         }
         @Override
@@ -536,7 +544,7 @@
             WifiScanner scanner, WifiConfigManager configManager, WifiInfo wifiInfo,
             WifiNetworkSelector networkSelector, WifiConnectivityHelper connectivityHelper,
             WifiLastResortWatchdog wifiLastResortWatchdog,
-            WifiNotificationController wifiNotificationController, WifiMetrics wifiMetrics,
+            OpenNetworkNotifier openNetworkNotifier, WifiMetrics wifiMetrics,
             Looper looper, Clock clock, LocalLog localLog, boolean enable,
             FrameworkFacade frameworkFacade,
             SavedNetworkEvaluator savedNetworkEvaluator,
@@ -550,7 +558,7 @@
         mConnectivityHelper = connectivityHelper;
         mLocalLog = localLog;
         mWifiLastResortWatchdog = wifiLastResortWatchdog;
-        mWifiNotificationController = wifiNotificationController;
+        mOpenNetworkNotifier = openNetworkNotifier;
         mWifiMetrics = wifiMetrics;
         mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
         mEventHandler = new Handler(looper);
@@ -558,9 +566,9 @@
         mConnectionAttemptTimeStamps = new LinkedList<>();
 
         mMin5GHzRssi = context.getResources().getInteger(
-                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz);
+                R.integer.config_wifi_framework_wifi_score_entry_rssi_threshold_5GHz);
         mMin24GHzRssi = context.getResources().getInteger(
-                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz);
+                R.integer.config_wifi_framework_wifi_score_entry_rssi_threshold_24GHz);
         mBand5GHzBonus = context.getResources().getInteger(
                 R.integer.config_wifi_framework_5GHz_preference_boost_factor);
         mCurrentConnectionBonus = context.getResources().getInteger(
@@ -735,7 +743,7 @@
                 localLog("connectToNetwork: Connect to " + targetAssociationId + " from "
                         + currentAssociationId);
             }
-            mStateMachine.startConnectToNetwork(candidate.networkId, targetBssid);
+            mStateMachine.startConnectToNetwork(candidate.networkId, Process.WIFI_UID, targetBssid);
         }
     }
 
@@ -788,7 +796,7 @@
             localLog("start a single scan from watchdogHandler");
 
             scheduleWatchdogTimer();
-            startSingleScan(true);
+            startSingleScan(true, WIFI_WORK_SOURCE);
         }
     }
 
@@ -817,7 +825,7 @@
         }
 
         mLastPeriodicSingleScanTimeStamp = currentTimeStamp;
-        startSingleScan(isFullBandScan);
+        startSingleScan(isFullBandScan, WIFI_WORK_SOURCE);
         schedulePeriodicScanTimer(mPeriodicSingleScanInterval);
 
         // Set up the next scan interval in an exponential backoff fashion.
@@ -844,7 +852,7 @@
     }
 
     // Start a single scan
-    private void startSingleScan(boolean isFullBandScan) {
+    private void startSingleScan(boolean isFullBandScan, WorkSource workSource) {
         if (!mWifiEnabled || !mWifiConnectivityManagerEnabled) {
             return;
         }
@@ -869,7 +877,7 @@
 
         SingleScanListener singleScanListener =
                 new SingleScanListener(isFullBandScan);
-        mScanner.startScan(settings, singleScanListener, WIFI_WORK_SOURCE);
+        mScanner.startScan(settings, singleScanListener, workSource);
     }
 
     // Start a periodic scan when screen is on
@@ -1037,7 +1045,7 @@
 
         mScreenOn = screenOn;
 
-        mWifiNotificationController.handleScreenStateChanged(screenOn);
+        mOpenNetworkNotifier.handleScreenStateChanged(screenOn);
 
         startConnectivityScan(SCAN_ON_SCHEDULE);
     }
@@ -1067,7 +1075,7 @@
         mWifiState = state;
 
         if (mWifiState == WIFI_STATE_CONNECTED) {
-            mWifiNotificationController.clearPendingNotification(false /* resetRepeatDelay */);
+            mOpenNetworkNotifier.handleWifiConnected();
         }
 
         // Reset BSSID of last connection attempt and kick off
@@ -1082,6 +1090,17 @@
     }
 
     /**
+     * Handler when a WiFi connection attempt ended.
+     *
+     * @param failureCode {@link WifiMetrics.ConnectionEvent} failure code.
+     */
+    public void handleConnectionAttemptEnded(int failureCode) {
+        if (failureCode != WifiMetrics.ConnectionEvent.FAILURE_NONE) {
+            mOpenNetworkNotifier.handleConnectionFailure();
+        }
+    }
+
+    /**
      * Handler when user toggles whether untrusted connection is allowed
      */
     public void setUntrustedConnectionAllowed(boolean allowed) {
@@ -1115,11 +1134,11 @@
     /**
      * Handler for on-demand connectivity scan
      */
-    public void forceConnectivityScan() {
-        localLog("forceConnectivityScan");
+    public void forceConnectivityScan(WorkSource workSource) {
+        localLog("forceConnectivityScan in request of " + workSource);
 
         mWaitForFullBandScanResults = true;
-        startSingleScan(true);
+        startSingleScan(true, workSource);
     }
 
     /**
@@ -1303,7 +1322,7 @@
         stopConnectivityScan();
         clearBssidBlacklist();
         resetLastPeriodicSingleScanTimeStamp();
-        mWifiNotificationController.clearPendingNotification(true /* resetRepeatDelay */);
+        mOpenNetworkNotifier.clearPendingNotification(true /* resetRepeatDelay */);
         mLastConnectionAttemptBssid = null;
         mWaitForFullBandScanResults = false;
     }
@@ -1363,6 +1382,6 @@
         pw.println("WifiConnectivityManager - Log Begin ----");
         mLocalLog.dump(fd, pw, args);
         pw.println("WifiConnectivityManager - Log End ----");
-        mWifiNotificationController.dump(fd, pw, args);
+        mOpenNetworkNotifier.dump(fd, pw, args);
     }
 }
diff --git a/service/java/com/android/server/wifi/WifiController.java b/service/java/com/android/server/wifi/WifiController.java
index c1b1861..494ce86 100644
--- a/service/java/com/android/server/wifi/WifiController.java
+++ b/service/java/com/android/server/wifi/WifiController.java
@@ -248,9 +248,9 @@
     }
 
     private void readWifiSleepPolicy() {
-        mSleepPolicy = mFacade.getIntegerSetting(mContext,
-                Settings.Global.WIFI_SLEEP_POLICY,
-                Settings.Global.WIFI_SLEEP_POLICY_NEVER);
+        // This should always set to default value because the settings menu to toggle this
+        // has been removed now.
+        mSleepPolicy = Settings.Global.WIFI_SLEEP_POLICY_NEVER;
     }
 
     private void readWifiReEnableDelay() {
diff --git a/service/java/com/android/server/wifi/WifiCountryCode.java b/service/java/com/android/server/wifi/WifiCountryCode.java
index e69fb8e..66a035f 100644
--- a/service/java/com/android/server/wifi/WifiCountryCode.java
+++ b/service/java/com/android/server/wifi/WifiCountryCode.java
@@ -83,11 +83,9 @@
     public synchronized void simCardRemoved() {
         if (DBG) Log.d(TAG, "SIM Card Removed");
         // SIM card is removed, we need to reset the country code to phone default.
-        if (mRevertCountryCodeOnCellularLoss) {
-            mTelephonyCountryCode = null;
-            if (mReady) {
-                updateCountryCode();
-            }
+        mTelephonyCountryCode = null;
+        if (mReady) {
+            updateCountryCode();
         }
     }
 
@@ -98,12 +96,9 @@
      */
     public synchronized void airplaneModeEnabled() {
         if (DBG) Log.d(TAG, "Airplane Mode Enabled");
-        mTelephonyCountryCode = null;
         // Airplane mode is enabled, we need to reset the country code to phone default.
-        if (mRevertCountryCodeOnCellularLoss) {
-            mTelephonyCountryCode = null;
-            // Country code will be set upon when wpa_supplicant starts next time.
-        }
+        // Country code will be set upon when wpa_supplicant starts next time.
+        mTelephonyCountryCode = null;
     }
 
     /**
@@ -133,8 +128,10 @@
         if (DBG) Log.d(TAG, "Receive set country code request: " + countryCode);
         // Empty country code.
         if (TextUtils.isEmpty(countryCode)) {
-            if (DBG) Log.d(TAG, "Received empty country code, reset to default country code");
-            mTelephonyCountryCode = null;
+            if (mRevertCountryCodeOnCellularLoss) {
+                if (DBG) Log.d(TAG, "Received empty country code, reset to default country code");
+                mTelephonyCountryCode = null;
+            }
         } else {
             mTelephonyCountryCode = countryCode.toUpperCase();
         }
diff --git a/service/java/com/android/server/wifi/WifiDiagnostics.java b/service/java/com/android/server/wifi/WifiDiagnostics.java
index 42078ef..921faa3 100644
--- a/service/java/com/android/server/wifi/WifiDiagnostics.java
+++ b/service/java/com/android/server/wifi/WifiDiagnostics.java
@@ -77,6 +77,8 @@
     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;
+    public static final int REPORT_REASON_WIFICOND_CRASH            = 8;
+    public static final int REPORT_REASON_HAL_CRASH                 = 9;
 
     /** number of bug reports to hold */
     public static final int MAX_BUG_REPORTS                         = 4;
diff --git a/service/java/com/android/server/wifi/WifiInjector.java b/service/java/com/android/server/wifi/WifiInjector.java
index 80daaea..4b8e682 100644
--- a/service/java/com/android/server/wifi/WifiInjector.java
+++ b/service/java/com/android/server/wifi/WifiInjector.java
@@ -45,7 +45,6 @@
 import com.android.server.net.DelayedDiskWrite;
 import com.android.server.net.IpConfigStore;
 import com.android.server.wifi.aware.WifiAwareMetrics;
-import com.android.server.wifi.hotspot2.LegacyPasspointConfigParser;
 import com.android.server.wifi.hotspot2.PasspointManager;
 import com.android.server.wifi.hotspot2.PasspointNetworkEvaluator;
 import com.android.server.wifi.hotspot2.PasspointObjectFactory;
@@ -87,7 +86,7 @@
     private final WifiStateMachine mWifiStateMachine;
     private final WifiSettingsStore mSettingsStore;
     private final WifiCertManager mCertManager;
-    private final WifiNotificationController mNotificationController;
+    private final OpenNetworkNotifier mOpenNetworkNotifier;
     private final WifiLockManager mLockManager;
     private final WifiController mWifiController;
     private final WificondControl mWificondControl;
@@ -101,9 +100,7 @@
     private final WifiMulticastLockManager mWifiMulticastLockManager;
     private final WifiConfigStore mWifiConfigStore;
     private final WifiKeyStore mWifiKeyStore;
-    private final WifiNetworkHistory mWifiNetworkHistory;
     private final IpConfigStore mIpConfigStore;
-    private final WifiConfigStoreLegacy mWifiConfigStoreLegacy;
     private final WifiConfigManager mWifiConfigManager;
     private final WifiConnectivityHelper mWifiConnectivityHelper;
     private final LocalLog mConnectivityLocalLog;
@@ -169,7 +166,8 @@
         mWifiVendorHal =
                 new WifiVendorHal(mHalDeviceManager, mWifiStateMachineHandlerThread.getLooper());
         mSupplicantStaIfaceHal = new SupplicantStaIfaceHal(mContext, mWifiMonitor);
-        mWificondControl = new WificondControl(this, mWifiMonitor);
+        mWificondControl = new WificondControl(this, mWifiMonitor,
+                new CarrierNetworkConfig(mContext));
         mWifiNative = new WifiNative(SystemProperties.get("wifi.interface", "wlan0"),
                 mWifiVendorHal, mSupplicantStaIfaceHal, mWificondControl);
         mWifiP2pMonitor = new WifiP2pMonitor(this);
@@ -194,16 +192,12 @@
                 WifiConfigStore.createSharedFile());
         // Legacy config store
         DelayedDiskWrite writer = new DelayedDiskWrite();
-        mWifiNetworkHistory = new WifiNetworkHistory(mContext, writer);
         mIpConfigStore = new IpConfigStore(writer);
-        mWifiConfigStoreLegacy = new WifiConfigStoreLegacy(
-                mWifiNetworkHistory, mWifiNative, mIpConfigStore,
-                new LegacyPasspointConfigParser());
         // Config Manager
         mWifiConfigManager = new WifiConfigManager(mContext, mClock,
                 UserManager.get(mContext), TelephonyManager.from(mContext),
-                mWifiKeyStore, mWifiConfigStore, mWifiConfigStoreLegacy, mWifiPermissionsUtil,
-                mWifiPermissionsWrapper, new NetworkListStoreData(),
+                mWifiKeyStore, mWifiConfigStore, mWifiPermissionsUtil,
+                mWifiPermissionsWrapper, new NetworkListStoreData(mContext),
                 new DeletedEphemeralSsidsStoreData());
         mWifiMetrics.setWifiConfigManager(mWifiConfigManager);
         mWifiConnectivityHelper = new WifiConnectivityHelper(mWifiNative);
@@ -230,8 +224,11 @@
                 this, mBackupManagerProxy, mCountryCode, mWifiNative,
                 new WrongPasswordNotifier(mContext, mFrameworkFacade));
         mCertManager = new WifiCertManager(mContext);
-        mNotificationController = new WifiNotificationController(mContext,
-                mWifiStateMachineHandlerThread.getLooper(), mFrameworkFacade, null);
+        mOpenNetworkNotifier = new OpenNetworkNotifier(mContext,
+                mWifiStateMachineHandlerThread.getLooper(), mFrameworkFacade, mClock, mWifiMetrics,
+                mWifiConfigManager, mWifiConfigStore, mWifiStateMachine,
+                new OpenNetworkRecommender(),
+                new ConnectToNetworkNotificationBuilder(mContext, mFrameworkFacade));
         mLockManager = new WifiLockManager(mContext, BatteryStatsService.getService());
         mWifiController = new WifiController(mContext, mWifiStateMachine, mSettingsStore,
                 mLockManager, mWifiServiceHandlerThread.getLooper(), mFrameworkFacade);
@@ -430,7 +427,7 @@
                                                                boolean hasConnectionRequests) {
         return new WifiConnectivityManager(mContext, mWifiStateMachine, getWifiScanner(),
                 mWifiConfigManager, wifiInfo, mWifiNetworkSelector, mWifiConnectivityHelper,
-                mWifiLastResortWatchdog, mNotificationController, mWifiMetrics,
+                mWifiLastResortWatchdog, mOpenNetworkNotifier, mWifiMetrics,
                 mWifiStateMachineHandlerThread.getLooper(), mClock, mConnectivityLocalLog,
                 hasConnectionRequests, mFrameworkFacade, mSavedNetworkEvaluator,
                 mScoredNetworkEvaluator, mPasspointNetworkEvaluator);
diff --git a/service/java/com/android/server/wifi/WifiMetrics.java b/service/java/com/android/server/wifi/WifiMetrics.java
index 7dfdb86..a8ad08c 100644
--- a/service/java/com/android/server/wifi/WifiMetrics.java
+++ b/service/java/com/android/server/wifi/WifiMetrics.java
@@ -31,12 +31,17 @@
 import android.util.Pair;
 import android.util.SparseIntArray;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.wifi.aware.WifiAwareMetrics;
+import com.android.server.wifi.hotspot2.ANQPNetworkKey;
 import com.android.server.wifi.hotspot2.NetworkDetail;
 import com.android.server.wifi.hotspot2.PasspointManager;
 import com.android.server.wifi.hotspot2.PasspointMatch;
 import com.android.server.wifi.hotspot2.PasspointProvider;
+import com.android.server.wifi.hotspot2.Utils;
 import com.android.server.wifi.nano.WifiMetricsProto;
+import com.android.server.wifi.nano.WifiMetricsProto.ConnectToNetworkNotificationAndActionCount;
+import com.android.server.wifi.nano.WifiMetricsProto.PnoScanMetrics;
 import com.android.server.wifi.nano.WifiMetricsProto.StaEvent;
 import com.android.server.wifi.nano.WifiMetricsProto.StaEvent.ConfigInfo;
 import com.android.server.wifi.util.InformationElementUtil;
@@ -47,9 +52,11 @@
 import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.Calendar;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -74,6 +81,8 @@
     public static final long TIMEOUT_RSSI_DELTA_MILLIS =  3000;
     private static final int MIN_WIFI_SCORE = 0;
     private static final int MAX_WIFI_SCORE = NetworkAgent.WIFI_BASE_SCORE;
+    @VisibleForTesting
+    static final int LOW_WIFI_SCORE = 50; // Mobile data score
     private final Object mLock = new Object();
     private static final int MAX_CONNECTION_EVENTS = 256;
     // Largest bucket in the NumConnectableNetworkCount histogram,
@@ -82,10 +91,15 @@
     public static final int MAX_CONNECTABLE_BSSID_NETWORK_BUCKET = 50;
     public static final int MAX_TOTAL_SCAN_RESULT_SSIDS_BUCKET = 100;
     public static final int MAX_TOTAL_SCAN_RESULTS_BUCKET = 250;
+    public static final int MAX_TOTAL_PASSPOINT_APS_BUCKET = 50;
+    public static final int MAX_TOTAL_PASSPOINT_UNIQUE_ESS_BUCKET = 20;
+    public static final int MAX_PASSPOINT_APS_PER_UNIQUE_ESS_BUCKET = 50;
+    private static final int CONNECT_TO_NETWORK_NOTIFICATION_ACTION_KEY_MULTIPLIER = 1000;
     private Clock mClock;
     private boolean mScreenOn;
     private int mWifiState;
     private WifiAwareMetrics mWifiAwareMetrics;
+    private final PnoScanMetrics mPnoScanMetrics = new PnoScanMetrics();
     private Handler mHandler;
     private WifiConfigManager mWifiConfigManager;
     private WifiNetworkSelector mWifiNetworkSelector;
@@ -148,6 +162,22 @@
     private final SparseIntArray mAvailableSavedPasspointProviderBssidsInScanHistogram =
             new SparseIntArray();
 
+    /** Mapping of "Connect to Network" notifications to counts. */
+    private final SparseIntArray mConnectToNetworkNotificationCount = new SparseIntArray();
+    /** Mapping of "Connect to Network" notification user actions to counts. */
+    private final SparseIntArray mConnectToNetworkNotificationActionCount = new SparseIntArray();
+    private int mOpenNetworkRecommenderBlacklistSize = 0;
+    private boolean mIsWifiNetworksAvailableNotificationOn = false;
+    private int mNumOpenNetworkConnectMessageFailedToSend = 0;
+    private int mNumOpenNetworkRecommendationUpdates = 0;
+
+    private final SparseIntArray mObservedHotspotR1ApInScanHistogram = new SparseIntArray();
+    private final SparseIntArray mObservedHotspotR2ApInScanHistogram = new SparseIntArray();
+    private final SparseIntArray mObservedHotspotR1EssInScanHistogram = new SparseIntArray();
+    private final SparseIntArray mObservedHotspotR2EssInScanHistogram = new SparseIntArray();
+    private final SparseIntArray mObservedHotspotR1ApsPerEssInScanHistogram = new SparseIntArray();
+    private final SparseIntArray mObservedHotspotR2ApsPerEssInScanHistogram = new SparseIntArray();
+
     class RouterFingerPrint {
         private WifiMetricsProto.RouterFingerPrint mRouterFingerPrintProto;
         RouterFingerPrint() {
@@ -407,6 +437,51 @@
         mPasspointManager = passpointManager;
     }
 
+    /**
+     * Increment total number of attempts to start a pno scan
+     */
+    public void incrementPnoScanStartAttempCount() {
+        synchronized (mLock) {
+            mPnoScanMetrics.numPnoScanAttempts++;
+        }
+    }
+
+    /**
+     * Increment total number of attempts with pno scan failed
+     */
+    public void incrementPnoScanFailedCount() {
+        synchronized (mLock) {
+            mPnoScanMetrics.numPnoScanFailed++;
+        }
+    }
+
+    /**
+     * Increment number of pno scans started successfully over offload
+     */
+    public void incrementPnoScanStartedOverOffloadCount() {
+        synchronized (mLock) {
+            mPnoScanMetrics.numPnoScanStartedOverOffload++;
+        }
+    }
+
+    /**
+     * Increment number of pno scans failed over offload
+     */
+    public void incrementPnoScanFailedOverOffloadCount() {
+        synchronized (mLock) {
+            mPnoScanMetrics.numPnoScanFailedOverOffload++;
+        }
+    }
+
+    /**
+     * Increment number of times pno scan found a result
+     */
+    public void incrementPnoFoundNetworkEventCount() {
+        synchronized (mLock) {
+            mPnoScanMetrics.numPnoFoundNetworkEvents++;
+        }
+    }
+
     // Values used for indexing SystemStateEntries
     private static final int SCREEN_ON = 1;
     private static final int SCREEN_OFF = 0;
@@ -983,10 +1058,14 @@
         }
     }
 
+    private boolean mWifiWins = false; // Based on scores, use wifi instead of mobile data?
+
     /**
      * Increments occurence of a particular wifi score calculated
      * in WifiScoreReport by current connected network. Scores are bounded
-     * within  [MIN_WIFI_SCORE, MAX_WIFI_SCORE] to limit size of SparseArray
+     * within  [MIN_WIFI_SCORE, MAX_WIFI_SCORE] to limit size of SparseArray.
+     *
+     * Also records events when the current score breaches significant thresholds.
      */
     public void incrementWifiScoreCount(int score) {
         if (score < MIN_WIFI_SCORE || score > MAX_WIFI_SCORE) {
@@ -995,6 +1074,20 @@
         synchronized (mLock) {
             int count = mWifiScoreCounts.get(score);
             mWifiScoreCounts.put(score, count + 1);
+
+            boolean wifiWins = mWifiWins;
+            if (mWifiWins && score < LOW_WIFI_SCORE) {
+                wifiWins = false;
+            } else if (!mWifiWins && score > LOW_WIFI_SCORE) {
+                wifiWins = true;
+            }
+            mLastScore = score;
+            if (wifiWins != mWifiWins) {
+                mWifiWins = wifiWins;
+                StaEvent event = new StaEvent();
+                event.type = StaEvent.TYPE_SCORE_BREACH;
+                addStaEvent(event);
+            }
         }
     }
 
@@ -1134,6 +1227,10 @@
             Set<PasspointProvider> savedPasspointProviderProfiles =
                     new HashSet<PasspointProvider>();
             int savedPasspointProviderBssids = 0;
+            int passpointR1Aps = 0;
+            int passpointR2Aps = 0;
+            Map<ANQPNetworkKey, Integer> passpointR1UniqueEss = new HashMap<>();
+            Map<ANQPNetworkKey, Integer> passpointR2UniqueEss = new HashMap<>();
             for (ScanDetail scanDetail : scanDetails) {
                 NetworkDetail networkDetail = scanDetail.getNetworkDetail();
                 ScanResult scanResult = scanDetail.getScanResult();
@@ -1147,6 +1244,36 @@
                     providerMatch =
                             mPasspointManager.matchProvider(scanResult);
                     passpointProvider = providerMatch != null ? providerMatch.first : null;
+
+                    if (networkDetail.getHSRelease() == NetworkDetail.HSRelease.R1) {
+                        passpointR1Aps++;
+                    } else if (networkDetail.getHSRelease() == NetworkDetail.HSRelease.R2) {
+                        passpointR2Aps++;
+                    }
+
+                    long bssid = 0;
+                    boolean validBssid = false;
+                    try {
+                        bssid = Utils.parseMac(scanResult.BSSID);
+                        validBssid = true;
+                    } catch (IllegalArgumentException e) {
+                        Log.e(TAG,
+                                "Invalid BSSID provided in the scan result: " + scanResult.BSSID);
+                    }
+                    if (validBssid) {
+                        ANQPNetworkKey uniqueEss = ANQPNetworkKey.buildKey(scanResult.SSID, bssid,
+                                scanResult.hessid, networkDetail.getAnqpDomainID());
+                        if (networkDetail.getHSRelease() == NetworkDetail.HSRelease.R1) {
+                            Integer countObj = passpointR1UniqueEss.get(uniqueEss);
+                            int count = countObj == null ? 0 : countObj;
+                            passpointR1UniqueEss.put(uniqueEss, count + 1);
+                        } else if (networkDetail.getHSRelease() == NetworkDetail.HSRelease.R2) {
+                            Integer countObj = passpointR2UniqueEss.get(uniqueEss);
+                            int count = countObj == null ? 0 : countObj;
+                            passpointR2UniqueEss.put(uniqueEss, count + 1);
+                        }
+                    }
+
                 }
                 ssids.add(matchInfo);
                 bssids++;
@@ -1187,6 +1314,67 @@
                     savedPasspointProviderProfiles.size());
             incrementBssid(mAvailableSavedPasspointProviderBssidsInScanHistogram,
                     savedPasspointProviderBssids);
+            incrementTotalPasspointAps(mObservedHotspotR1ApInScanHistogram, passpointR1Aps);
+            incrementTotalPasspointAps(mObservedHotspotR2ApInScanHistogram, passpointR2Aps);
+            incrementTotalUniquePasspointEss(mObservedHotspotR1EssInScanHistogram,
+                    passpointR1UniqueEss.size());
+            incrementTotalUniquePasspointEss(mObservedHotspotR2EssInScanHistogram,
+                    passpointR2UniqueEss.size());
+            for (Integer count : passpointR1UniqueEss.values()) {
+                incrementPasspointPerUniqueEss(mObservedHotspotR1ApsPerEssInScanHistogram, count);
+            }
+            for (Integer count : passpointR2UniqueEss.values()) {
+                incrementPasspointPerUniqueEss(mObservedHotspotR2ApsPerEssInScanHistogram, count);
+            }
+        }
+    }
+
+    /** Increments the occurence of a "Connect to Network" notification. */
+    public void incrementConnectToNetworkNotification(int notificationType) {
+        synchronized (mLock) {
+            int count = mConnectToNetworkNotificationCount.get(notificationType);
+            mConnectToNetworkNotificationCount.put(notificationType, count + 1);
+        }
+    }
+
+    /** Increments the occurence of an "Connect to Network" notification user action. */
+    public void incrementConnectToNetworkNotificationAction(int notificationType, int actionType) {
+        synchronized (mLock) {
+            int key = notificationType * CONNECT_TO_NETWORK_NOTIFICATION_ACTION_KEY_MULTIPLIER
+                    + actionType;
+            int count = mConnectToNetworkNotificationActionCount.get(key);
+            mConnectToNetworkNotificationActionCount.put(key, count + 1);
+        }
+    }
+
+    /**
+     * Sets the number of SSIDs blacklisted from recommendation by the open network notification
+     * recommender.
+     */
+    public void setOpenNetworkRecommenderBlacklistSize(int size) {
+        synchronized (mLock) {
+            mOpenNetworkRecommenderBlacklistSize = size;
+        }
+    }
+
+    /** Sets if the available network notification feature is enabled. */
+    public void setIsWifiNetworksAvailableNotificationEnabled(boolean enabled) {
+        synchronized (mLock) {
+            mIsWifiNetworksAvailableNotificationOn = enabled;
+        }
+    }
+
+    /** Increments the occurence of connection attempts that were initiated unsuccessfully */
+    public void incrementNumOpenNetworkRecommendationUpdates() {
+        synchronized (mLock) {
+            mNumOpenNetworkRecommendationUpdates++;
+        }
+    }
+
+    /** Increments the occurence of connection attempts that were initiated unsuccessfully */
+    public void incrementNumOpenNetworkConnectMessageFailedToSend() {
+        synchronized (mLock) {
+            mNumOpenNetworkConnectMessageFailedToSend++;
         }
     }
 
@@ -1388,8 +1576,8 @@
                 pw.println("mWifiLogProto.numWifiOnFailureDueToWificond="
                         + mWifiLogProto.numWifiOnFailureDueToWificond);
                 pw.println("StaEventList:");
-                for (StaEvent event : mStaEventList) {
-                    pw.println(staEventToString(event));
+                for (StaEventWithTime event : mStaEventList) {
+                    pw.println(event);
                 }
 
                 pw.println("mWifiLogProto.numPasspointProviders="
@@ -1430,6 +1618,43 @@
                         + mWifiLogProto.fullBandAllSingleScanListenerResults);
                 pw.println("mWifiAwareMetrics:");
                 mWifiAwareMetrics.dump(fd, pw, args);
+
+                pw.println("mPnoScanMetrics.numPnoScanAttempts="
+                        + mPnoScanMetrics.numPnoScanAttempts);
+                pw.println("mPnoScanMetrics.numPnoScanFailed="
+                        + mPnoScanMetrics.numPnoScanFailed);
+                pw.println("mPnoScanMetrics.numPnoScanStartedOverOffload="
+                        + mPnoScanMetrics.numPnoScanStartedOverOffload);
+                pw.println("mPnoScanMetrics.numPnoScanFailedOverOffload="
+                        + mPnoScanMetrics.numPnoScanFailedOverOffload);
+                pw.println("mPnoScanMetrics.numPnoFoundNetworkEvents="
+                        + mPnoScanMetrics.numPnoFoundNetworkEvents);
+
+                pw.println("mWifiLogProto.connectToNetworkNotificationCount="
+                        + mConnectToNetworkNotificationCount.toString());
+                pw.println("mWifiLogProto.connectToNetworkNotificationActionCount="
+                        + mConnectToNetworkNotificationActionCount.toString());
+                pw.println("mWifiLogProto.openNetworkRecommenderBlacklistSize="
+                        + mOpenNetworkRecommenderBlacklistSize);
+                pw.println("mWifiLogProto.isWifiNetworksAvailableNotificationOn="
+                        + mIsWifiNetworksAvailableNotificationOn);
+                pw.println("mWifiLogProto.numOpenNetworkRecommendationUpdates="
+                        + mNumOpenNetworkRecommendationUpdates);
+                pw.println("mWifiLogProto.numOpenNetworkConnectMessageFailedToSend="
+                        + mNumOpenNetworkConnectMessageFailedToSend);
+
+                pw.println("mWifiLogProto.observedHotspotR1ApInScanHistogram="
+                        + mObservedHotspotR1ApInScanHistogram);
+                pw.println("mWifiLogProto.observedHotspotR2ApInScanHistogram="
+                        + mObservedHotspotR2ApInScanHistogram);
+                pw.println("mWifiLogProto.observedHotspotR1EssInScanHistogram="
+                        + mObservedHotspotR1EssInScanHistogram);
+                pw.println("mWifiLogProto.observedHotspotR2EssInScanHistogram="
+                        + mObservedHotspotR2EssInScanHistogram);
+                pw.println("mWifiLogProto.observedHotspotR1ApsPerEssInScanHistogram="
+                        + mObservedHotspotR1ApsPerEssInScanHistogram);
+                pw.println("mWifiLogProto.observedHotspotR2ApsPerEssInScanHistogram="
+                        + mObservedHotspotR2ApsPerEssInScanHistogram);
             }
         }
     }
@@ -1605,6 +1830,14 @@
                 mWifiLogProto.softApReturnCode[sapCode].count =
                         mSoftApManagerReturnCodeCounts.valueAt(sapCode);
             }
+
+            /**
+             * Convert StaEventList to array of StaEvents
+             */
+            mWifiLogProto.staEventList = new StaEvent[mStaEventList.size()];
+            for (int i = 0; i < mStaEventList.size(); i++) {
+                mWifiLogProto.staEventList[i] = mStaEventList.get(i).staEvent;
+            }
             mWifiLogProto.totalSsidsInScanHistogram =
                     makeNumConnectableNetworksBucketArray(mTotalSsidsInScanHistogram);
             mWifiLogProto.totalBssidsInScanHistogram =
@@ -1629,8 +1862,71 @@
             mWifiLogProto.availableSavedPasspointProviderBssidsInScanHistogram =
                     makeNumConnectableNetworksBucketArray(
                     mAvailableSavedPasspointProviderBssidsInScanHistogram);
-            mWifiLogProto.staEventList = mStaEventList.toArray(mWifiLogProto.staEventList);
             mWifiLogProto.wifiAwareLog = mWifiAwareMetrics.consolidateProto();
+
+            mWifiLogProto.pnoScanMetrics = mPnoScanMetrics;
+
+            /**
+             * Convert the SparseIntArray of "Connect to Network" notification types and counts to
+             * proto's repeated IntKeyVal array.
+             */
+            ConnectToNetworkNotificationAndActionCount[] notificationCountArray =
+                    new ConnectToNetworkNotificationAndActionCount[
+                            mConnectToNetworkNotificationCount.size()];
+            for (int i = 0; i < mConnectToNetworkNotificationCount.size(); i++) {
+                ConnectToNetworkNotificationAndActionCount keyVal =
+                        new ConnectToNetworkNotificationAndActionCount();
+                keyVal.notification = mConnectToNetworkNotificationCount.keyAt(i);
+                keyVal.recommender =
+                        ConnectToNetworkNotificationAndActionCount.RECOMMENDER_OPEN;
+                keyVal.count = mConnectToNetworkNotificationCount.valueAt(i);
+                notificationCountArray[i] = keyVal;
+            }
+            mWifiLogProto.connectToNetworkNotificationCount = notificationCountArray;
+
+            /**
+             * Convert the SparseIntArray of "Connect to Network" notification types and counts to
+             * proto's repeated IntKeyVal array.
+             */
+            ConnectToNetworkNotificationAndActionCount[] notificationActionCountArray =
+                    new ConnectToNetworkNotificationAndActionCount[
+                            mConnectToNetworkNotificationActionCount.size()];
+            for (int i = 0; i < mConnectToNetworkNotificationActionCount.size(); i++) {
+                ConnectToNetworkNotificationAndActionCount keyVal =
+                        new ConnectToNetworkNotificationAndActionCount();
+                int key = mConnectToNetworkNotificationActionCount.keyAt(i);
+                keyVal.notification = key / CONNECT_TO_NETWORK_NOTIFICATION_ACTION_KEY_MULTIPLIER;
+                keyVal.action = key % CONNECT_TO_NETWORK_NOTIFICATION_ACTION_KEY_MULTIPLIER;
+                keyVal.recommender =
+                        ConnectToNetworkNotificationAndActionCount.RECOMMENDER_OPEN;
+                keyVal.count = mConnectToNetworkNotificationActionCount.valueAt(i);
+                notificationActionCountArray[i] = keyVal;
+            }
+            mWifiLogProto.connectToNetworkNotificationActionCount = notificationActionCountArray;
+
+            mWifiLogProto.openNetworkRecommenderBlacklistSize =
+                    mOpenNetworkRecommenderBlacklistSize;
+            mWifiLogProto.isWifiNetworksAvailableNotificationOn =
+                    mIsWifiNetworksAvailableNotificationOn;
+            mWifiLogProto.numOpenNetworkRecommendationUpdates =
+                    mNumOpenNetworkRecommendationUpdates;
+            mWifiLogProto.numOpenNetworkConnectMessageFailedToSend =
+                    mNumOpenNetworkConnectMessageFailedToSend;
+
+            mWifiLogProto.observedHotspotR1ApsInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(mObservedHotspotR1ApInScanHistogram);
+            mWifiLogProto.observedHotspotR2ApsInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(mObservedHotspotR2ApInScanHistogram);
+            mWifiLogProto.observedHotspotR1EssInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(mObservedHotspotR1EssInScanHistogram);
+            mWifiLogProto.observedHotspotR2EssInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(mObservedHotspotR2EssInScanHistogram);
+            mWifiLogProto.observedHotspotR1ApsPerEssInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(
+                            mObservedHotspotR1ApsPerEssInScanHistogram);
+            mWifiLogProto.observedHotspotR2ApsPerEssInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(
+                            mObservedHotspotR2ApsPerEssInScanHistogram);
         }
     }
 
@@ -1649,7 +1945,8 @@
     }
 
     /**
-     * Clear all WifiMetrics, except for currentConnectionEvent.
+     * Clear all WifiMetrics, except for currentConnectionEvent and Open Network Notification
+     * feature enabled state, blacklist size.
      */
     private void clear() {
         synchronized (mLock) {
@@ -1679,6 +1976,17 @@
             mAvailableOpenOrSavedBssidsInScanHistogram.clear();
             mAvailableSavedPasspointProviderProfilesInScanHistogram.clear();
             mAvailableSavedPasspointProviderBssidsInScanHistogram.clear();
+            mPnoScanMetrics.clear();
+            mConnectToNetworkNotificationCount.clear();
+            mConnectToNetworkNotificationActionCount.clear();
+            mNumOpenNetworkRecommendationUpdates = 0;
+            mNumOpenNetworkConnectMessageFailedToSend = 0;
+            mObservedHotspotR1ApInScanHistogram.clear();
+            mObservedHotspotR2ApInScanHistogram.clear();
+            mObservedHotspotR1EssInScanHistogram.clear();
+            mObservedHotspotR2EssInScanHistogram.clear();
+            mObservedHotspotR1ApsPerEssInScanHistogram.clear();
+            mObservedHotspotR2ApsPerEssInScanHistogram.clear();
         }
     }
 
@@ -1697,6 +2005,7 @@
     public void setWifiState(int wifiState) {
         synchronized (mLock) {
             mWifiState = wifiState;
+            mWifiWins = (wifiState == WifiMetricsProto.WifiLog.WIFI_ASSOCIATED);
         }
     }
 
@@ -1802,6 +2111,7 @@
             case StaEvent.TYPE_CONNECT_NETWORK:
             case StaEvent.TYPE_NETWORK_AGENT_VALID_NETWORK:
             case StaEvent.TYPE_FRAMEWORK_DISCONNECT:
+            case StaEvent.TYPE_SCORE_BREACH:
                 break;
             default:
                 Log.e(TAG, "Unknown StaEvent:" + type);
@@ -1822,11 +2132,13 @@
         staEvent.lastFreq = mLastPollFreq;
         staEvent.lastLinkSpeed = mLastPollLinkSpeed;
         staEvent.supplicantStateChangesBitmask = mSupplicantStateChangeBitmask;
+        staEvent.lastScore = mLastScore;
         mSupplicantStateChangeBitmask = 0;
         mLastPollRssi = -127;
         mLastPollFreq = -1;
         mLastPollLinkSpeed = -1;
-        mStaEventList.add(staEvent);
+        mLastScore = -1;
+        mStaEventList.add(new StaEventWithTime(staEvent, mClock.getWallClockMillis()));
         // Prune StaEventList if it gets too long
         if (mStaEventList.size() > MAX_STA_EVENTS) mStaEventList.remove();
     }
@@ -1903,7 +2215,7 @@
 
     private static String supplicantStateChangesBitmaskToString(int mask) {
         StringBuilder sb = new StringBuilder();
-        sb.append("SUPPLICANT_STATE_CHANGE_EVENTS: {");
+        sb.append("supplicantStateChangeEvents: {");
         if ((mask & (1 << StaEvent.STATE_DISCONNECTED)) > 0) sb.append(" DISCONNECTED");
         if ((mask & (1 << StaEvent.STATE_INTERFACE_DISABLED)) > 0) sb.append(" INTERFACE_DISABLED");
         if ((mask & (1 << StaEvent.STATE_INACTIVE)) > 0) sb.append(" INACTIVE");
@@ -1928,61 +2240,62 @@
     public static String staEventToString(StaEvent event) {
         if (event == null) return "<NULL>";
         StringBuilder sb = new StringBuilder();
-        Long time = event.startTimeMillis;
-        sb.append(String.format("%9d ", time.longValue())).append(" ");
         switch (event.type) {
             case StaEvent.TYPE_ASSOCIATION_REJECTION_EVENT:
-                sb.append("ASSOCIATION_REJECTION_EVENT:")
+                sb.append("ASSOCIATION_REJECTION_EVENT")
                         .append(" timedOut=").append(event.associationTimedOut)
                         .append(" status=").append(event.status).append(":")
                         .append(ISupplicantStaIfaceCallback.StatusCode.toString(event.status));
                 break;
             case StaEvent.TYPE_AUTHENTICATION_FAILURE_EVENT:
-                sb.append("AUTHENTICATION_FAILURE_EVENT: reason=").append(event.authFailureReason)
+                sb.append("AUTHENTICATION_FAILURE_EVENT reason=").append(event.authFailureReason)
                         .append(":").append(authFailureReasonToString(event.authFailureReason));
                 break;
             case StaEvent.TYPE_NETWORK_CONNECTION_EVENT:
-                sb.append("NETWORK_CONNECTION_EVENT:");
+                sb.append("NETWORK_CONNECTION_EVENT");
                 break;
             case StaEvent.TYPE_NETWORK_DISCONNECTION_EVENT:
-                sb.append("NETWORK_DISCONNECTION_EVENT:")
+                sb.append("NETWORK_DISCONNECTION_EVENT")
                         .append(" local_gen=").append(event.localGen)
                         .append(" reason=").append(event.reason).append(":")
                         .append(ISupplicantStaIfaceCallback.ReasonCode.toString(
                                 (event.reason >= 0 ? event.reason : -1 * event.reason)));
                 break;
             case StaEvent.TYPE_CMD_ASSOCIATED_BSSID:
-                sb.append("CMD_ASSOCIATED_BSSID:");
+                sb.append("CMD_ASSOCIATED_BSSID");
                 break;
             case StaEvent.TYPE_CMD_IP_CONFIGURATION_SUCCESSFUL:
-                sb.append("CMD_IP_CONFIGURATION_SUCCESSFUL:");
+                sb.append("CMD_IP_CONFIGURATION_SUCCESSFUL");
                 break;
             case StaEvent.TYPE_CMD_IP_CONFIGURATION_LOST:
-                sb.append("CMD_IP_CONFIGURATION_LOST:");
+                sb.append("CMD_IP_CONFIGURATION_LOST");
                 break;
             case StaEvent.TYPE_CMD_IP_REACHABILITY_LOST:
-                sb.append("CMD_IP_REACHABILITY_LOST:");
+                sb.append("CMD_IP_REACHABILITY_LOST");
                 break;
             case StaEvent.TYPE_CMD_TARGET_BSSID:
-                sb.append("CMD_TARGET_BSSID:");
+                sb.append("CMD_TARGET_BSSID");
                 break;
             case StaEvent.TYPE_CMD_START_CONNECT:
-                sb.append("CMD_START_CONNECT:");
+                sb.append("CMD_START_CONNECT");
                 break;
             case StaEvent.TYPE_CMD_START_ROAM:
-                sb.append("CMD_START_ROAM:");
+                sb.append("CMD_START_ROAM");
                 break;
             case StaEvent.TYPE_CONNECT_NETWORK:
-                sb.append("CONNECT_NETWORK:");
+                sb.append("CONNECT_NETWORK");
                 break;
             case StaEvent.TYPE_NETWORK_AGENT_VALID_NETWORK:
-                sb.append("NETWORK_AGENT_VALID_NETWORK:");
+                sb.append("NETWORK_AGENT_VALID_NETWORK");
                 break;
             case StaEvent.TYPE_FRAMEWORK_DISCONNECT:
-                sb.append("FRAMEWORK_DISCONNECT:")
+                sb.append("FRAMEWORK_DISCONNECT")
                         .append(" reason=")
                         .append(frameworkDisconnectReasonToString(event.frameworkDisconnectReason));
                 break;
+            case StaEvent.TYPE_SCORE_BREACH:
+                sb.append("SCORE_BREACH");
+                break;
             default:
                 sb.append("UNKNOWN " + event.type + ":");
                 break;
@@ -1990,12 +2303,13 @@
         if (event.lastRssi != -127) sb.append(" lastRssi=").append(event.lastRssi);
         if (event.lastFreq != -1) sb.append(" lastFreq=").append(event.lastFreq);
         if (event.lastLinkSpeed != -1) sb.append(" lastLinkSpeed=").append(event.lastLinkSpeed);
+        if (event.lastScore != -1) sb.append(" lastScore=").append(event.lastScore);
         if (event.supplicantStateChangesBitmask != 0) {
-            sb.append("\n             ").append(supplicantStateChangesBitmaskToString(
+            sb.append(", ").append(supplicantStateChangesBitmaskToString(
                     event.supplicantStateChangesBitmask));
         }
         if (event.configInfo != null) {
-            sb.append("\n             ").append(configInfoToString(event.configInfo));
+            sb.append(", ").append(configInfoToString(event.configInfo));
         }
 
         return sb.toString();
@@ -2052,11 +2366,12 @@
         return sb.toString();
     }
 
-    public static final int MAX_STA_EVENTS = 512;
-    private LinkedList<StaEvent> mStaEventList = new LinkedList<StaEvent>();
+    public static final int MAX_STA_EVENTS = 768;
+    private LinkedList<StaEventWithTime> mStaEventList = new LinkedList<StaEventWithTime>();
     private int mLastPollRssi = -127;
     private int mLastPollLinkSpeed = -1;
     private int mLastPollFreq = -1;
+    private int mLastScore = -1;
 
     /**
      * Converts the first 31 bits of a BitSet to a little endian int
@@ -2081,8 +2396,40 @@
     private void incrementTotalScanSsids(SparseIntArray sia, int element) {
         increment(sia, Math.min(element, MAX_TOTAL_SCAN_RESULT_SSIDS_BUCKET));
     }
+    private void incrementTotalPasspointAps(SparseIntArray sia, int element) {
+        increment(sia, Math.min(element, MAX_TOTAL_PASSPOINT_APS_BUCKET));
+    }
+    private void incrementTotalUniquePasspointEss(SparseIntArray sia, int element) {
+        increment(sia, Math.min(element, MAX_TOTAL_PASSPOINT_UNIQUE_ESS_BUCKET));
+    }
+    private void incrementPasspointPerUniqueEss(SparseIntArray sia, int element) {
+        increment(sia, Math.min(element, MAX_PASSPOINT_APS_PER_UNIQUE_ESS_BUCKET));
+    }
     private void increment(SparseIntArray sia, int element) {
         int count = sia.get(element);
         sia.put(element, count + 1);
     }
+
+    private static class StaEventWithTime {
+        public StaEvent staEvent;
+        public long wallClockMillis;
+
+        StaEventWithTime(StaEvent event, long wallClockMillis) {
+            staEvent = event;
+            this.wallClockMillis = wallClockMillis;
+        }
+
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            Calendar c = Calendar.getInstance();
+            c.setTimeInMillis(wallClockMillis);
+            if (wallClockMillis != 0) {
+                sb.append(String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c));
+            } else {
+                sb.append("                  ");
+            }
+            sb.append(" ").append(staEventToString(staEvent));
+            return sb.toString();
+        }
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiNative.java b/service/java/com/android/server/wifi/WifiNative.java
index 973b659..5b12a36 100644
--- a/service/java/com/android/server/wifi/WifiNative.java
+++ b/service/java/com/android/server/wifi/WifiNative.java
@@ -227,7 +227,16 @@
      * Returns an empty ArrayList on failure.
      */
     public ArrayList<ScanDetail> getScanResults() {
-        return mWificondControl.getScanResults();
+        return mWificondControl.getScanResults(WificondControl.SCAN_TYPE_SINGLE_SCAN);
+    }
+
+    /**
+     * Fetch the latest scan result from kernel via wificond.
+     * @return Returns an ArrayList of ScanDetail.
+     * Returns an empty ArrayList on failure.
+     */
+    public ArrayList<ScanDetail> getPnoScanResults() {
+        return mWificondControl.getScanResults(WificondControl.SCAN_TYPE_PNO_SCAN);
     }
 
     /**
@@ -1536,26 +1545,6 @@
     }
 
     /**
-     * Set the PNO settings & the network list in HAL to start PNO.
-     * @param settings PNO settings and network list.
-     * @param eventHandler Handler to receive notifications back during PNO scan.
-     * @return true if success, false otherwise
-     */
-    public boolean setPnoList(PnoSettings settings, PnoEventHandler eventHandler) {
-        Log.e(mTAG, "setPnoList not supported");
-        return false;
-    }
-
-    /**
-     * Reset the PNO settings in HAL to stop PNO.
-     * @return true if success, false otherwise
-     */
-    public boolean resetPnoList() {
-        Log.e(mTAG, "resetPnoList not supported");
-        return false;
-    }
-
-    /**
      * Start sending the specified keep alive packets periodically.
      *
      * @param slot Integer used to identify each request.
diff --git a/service/java/com/android/server/wifi/WifiNetworkHistory.java b/service/java/com/android/server/wifi/WifiNetworkHistory.java
deleted file mode 100644
index f8457cd..0000000
--- a/service/java/com/android/server/wifi/WifiNetworkHistory.java
+++ /dev/null
@@ -1,634 +0,0 @@
-/*
- * Copyright (C) 2016 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 android.content.Context;
-
-import android.net.wifi.ScanResult;
-
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
-import android.net.wifi.WifiSsid;
-import android.os.Environment;
-import android.os.Process;
-import android.text.TextUtils;
-
-import android.util.Log;
-
-import com.android.server.net.DelayedDiskWrite;
-
-import java.io.BufferedInputStream;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.EOFException;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.text.DateFormat;
-import java.util.BitSet;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * Provides an API to read and write the network history from WifiConfigurations to file
- * This is largely separate and extra to the supplicant config file.
- */
-public class WifiNetworkHistory {
-    public static final String TAG = "WifiNetworkHistory";
-    private static final boolean DBG = true;
-    private static final boolean VDBG = true;
-    static final String NETWORK_HISTORY_CONFIG_FILE = Environment.getDataDirectory()
-            + "/misc/wifi/networkHistory.txt";
-    /* Network History Keys */
-    private static final String SSID_KEY = "SSID";
-    static final String CONFIG_KEY = "CONFIG";
-    private static final String CONFIG_BSSID_KEY = "CONFIG_BSSID";
-    private static final String CHOICE_KEY = "CHOICE";
-    private static final String CHOICE_TIME_KEY = "CHOICE_TIME";
-    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 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 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";
-    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 FQDN_KEY = "FQDN";
-    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 NO_INTERNET_ACCESS_EXPECTED_KEY = "NO_INTERNET_ACCESS_EXPECTED";
-    private static final String EPHEMERAL_KEY = "EPHEMERAL";
-    private static final String USE_EXTERNAL_SCORES_KEY = "USE_EXTERNAL_SCORES";
-    private static final String METERED_HINT_KEY = "METERED_HINT";
-    private static final String NUM_ASSOCIATION_KEY = "NUM_ASSOCIATION";
-    private static final String DELETED_EPHEMERAL_KEY = "DELETED_EPHEMERAL";
-    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";
-    static final String SHARED_KEY = "SHARED";
-    private static final String NETWORK_SELECTION_STATUS_KEY = "NETWORK_SELECTION_STATUS";
-    private static final String NETWORK_SELECTION_DISABLE_REASON_KEY =
-            "NETWORK_SELECTION_DISABLE_REASON";
-    private static final String HAS_EVER_CONNECTED_KEY = "HAS_EVER_CONNECTED";
-
-    private static final String SEPARATOR = ":  ";
-    private static final String NL = "\n";
-
-    protected final DelayedDiskWrite mWriter;
-    Context mContext;
-    /*
-     * Lost config list, whenever we read a config from networkHistory.txt that was not in
-     * wpa_supplicant.conf
-     */
-    HashSet<String> mLostConfigsDbg = new HashSet<String>();
-
-    public WifiNetworkHistory(Context c, DelayedDiskWrite writer) {
-        mContext = c;
-        mWriter = writer;
-    }
-
-    /**
-     * Write network history to file, for configured networks
-     *
-     * @param networks List of ConfiguredNetworks to write to NetworkHistory
-     */
-    public void writeKnownNetworkHistory(final List<WifiConfiguration> networks,
-            final ConcurrentHashMap<Integer, ScanDetailCache> scanDetailCaches,
-            final Set<String> deletedEphemeralSSIDs) {
-
-        /* Make a copy */
-        //final List<WifiConfiguration> networks = new ArrayList<WifiConfiguration>();
-
-        //for (WifiConfiguration config : mConfiguredNetworks.valuesForAllUsers()) {
-        //    networks.add(new WifiConfiguration(config));
-        //}
-
-        mWriter.write(NETWORK_HISTORY_CONFIG_FILE, new DelayedDiskWrite.Writer() {
-            public void onWriteCalled(DataOutputStream out) throws IOException {
-                for (WifiConfiguration config : networks) {
-                    //loge("onWriteCalled write SSID: " + config.SSID);
-                   /* if (config.getLinkProperties() != null)
-                        loge(" lp " + config.getLinkProperties().toString());
-                    else
-                        loge("attempt config w/o lp");
-                    */
-                    NetworkSelectionStatus status = config.getNetworkSelectionStatus();
-                    if (VDBG) {
-                        int numlink = 0;
-                        if (config.linkedConfigurations != null) {
-                            numlink = config.linkedConfigurations.size();
-                        }
-                        String disableTime;
-                        if (config.getNetworkSelectionStatus().isNetworkEnabled()) {
-                            disableTime = "";
-                        } else {
-                            disableTime = "Disable time: " + DateFormat.getInstance().format(
-                                    config.getNetworkSelectionStatus().getDisableTime());
-                        }
-                        logd("saving network history: " + config.configKey()  + " gw: "
-                                + config.defaultGwMacAddress + " Network Selection-status: "
-                                + status.getNetworkStatusString()
-                                + disableTime + " ephemeral=" + config.ephemeral
-                                + " choice:" + status.getConnectChoice()
-                                + " link:" + numlink
-                                + " status:" + config.status
-                                + " nid:" + config.networkId
-                                + " hasEverConnected: " + status.getHasEverConnected());
-                    }
-
-                    if (!isValid(config)) {
-                        continue;
-                    }
-
-                    if (config.SSID == null) {
-                        if (VDBG) {
-                            logv("writeKnownNetworkHistory trying to write config with null SSID");
-                        }
-                        continue;
-                    }
-                    if (VDBG) {
-                        logv("writeKnownNetworkHistory write config " + config.configKey());
-                    }
-                    out.writeUTF(CONFIG_KEY + SEPARATOR + config.configKey() + NL);
-
-                    if (config.SSID != null) {
-                        out.writeUTF(SSID_KEY + SEPARATOR + config.SSID + NL);
-                    }
-                    if (config.BSSID != null) {
-                        out.writeUTF(CONFIG_BSSID_KEY + SEPARATOR + config.BSSID + NL);
-                    } else {
-                        out.writeUTF(CONFIG_BSSID_KEY + SEPARATOR + "null" + NL);
-                    }
-                    if (config.FQDN != null) {
-                        out.writeUTF(FQDN_KEY + SEPARATOR + config.FQDN + NL);
-                    }
-
-                    out.writeUTF(PRIORITY_KEY + SEPARATOR + Integer.toString(config.priority) + 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(NO_INTERNET_ACCESS_EXPECTED_KEY + SEPARATOR +
-                            Boolean.toString(config.noInternetAccessExpected) + NL);
-                    out.writeUTF(EPHEMERAL_KEY + SEPARATOR
-                            + Boolean.toString(config.ephemeral) + NL);
-                    out.writeUTF(METERED_HINT_KEY + SEPARATOR
-                            + Boolean.toString(config.meteredHint) + NL);
-                    out.writeUTF(USE_EXTERNAL_SCORES_KEY + SEPARATOR
-                            + Boolean.toString(config.useExternalScores) + 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(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(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);
-                    out.writeUTF(SHARED_KEY + SEPARATOR + Boolean.toString(config.shared) + NL);
-                    String allowedKeyManagementString =
-                            makeString(config.allowedKeyManagement,
-                                    WifiConfiguration.KeyMgmt.strings);
-                    out.writeUTF(AUTH_KEY + SEPARATOR
-                            + allowedKeyManagementString + NL);
-                    out.writeUTF(NETWORK_SELECTION_STATUS_KEY + SEPARATOR
-                            + status.getNetworkSelectionStatus() + NL);
-                    out.writeUTF(NETWORK_SELECTION_DISABLE_REASON_KEY + SEPARATOR
-                            + status.getNetworkSelectionDisableReason() + NL);
-
-                    if (status.getConnectChoice() != null) {
-                        out.writeUTF(CHOICE_KEY + SEPARATOR + status.getConnectChoice() + NL);
-                        out.writeUTF(CHOICE_TIME_KEY + SEPARATOR
-                                + status.getConnectChoiceTimestamp() + NL);
-                    }
-
-                    if (config.linkedConfigurations != null) {
-                        log("writeKnownNetworkHistory write linked "
-                                + config.linkedConfigurations.size());
-
-                        for (String key : config.linkedConfigurations.keySet()) {
-                            out.writeUTF(LINK_KEY + SEPARATOR + key + NL);
-                        }
-                    }
-
-                    String macAddress = config.defaultGwMacAddress;
-                    if (macAddress != null) {
-                        out.writeUTF(DEFAULT_GW_KEY + SEPARATOR + macAddress + NL);
-                    }
-
-                    if (getScanDetailCache(config, scanDetailCaches) != null) {
-                        for (ScanDetail scanDetail : getScanDetailCache(config,
-                                    scanDetailCaches).values()) {
-                            ScanResult result = scanDetail.getScanResult();
-                            out.writeUTF(BSSID_KEY + SEPARATOR
-                                    + result.BSSID + NL);
-                            out.writeUTF(FREQ_KEY + SEPARATOR
-                                    + Integer.toString(result.frequency) + NL);
-
-                            out.writeUTF(RSSI_KEY + SEPARATOR
-                                    + Integer.toString(result.level) + NL);
-
-                            out.writeUTF(BSSID_KEY_END + NL);
-                        }
-                    }
-                    if (config.lastFailure != null) {
-                        out.writeUTF(FAILURE_KEY + SEPARATOR + config.lastFailure + NL);
-                    }
-                    out.writeUTF(HAS_EVER_CONNECTED_KEY + SEPARATOR
-                            + Boolean.toString(status.getHasEverConnected()) + NL);
-                    out.writeUTF(NL);
-                    // Add extra blank lines for clarity
-                    out.writeUTF(NL);
-                    out.writeUTF(NL);
-                }
-                if (deletedEphemeralSSIDs != null && deletedEphemeralSSIDs.size() > 0) {
-                    for (String ssid : deletedEphemeralSSIDs) {
-                        out.writeUTF(DELETED_EPHEMERAL_KEY);
-                        out.writeUTF(ssid);
-                        out.writeUTF(NL);
-                    }
-                }
-            }
-        });
-    }
-
-    /**
-     * Adds information stored in networkHistory.txt to the given configs. The configs are provided
-     * as a mapping from configKey to WifiConfiguration, because the WifiConfigurations themselves
-     * do not contain sufficient information to compute their configKeys until after the information
-     * that is stored in networkHistory.txt has been added to them.
-     *
-     * @param configs mapping from configKey to a WifiConfiguration that contains the information
-     *         information read from wpa_supplicant.conf
-     */
-    public void readNetworkHistory(Map<String, WifiConfiguration> configs,
-            Map<Integer, ScanDetailCache> scanDetailCaches,
-            Set<String> deletedEphemeralSSIDs) {
-
-        try (DataInputStream in =
-                     new DataInputStream(new BufferedInputStream(
-                             new FileInputStream(NETWORK_HISTORY_CONFIG_FILE)))) {
-
-            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) {
-                String line = in.readUTF();
-                if (line == null) {
-                    break;
-                }
-                int colon = line.indexOf(':');
-                if (colon < 0) {
-                    continue;
-                }
-
-                String key = line.substring(0, colon).trim();
-                String value = line.substring(colon + 1).trim();
-
-                if (key.equals(CONFIG_KEY)) {
-                    config = configs.get(value);
-
-                    // skip reading that configuration data
-                    // since we don't have a corresponding network ID
-                    if (config == null) {
-                        Log.e(TAG, "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) {
-                    NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
-                    switch (key) {
-                        case SSID_KEY:
-                            if (config.isPasspoint()) {
-                                break;
-                            }
-                            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 CONFIG_BSSID_KEY:
-                            config.BSSID = value.equals("null") ? null : value;
-                            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 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 NO_INTERNET_ACCESS_EXPECTED_KEY:
-                            config.noInternetAccessExpected = 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 METERED_HINT_KEY:
-                            config.meteredHint = Boolean.parseBoolean(value);
-                            break;
-                        case USE_EXTERNAL_SCORES_KEY:
-                            config.useExternalScores = Boolean.parseBoolean(value);
-                            break;
-                        case CREATOR_UID_KEY:
-                            config.creatorUid = 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 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 NETWORK_SELECTION_STATUS_KEY:
-                            int networkStatusValue = Integer.parseInt(value);
-                            // Reset temporarily disabled network status
-                            if (networkStatusValue ==
-                                    NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED) {
-                                networkStatusValue =
-                                        NetworkSelectionStatus.NETWORK_SELECTION_ENABLED;
-                            }
-                            networkStatus.setNetworkSelectionStatus(networkStatusValue);
-                            break;
-                        case NETWORK_SELECTION_DISABLE_REASON_KEY:
-                            networkStatus.setNetworkSelectionDisableReason(Integer.parseInt(value));
-                            break;
-                        case CHOICE_KEY:
-                            networkStatus.setConnectChoice(value);
-                            break;
-                        case CHOICE_TIME_KEY:
-                            networkStatus.setConnectChoiceTimestamp(Long.parseLong(value));
-                            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 = "";
-                            break;
-                        case RSSI_KEY:
-                            rssi = 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 (getScanDetailCache(config, scanDetailCaches) != null) {
-                                    WifiSsid wssid = WifiSsid.createFromAsciiEncoded(ssid);
-                                    ScanDetail scanDetail = new ScanDetail(wssid, bssid,
-                                            caps, rssi, freq, (long) 0, seen);
-                                    getScanDetailCache(config, scanDetailCaches).put(scanDetail);
-                                }
-                            }
-                            break;
-                        case DELETED_EPHEMERAL_KEY:
-                            if (!TextUtils.isEmpty(value)) {
-                                deletedEphemeralSSIDs.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;
-                        case SHARED_KEY:
-                            config.shared = Boolean.parseBoolean(value);
-                            break;
-                        case HAS_EVER_CONNECTED_KEY:
-                            networkStatus.setHasEverConnected(Boolean.parseBoolean(value));
-                            break;
-                    }
-                }
-            }
-        } catch (EOFException e) {
-            // do nothing
-        } catch (FileNotFoundException e) {
-            Log.i(TAG, "readNetworkHistory: no config file, " + e);
-        } catch (NumberFormatException e) {
-            Log.e(TAG, "readNetworkHistory: failed to parse, " + e, e);
-        } catch (IOException e) {
-            Log.e(TAG, "readNetworkHistory: failed to read, " + e, e);
-        }
-    }
-
-    /**
-     * Ported this out of WifiServiceImpl, I have no idea what it's doing
-     * <TODO> figure out what/why this is doing
-     * <TODO> Port it into WifiConfiguration, then remove all the silly business from ServiceImpl
-     */
-    public boolean isValid(WifiConfiguration config) {
-        if (config.allowedKeyManagement == null) {
-            return false;
-        }
-        if (config.allowedKeyManagement.cardinality() > 1) {
-            if (config.allowedKeyManagement.cardinality() != 2) {
-                return false;
-            }
-            if (!config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP)) {
-                return false;
-            }
-            if ((!config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X))
-                    && (!config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK))) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    private static String makeString(BitSet set, String[] strings) {
-        StringBuffer buf = new StringBuffer();
-        int nextSetBit = -1;
-
-        /* Make sure all set bits are in [0, strings.length) to avoid
-         * going out of bounds on strings.  (Shouldn't happen, but...) */
-        set = set.get(0, strings.length);
-
-        while ((nextSetBit = set.nextSetBit(nextSetBit + 1)) != -1) {
-            buf.append(strings[nextSetBit].replace('_', '-')).append(' ');
-        }
-
-        // remove trailing space
-        if (set.cardinality() > 0) {
-            buf.setLength(buf.length() - 1);
-        }
-
-        return buf.toString();
-    }
-
-    protected void logv(String s) {
-        Log.v(TAG, s);
-    }
-    protected void logd(String s) {
-        Log.d(TAG, s);
-    }
-    protected void log(String s) {
-        Log.d(TAG, s);
-    }
-    protected void loge(String s) {
-        loge(s, false);
-    }
-    protected void loge(String s, boolean stack) {
-        if (stack) {
-            Log.e(TAG, s + " 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, s);
-        }
-    }
-
-    private ScanDetailCache getScanDetailCache(WifiConfiguration config,
-            Map<Integer, ScanDetailCache> scanDetailCaches) {
-        if (config == null || scanDetailCaches == null) return null;
-        ScanDetailCache cache = scanDetailCaches.get(config.networkId);
-        if (cache == null && config.networkId != WifiConfiguration.INVALID_NETWORK_ID) {
-            cache =
-                    new ScanDetailCache(
-                            config, WifiConfigManager.SCAN_CACHE_ENTRIES_MAX_SIZE,
-                            WifiConfigManager.SCAN_CACHE_ENTRIES_TRIM_SIZE);
-            scanDetailCaches.put(config.networkId, cache);
-        }
-        return cache;
-    }
-}
diff --git a/service/java/com/android/server/wifi/WifiNetworkSelector.java b/service/java/com/android/server/wifi/WifiNetworkSelector.java
index 89068a8..071fc07 100644
--- a/service/java/com/android/server/wifi/WifiNetworkSelector.java
+++ b/service/java/com/android/server/wifi/WifiNetworkSelector.java
@@ -575,15 +575,15 @@
         mLocalLog = localLog;
 
         mThresholdQualifiedRssi24 = context.getResources().getInteger(
-                            R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_24GHz);
+                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_24GHz);
         mThresholdQualifiedRssi5 = context.getResources().getInteger(
-                            R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_5GHz);
+                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_5GHz);
         mThresholdMinimumRssi24 = context.getResources().getInteger(
-                            R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz);
+                R.integer.config_wifi_framework_wifi_score_entry_rssi_threshold_24GHz);
         mThresholdMinimumRssi5 = context.getResources().getInteger(
-                            R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz);
+                R.integer.config_wifi_framework_wifi_score_entry_rssi_threshold_5GHz);
         mEnableAutoJoinWhenAssociated = context.getResources().getBoolean(
-                            R.bool.config_wifi_framework_enable_associated_network_selection);
+                R.bool.config_wifi_framework_enable_associated_network_selection);
         mStayOnNetworkMinimumTxRate = context.getResources().getInteger(
                 R.integer.config_wifi_framework_min_tx_rate_for_staying_on_network);
         mStayOnNetworkMinimumRxRate = context.getResources().getInteger(
diff --git a/service/java/com/android/server/wifi/WifiNotificationController.java b/service/java/com/android/server/wifi/WifiNotificationController.java
deleted file mode 100644
index 797fd43..0000000
--- a/service/java/com/android/server/wifi/WifiNotificationController.java
+++ /dev/null
@@ -1,252 +0,0 @@
-/*
- * Copyright (C) 2013 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 android.annotation.NonNull;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.TaskStackBuilder;
-import android.content.Context;
-import android.content.Intent;
-import android.database.ContentObserver;
-import android.net.wifi.WifiManager;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-
-import com.android.internal.notification.SystemNotificationChannels;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.List;
-
-/**
- * Takes care of handling the "open wi-fi network available" notification
- * @hide
- */
-public class WifiNotificationController {
-    /**
-     * The icon to show in the 'available networks' notification. This will also
-     * be the ID of the Notification given to the NotificationManager.
-     */
-    private static final int ICON_NETWORKS_AVAILABLE =
-            com.android.internal.R.drawable.stat_notify_wifi_in_range;
-    /**
-     * When a notification is shown, we wait this amount before possibly showing it again.
-     */
-    private final long NOTIFICATION_REPEAT_DELAY_MS;
-
-    /** Whether the user has set the setting to show the 'available networks' notification. */
-    private boolean mSettingEnabled;
-
-    /**
-     * Observes the user setting to keep {@link #mSettingEnabled} in sync.
-     */
-    private NotificationEnabledSettingObserver mNotificationEnabledSettingObserver;
-
-    /**
-     * The {@link System#currentTimeMillis()} must be at least this value for us
-     * to show the notification again.
-     */
-    private long mNotificationRepeatTime;
-    /**
-     * The Notification object given to the NotificationManager.
-     */
-    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
-     * will still be true. We only guarantee if this is false, then the
-     * notification is not showing.
-     */
-    private boolean mNotificationShown;
-    /** Whether the screen is on or not. */
-    private boolean mScreenOn;
-
-    private final Context mContext;
-    private FrameworkFacade mFrameworkFacade;
-
-    WifiNotificationController(Context context,
-                               Looper looper,
-                               FrameworkFacade framework,
-                               Notification.Builder builder) {
-        mContext = context;
-        mFrameworkFacade = framework;
-        mNotificationBuilder = builder;
-
-        mScreenOn = false;
-
-        // Setting is in seconds
-        NOTIFICATION_REPEAT_DELAY_MS = mFrameworkFacade.getIntegerSetting(context,
-                Settings.Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, 900) * 1000L;
-        mNotificationEnabledSettingObserver = new NotificationEnabledSettingObserver(
-                new Handler(looper));
-        mNotificationEnabledSettingObserver.register();
-    }
-
-    /**
-     * Clears the pending notification. This is called by {@link WifiConnectivityManager} on stop.
-     *
-     * @param resetRepeatDelay resets the time delay for repeated notification if true.
-     */
-    public void clearPendingNotification(boolean resetRepeatDelay) {
-        if (resetRepeatDelay) {
-            mNotificationRepeatTime = 0;
-        }
-        setNotificationVisible(false, 0, false, 0);
-    }
-
-    private boolean isControllerEnabled() {
-        return mSettingEnabled && !UserManager.get(mContext)
-                .hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, UserHandle.CURRENT);
-    }
-
-    /**
-     * If there are open networks, attempt to post an open network notification.
-     *
-     * @param availableNetworks Available networks from
-     * {@link WifiNetworkSelector.NetworkEvaluator#getFilteredScanDetailsForOpenUnsavedNetworks()}.
-     */
-    public void handleScanResults(@NonNull List<ScanDetail> availableNetworks) {
-        if (!isControllerEnabled()) {
-            clearPendingNotification(true /* resetRepeatDelay */);
-            return;
-        }
-        if (availableNetworks.isEmpty()) {
-            clearPendingNotification(false /* resetRepeatDelay */);
-            return;
-        }
-
-        // Do not show or update the notification if screen is off. We want to avoid a race that
-        // could occur between a user picking a network in settings and a network candidate picked
-        // through network selection, which will happen because screen on triggers a new
-        // connectivity scan.
-        if (mNotificationShown || !mScreenOn) {
-            return;
-        }
-
-        setNotificationVisible(true, availableNetworks.size(), false, 0);
-    }
-
-    /** Handles screen state changes. */
-    public void handleScreenStateChanged(boolean screenOn) {
-        mScreenOn = screenOn;
-    }
-
-    /**
-     * Display or don't display a notification that there are open Wi-Fi networks.
-     * @param visible {@code true} if notification should be visible, {@code false} otherwise
-     * @param numNetworks the number networks seen
-     * @param force {@code true} to force notification to be shown/not-shown,
-     * even if it is already shown/not-shown.
-     * @param delay time in milliseconds after which the notification should be made
-     * visible or invisible.
-     */
-    private void setNotificationVisible(boolean visible, int numNetworks, boolean force,
-            int delay) {
-
-        // Since we use auto cancel on the notification, when the
-        // mNetworksAvailableNotificationShown is true, the notification may
-        // have actually been canceled.  However, when it is false we know
-        // for sure that it is not being shown (it will not be shown any other
-        // place than here)
-
-        // If it should be hidden and it is already hidden, then noop
-        if (!visible && !mNotificationShown && !force) {
-            return;
-        }
-
-        NotificationManager notificationManager = (NotificationManager) mContext
-                .getSystemService(Context.NOTIFICATION_SERVICE);
-
-        Message message;
-        if (visible) {
-
-            // Not enough time has passed to show the notification again
-            if (System.currentTimeMillis() < mNotificationRepeatTime) {
-                return;
-            }
-
-            if (mNotificationBuilder == null) {
-                // Cache the Notification builder object.
-                mNotificationBuilder = new Notification.Builder(mContext,
-                        SystemNotificationChannels.NETWORK_AVAILABLE)
-                        .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);
-            mNotificationBuilder.setTicker(title);
-            mNotificationBuilder.setContentTitle(title);
-            mNotificationBuilder.setContentText(details);
-
-            mNotificationRepeatTime = System.currentTimeMillis() + NOTIFICATION_REPEAT_DELAY_MS;
-
-            notificationManager.notifyAsUser(null, ICON_NETWORKS_AVAILABLE,
-                    mNotificationBuilder.build(), UserHandle.ALL);
-        } else {
-            notificationManager.cancelAsUser(null, ICON_NETWORKS_AVAILABLE, UserHandle.ALL);
-        }
-
-        mNotificationShown = visible;
-    }
-
-    /** Dump ONA controller state. */
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("WifiNotificationController: ");
-        pw.println("mSettingEnabled " + mSettingEnabled);
-        pw.println("mNotificationRepeatTime " + mNotificationRepeatTime);
-        pw.println("mNotificationShown " + mNotificationShown);
-    }
-
-    private class NotificationEnabledSettingObserver extends ContentObserver {
-        NotificationEnabledSettingObserver(Handler handler) {
-            super(handler);
-        }
-
-        public void register() {
-            mFrameworkFacade.registerContentObserver(mContext, Settings.Global.getUriFor(
-                    Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON), true, this);
-            mSettingEnabled = getValue();
-        }
-
-        @Override
-        public void onChange(boolean selfChange) {
-            super.onChange(selfChange);
-            mSettingEnabled = getValue();
-            clearPendingNotification(true /* resetRepeatDelay */);
-        }
-
-        private boolean getValue() {
-            return mFrameworkFacade.getIntegerSetting(mContext,
-                    Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1) == 1;
-        }
-    }
-}
diff --git a/service/java/com/android/server/wifi/WifiServiceImpl.java b/service/java/com/android/server/wifi/WifiServiceImpl.java
index d6faf9b..c83f6c6 100644
--- a/service/java/com/android/server/wifi/WifiServiceImpl.java
+++ b/service/java/com/android/server/wifi/WifiServiceImpl.java
@@ -591,7 +591,7 @@
     public void startScan(ScanSettings settings, WorkSource workSource, String packageName) {
         enforceChangePermission();
 
-        mLog.trace("startScan uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("startScan uid=%").c(Binder.getCallingUid()).flush();
         // Check and throttle background apps for wifi scan.
         if (isRequestFromBackground(packageName)) {
             long lastScanMs = mLastScanTimestamps.getOrDefault(packageName, 0L);
@@ -683,7 +683,7 @@
     @Override
     public String getCurrentNetworkWpsNfcConfigurationToken() {
         enforceConnectivityInternalPermission();
-        mLog.trace("getCurrentNetworkWpsNfcConfigurationToken uid=%")
+        mLog.info("getCurrentNetworkWpsNfcConfigurationToken uid=%")
                 .c(Binder.getCallingUid()).flush();
         // TODO Add private logging for netId b/33807876
         return mWifiStateMachine.syncGetCurrentNetworkWpsNfcConfigurationToken();
@@ -713,6 +713,11 @@
         }
     }
 
+    private boolean checkNetworkSettingsPermission(int pid, int uid) {
+        return mContext.checkPermission(android.Manifest.permission.NETWORK_SETTINGS, pid, uid)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
     private void enforceNetworkSettingsPermission() {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.NETWORK_SETTINGS,
                 "WifiService");
@@ -777,15 +782,15 @@
         enforceChangePermission();
         Slog.d(TAG, "setWifiEnabled: " + enable + " pid=" + Binder.getCallingPid()
                     + ", uid=" + Binder.getCallingUid() + ", package=" + packageName);
-        mLog.trace("setWifiEnabled package=% uid=% enable=%").c(packageName)
+        mLog.info("setWifiEnabled package=% uid=% enable=%").c(packageName)
                 .c(Binder.getCallingUid()).c(enable).flush();
 
-        boolean isFromSettings =
-                mWifiPermissionsUtil.checkNetworkSettingsPermission(Binder.getCallingUid());
+        boolean isFromSettings = checkNetworkSettingsPermission(
+                Binder.getCallingPid(), Binder.getCallingUid());
 
         // If Airplane mode is enabled, only Settings is allowed to toggle Wifi
         if (mSettingsStore.isAirplaneModeOn() && !isFromSettings) {
-            mLog.trace("setWifiEnabled in Airplane mode: only Settings can enable wifi").flush();
+            mLog.info("setWifiEnabled in Airplane mode: only Settings can enable wifi").flush();
             return false;
         }
 
@@ -794,7 +799,7 @@
                 mWifiStateMachine.syncGetWifiApState() != WifiManager.WIFI_AP_STATE_DISABLED;
 
         if (apEnabled && !isFromSettings) {
-            mLog.trace("setWifiEnabled SoftAp not disabled: only Settings can enable wifi").flush();
+            mLog.info("setWifiEnabled SoftAp not disabled: only Settings can enable wifi").flush();
             return false;
         }
 
@@ -847,7 +852,7 @@
     @Override
     public int getWifiEnabledState() {
         enforceAccessPermission();
-        mLog.trace("getWifiEnabledState uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("getWifiEnabledState uid=%").c(Binder.getCallingUid()).flush();
         return mWifiStateMachine.syncGetWifiState();
     }
 
@@ -862,7 +867,7 @@
         enforceChangePermission();
         mWifiPermissionsUtil.enforceTetherChangePermission(mContext);
 
-        mLog.trace("setWifiApEnabled uid=% enable=%").c(Binder.getCallingUid()).c(enabled).flush();
+        mLog.info("setWifiApEnabled uid=% enable=%").c(Binder.getCallingUid()).c(enabled).flush();
 
         if (mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING)) {
             throw new SecurityException("DISALLOW_CONFIG_TETHERING is enabled for this user.");
@@ -888,7 +893,7 @@
     @Override
     public int getWifiApEnabledState() {
         enforceAccessPermission();
-        mLog.trace("getWifiApEnabledState uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("getWifiApEnabledState uid=%").c(Binder.getCallingUid()).flush();
         return mWifiStateMachine.syncGetWifiApState();
     }
 
@@ -963,7 +968,7 @@
                     }
                     break;
                 default:
-                    mLog.trace("updateInterfaceIpStateInternal: unknown mode %").c(mode).flush();
+                    mLog.warn("updateInterfaceIpStateInternal: unknown mode %").c(mode).flush();
             }
         }
     }
@@ -979,7 +984,7 @@
         // NETWORK_STACK is a signature only permission.
         enforceNetworkStackPermission();
 
-        mLog.trace("startSoftAp uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("startSoftAp uid=%").c(Binder.getCallingUid()).flush();
 
         synchronized (mLocalOnlyHotspotRequests) {
             // If a tethering request comes in while we have LOHS running (or requested), call stop
@@ -1023,7 +1028,7 @@
         // only permitted callers are allowed to this point - they must have gone through
         // connectivity service since this method is protected with the NETWORK_STACK PERMISSION
 
-        mLog.trace("stopSoftAp uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("stopSoftAp uid=%").c(Binder.getCallingUid()).flush();
 
         synchronized (mLocalOnlyHotspotRequests) {
             // If a tethering request comes in while we have LOHS running (or requested), call stop
@@ -1212,17 +1217,17 @@
                 return LocalOnlyHotspotCallback.ERROR_INCOMPATIBLE_MODE;
             }
         } catch (RemoteException e) {
-            mLog.trace("RemoteException during isAppForeground when calling startLOHS");
+            mLog.warn("RemoteException during isAppForeground when calling startLOHS");
             return LocalOnlyHotspotCallback.ERROR_INCOMPATIBLE_MODE;
         }
 
-        mLog.trace("startLocalOnlyHotspot uid=% pid=%").c(uid).c(pid).flush();
+        mLog.info("startLocalOnlyHotspot uid=% pid=%").c(uid).c(pid).flush();
 
         synchronized (mLocalOnlyHotspotRequests) {
             // check if we are currently tethering
             if (mIfaceIpModes.contains(WifiManager.IFACE_IP_MODE_TETHERED)) {
                 // Tethering is enabled, cannot start LocalOnlyHotspot
-                mLog.trace("Cannot start localOnlyHotspot when WiFi Tethering is active.");
+                mLog.info("Cannot start localOnlyHotspot when WiFi Tethering is active.");
                 return LocalOnlyHotspotCallback.ERROR_INCOMPATIBLE_MODE;
             }
 
@@ -1272,7 +1277,7 @@
         final int uid = Binder.getCallingUid();
         final int pid = Binder.getCallingPid();
 
-        mLog.trace("stopLocalOnlyHotspot uid=% pid=%").c(uid).c(pid).flush();
+        mLog.info("stopLocalOnlyHotspot uid=% pid=%").c(uid).c(pid).flush();
 
         synchronized (mLocalOnlyHotspotRequests) {
             // was the caller already registered?  check request tracker - return false if not
@@ -1360,7 +1365,7 @@
             throw new SecurityException("App not allowed to read or update stored WiFi Ap config "
                     + "(uid = " + uid + ")");
         }
-        mLog.trace("getWifiApConfiguration uid=%").c(uid).flush();
+        mLog.info("getWifiApConfiguration uid=%").c(uid).flush();
         return mWifiStateMachine.syncGetWifiApConfiguration();
     }
 
@@ -1379,7 +1384,7 @@
             throw new SecurityException("App not allowed to read or update stored WiFi AP config "
                     + "(uid = " + uid + ")");
         }
-        mLog.trace("setWifiApConfiguration uid=%").c(uid).flush();
+        mLog.info("setWifiApConfiguration uid=%").c(uid).flush();
         if (wifiConfig == null)
             return;
         if (isValid(wifiConfig)) {
@@ -1395,7 +1400,7 @@
     @Override
     public boolean isScanAlwaysAvailable() {
         enforceAccessPermission();
-        mLog.trace("isScanAlwaysAvailable uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("isScanAlwaysAvailable uid=%").c(Binder.getCallingUid()).flush();
         return mSettingsStore.isScanAlwaysAvailable();
     }
 
@@ -1405,7 +1410,7 @@
     @Override
     public void disconnect() {
         enforceChangePermission();
-        mLog.trace("disconnect uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("disconnect uid=%").c(Binder.getCallingUid()).flush();
         mWifiStateMachine.disconnectCommand();
     }
 
@@ -1415,8 +1420,8 @@
     @Override
     public void reconnect() {
         enforceChangePermission();
-        mLog.trace("reconnect uid=%").c(Binder.getCallingUid()).flush();
-        mWifiStateMachine.reconnectCommand();
+        mLog.info("reconnect uid=%").c(Binder.getCallingUid()).flush();
+        mWifiStateMachine.reconnectCommand(new WorkSource(Binder.getCallingUid()));
     }
 
     /**
@@ -1425,7 +1430,7 @@
     @Override
     public void reassociate() {
         enforceChangePermission();
-        mLog.trace("reassociate uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("reassociate uid=%").c(Binder.getCallingUid()).flush();
         mWifiStateMachine.reassociateCommand();
     }
 
@@ -1435,7 +1440,7 @@
     @Override
     public int getSupportedFeatures() {
         enforceAccessPermission();
-        mLog.trace("getSupportedFeatures uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("getSupportedFeatures uid=%").c(Binder.getCallingUid()).flush();
         if (mWifiStateMachineChannel != null) {
             return mWifiStateMachine.syncGetSupportedFeatures(mWifiStateMachineChannel);
         } else {
@@ -1447,7 +1452,7 @@
     @Override
     public void requestActivityInfo(ResultReceiver result) {
         Bundle bundle = new Bundle();
-        mLog.trace("requestActivityInfo uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("requestActivityInfo uid=%").c(Binder.getCallingUid()).flush();
         bundle.putParcelable(BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY, reportActivityInfo());
         result.send(0, bundle);
     }
@@ -1458,7 +1463,7 @@
     @Override
     public WifiActivityEnergyInfo reportActivityInfo() {
         enforceAccessPermission();
-        mLog.trace("reportActivityInfo uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("reportActivityInfo uid=%").c(Binder.getCallingUid()).flush();
         if ((getSupportedFeatures() & WifiManager.WIFI_FEATURE_LINK_LAYER_STATS) == 0) {
             return null;
         }
@@ -1531,7 +1536,7 @@
     @Override
     public ParceledListSlice<WifiConfiguration> getConfiguredNetworks() {
         enforceAccessPermission();
-        mLog.trace("getConfiguredNetworks uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("getConfiguredNetworks uid=%").c(Binder.getCallingUid()).flush();
         if (mWifiStateMachineChannel != null) {
             List<WifiConfiguration> configs = mWifiStateMachine.syncGetConfiguredNetworks(
                     Binder.getCallingUid(), mWifiStateMachineChannel);
@@ -1552,7 +1557,7 @@
     public ParceledListSlice<WifiConfiguration> getPrivilegedConfiguredNetworks() {
         enforceReadCredentialPermission();
         enforceAccessPermission();
-        mLog.trace("getPrivilegedConfiguredNetworks uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("getPrivilegedConfiguredNetworks uid=%").c(Binder.getCallingUid()).flush();
         if (mWifiStateMachineChannel != null) {
             List<WifiConfiguration> configs =
                     mWifiStateMachine.syncGetPrivilegedConfiguredNetwork(mWifiStateMachineChannel);
@@ -1574,7 +1579,7 @@
     @Override
     public WifiConfiguration getMatchingWifiConfig(ScanResult scanResult) {
         enforceAccessPermission();
-        mLog.trace("getMatchingWifiConfig uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("getMatchingWifiConfig uid=%").c(Binder.getCallingUid()).flush();
         if (!mContext.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_WIFI_PASSPOINT)) {
             throw new UnsupportedOperationException("Passpoint not enabled");
@@ -1591,7 +1596,7 @@
     @Override
     public List<OsuProvider> getMatchingOsuProviders(ScanResult scanResult) {
         enforceAccessPermission();
-        mLog.trace("getMatchingOsuProviders uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("getMatchingOsuProviders uid=%").c(Binder.getCallingUid()).flush();
         if (!mContext.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_WIFI_PASSPOINT)) {
             throw new UnsupportedOperationException("Passpoint not enabled");
@@ -1607,7 +1612,7 @@
     @Override
     public int addOrUpdateNetwork(WifiConfiguration config) {
         enforceChangePermission();
-        mLog.trace("addOrUpdateNetwork uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("addOrUpdateNetwork uid=%").c(Binder.getCallingUid()).flush();
 
         // Previously, this API is overloaded for installing Passpoint profiles.  Now
         // that we have a dedicated API for doing it, redirect the call to the dedicated API.
@@ -1678,7 +1683,7 @@
     @Override
     public boolean removeNetwork(int netId) {
         enforceChangePermission();
-        mLog.trace("removeNetwork uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("removeNetwork uid=%").c(Binder.getCallingUid()).flush();
         // TODO Add private logging for netId b/33807876
         if (mWifiStateMachineChannel != null) {
             return mWifiStateMachine.syncRemoveNetwork(mWifiStateMachineChannel, netId);
@@ -1699,7 +1704,7 @@
     public boolean enableNetwork(int netId, boolean disableOthers) {
         enforceChangePermission();
         // TODO b/33807876 Log netId
-        mLog.trace("enableNetwork uid=% disableOthers=%")
+        mLog.info("enableNetwork uid=% disableOthers=%")
                 .c(Binder.getCallingUid())
                 .c(disableOthers).flush();
 
@@ -1722,7 +1727,7 @@
     public boolean disableNetwork(int netId) {
         enforceChangePermission();
         // TODO b/33807876 Log netId
-        mLog.trace("disableNetwork uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("disableNetwork uid=%").c(Binder.getCallingUid()).flush();
 
         if (mWifiStateMachineChannel != null) {
             return mWifiStateMachine.syncDisableNetwork(mWifiStateMachineChannel, netId);
@@ -1737,14 +1742,14 @@
      * @return the Wi-Fi information, contained in {@link WifiInfo}.
      */
     @Override
-    public WifiInfo getConnectionInfo() {
+    public WifiInfo getConnectionInfo(String callingPackage) {
         enforceAccessPermission();
-        mLog.trace("getConnectionInfo uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("getConnectionInfo uid=%").c(Binder.getCallingUid()).flush();
         /*
          * Make sure we have the latest information, by sending
          * a status request to the supplicant.
          */
-        return mWifiStateMachine.syncRequestConnectionInfo();
+        return mWifiStateMachine.syncRequestConnectionInfo(callingPackage);
     }
 
     /**
@@ -1780,7 +1785,7 @@
     @Override
     public boolean addOrUpdatePasspointConfiguration(PasspointConfiguration config) {
         enforceChangePermission();
-        mLog.trace("addorUpdatePasspointConfiguration uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("addorUpdatePasspointConfiguration uid=%").c(Binder.getCallingUid()).flush();
         if (!mContext.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_WIFI_PASSPOINT)) {
             throw new UnsupportedOperationException("Passpoint not enabled");
@@ -1798,7 +1803,7 @@
     @Override
     public boolean removePasspointConfiguration(String fqdn) {
         enforceChangePermission();
-        mLog.trace("removePasspointConfiguration uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("removePasspointConfiguration uid=%").c(Binder.getCallingUid()).flush();
         if (!mContext.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_WIFI_PASSPOINT)) {
             throw new UnsupportedOperationException("Passpoint not enabled");
@@ -1816,7 +1821,7 @@
     @Override
     public List<PasspointConfiguration> getPasspointConfigurations() {
         enforceAccessPermission();
-        mLog.trace("getPasspointConfigurations uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("getPasspointConfigurations uid=%").c(Binder.getCallingUid()).flush();
         if (!mContext.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_WIFI_PASSPOINT)) {
             throw new UnsupportedOperationException("Passpoint not enabled");
@@ -1832,7 +1837,7 @@
     @Override
     public void queryPasspointIcon(long bssid, String fileName) {
         enforceAccessPermission();
-        mLog.trace("queryPasspointIcon uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("queryPasspointIcon uid=%").c(Binder.getCallingUid()).flush();
         if (!mContext.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_WIFI_PASSPOINT)) {
             throw new UnsupportedOperationException("Passpoint not enabled");
@@ -1847,7 +1852,7 @@
      */
     @Override
     public int matchProviderWithCurrentNetwork(String fqdn) {
-        mLog.trace("matchProviderWithCurrentNetwork uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("matchProviderWithCurrentNetwork uid=%").c(Binder.getCallingUid()).flush();
         return mWifiStateMachine.matchProviderWithCurrentNetwork(mWifiStateMachineChannel, fqdn);
     }
 
@@ -1858,7 +1863,7 @@
      */
     @Override
     public void deauthenticateNetwork(long holdoff, boolean ess) {
-        mLog.trace("deauthenticateNetwork uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("deauthenticateNetwork uid=%").c(Binder.getCallingUid()).flush();
         mWifiStateMachine.deauthenticateNetwork(mWifiStateMachineChannel, holdoff, ess);
     }
 
@@ -1871,7 +1876,7 @@
     @Override
     public boolean saveConfiguration() {
         enforceChangePermission();
-        mLog.trace("saveConfiguration uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("saveConfiguration uid=%").c(Binder.getCallingUid()).flush();
         if (mWifiStateMachineChannel != null) {
             return mWifiStateMachine.syncSaveConfig(mWifiStateMachineChannel);
         } else {
@@ -1894,7 +1899,7 @@
         Slog.i(TAG, "WifiService trying to set country code to " + countryCode +
                 " with persist set to " + persist);
         enforceConnectivityInternalPermission();
-        mLog.trace("setCountryCode uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("setCountryCode uid=%").c(Binder.getCallingUid()).flush();
         final long token = Binder.clearCallingIdentity();
         mCountryCode.setCountryCode(countryCode);
         Binder.restoreCallingIdentity(token);
@@ -1909,7 +1914,7 @@
     @Override
     public String getCountryCode() {
         enforceConnectivityInternalPermission();
-        mLog.trace("getCountryCode uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("getCountryCode uid=%").c(Binder.getCallingUid()).flush();
         String country = mCountryCode.getCountryCode();
         return country;
     }
@@ -1917,7 +1922,7 @@
     @Override
     public boolean isDualBandSupported() {
         //TODO: Should move towards adding a driver API that checks at runtime
-        mLog.trace("isDualBandSupported uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("isDualBandSupported uid=%").c(Binder.getCallingUid()).flush();
         return mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_wifi_dual_band_support);
     }
@@ -1932,7 +1937,7 @@
     @Deprecated
     public DhcpInfo getDhcpInfo() {
         enforceAccessPermission();
-        mLog.trace("getDhcpInfo uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("getDhcpInfo uid=%").c(Binder.getCallingUid()).flush();
         DhcpResults dhcpResults = mWifiStateMachine.syncGetDhcpResults();
 
         DhcpInfo info = new DhcpInfo();
@@ -2045,7 +2050,7 @@
         if (remoteAddress == null) {
           throw new IllegalArgumentException("remoteAddress cannot be null");
         }
-        mLog.trace("enableTdls uid=% enable=%").c(Binder.getCallingUid()).c(enable).flush();
+        mLog.info("enableTdls uid=% enable=%").c(Binder.getCallingUid()).c(enable).flush();
         TdlsTaskParams params = new TdlsTaskParams();
         params.remoteIpAddress = remoteAddress;
         params.enable = enable;
@@ -2055,7 +2060,7 @@
 
     @Override
     public void enableTdlsWithMacAddress(String remoteMacAddress, boolean enable) {
-        mLog.trace("enableTdlsWithMacAddress uid=% enable=%")
+        mLog.info("enableTdlsWithMacAddress uid=% enable=%")
                 .c(Binder.getCallingUid())
                 .c(enable)
                 .flush();
@@ -2074,7 +2079,7 @@
     public Messenger getWifiServiceMessenger() {
         enforceAccessPermission();
         enforceChangePermission();
-        mLog.trace("getWifiServiceMessenger uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("getWifiServiceMessenger uid=%").c(Binder.getCallingUid()).flush();
         return new Messenger(mClientHandler);
     }
 
@@ -2085,7 +2090,7 @@
     public void disableEphemeralNetwork(String SSID) {
         enforceAccessPermission();
         enforceChangePermission();
-        mLog.trace("disableEphemeralNetwork uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("disableEphemeralNetwork uid=%").c(Binder.getCallingUid()).flush();
         mWifiStateMachine.disableEphemeralNetwork(SSID);
     }
 
@@ -2321,7 +2326,7 @@
 
     @Override
     public boolean acquireWifiLock(IBinder binder, int lockMode, String tag, WorkSource ws) {
-        mLog.trace("acquireWifiLock uid=% lockMode=%")
+        mLog.info("acquireWifiLock uid=% lockMode=%")
                 .c(Binder.getCallingUid())
                 .c(lockMode).flush();
         if (mWifiLockManager.acquireWifiLock(lockMode, tag, binder, ws)) {
@@ -2333,13 +2338,13 @@
 
     @Override
     public void updateWifiLockWorkSource(IBinder binder, WorkSource ws) {
-        mLog.trace("updateWifiLockWorkSource uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("updateWifiLockWorkSource uid=%").c(Binder.getCallingUid()).flush();
         mWifiLockManager.updateWifiLockWorkSource(binder, ws);
     }
 
     @Override
     public boolean releaseWifiLock(IBinder binder) {
-        mLog.trace("releaseWifiLock uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("releaseWifiLock uid=%").c(Binder.getCallingUid()).flush();
         if (mWifiLockManager.releaseWifiLock(binder)) {
             mWifiController.sendMessage(CMD_LOCKS_CHANGED);
             return true;
@@ -2350,35 +2355,35 @@
     @Override
     public void initializeMulticastFiltering() {
         enforceMulticastChangePermission();
-        mLog.trace("initializeMulticastFiltering uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("initializeMulticastFiltering uid=%").c(Binder.getCallingUid()).flush();
         mWifiMulticastLockManager.initializeFiltering();
     }
 
     @Override
     public void acquireMulticastLock(IBinder binder, String tag) {
         enforceMulticastChangePermission();
-        mLog.trace("acquireMulticastLock uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("acquireMulticastLock uid=%").c(Binder.getCallingUid()).flush();
         mWifiMulticastLockManager.acquireLock(binder, tag);
     }
 
     @Override
     public void releaseMulticastLock() {
         enforceMulticastChangePermission();
-        mLog.trace("releaseMulticastLock uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("releaseMulticastLock uid=%").c(Binder.getCallingUid()).flush();
         mWifiMulticastLockManager.releaseLock();
     }
 
     @Override
     public boolean isMulticastEnabled() {
         enforceAccessPermission();
-        mLog.trace("isMulticastEnabled uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("isMulticastEnabled uid=%").c(Binder.getCallingUid()).flush();
         return mWifiMulticastLockManager.isMulticastEnabled();
     }
 
     @Override
     public void enableVerboseLogging(int verbose) {
         enforceAccessPermission();
-        mLog.trace("enableVerboseLogging uid=% verbose=%")
+        mLog.info("enableVerboseLogging uid=% verbose=%")
                 .c(Binder.getCallingUid())
                 .c(verbose).flush();
         mFacade.setIntegerSetting(
@@ -2398,7 +2403,7 @@
     @Override
     public int getVerboseLoggingLevel() {
         enforceAccessPermission();
-        mLog.trace("getVerboseLoggingLevel uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("getVerboseLoggingLevel uid=%").c(Binder.getCallingUid()).flush();
         return mFacade.getIntegerSetting(
                 mContext, Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED, 0);
     }
@@ -2406,7 +2411,7 @@
     @Override
     public void enableAggressiveHandover(int enabled) {
         enforceAccessPermission();
-        mLog.trace("enableAggressiveHandover uid=% enabled=%")
+        mLog.info("enableAggressiveHandover uid=% enabled=%")
             .c(Binder.getCallingUid())
             .c(enabled)
             .flush();
@@ -2416,14 +2421,14 @@
     @Override
     public int getAggressiveHandover() {
         enforceAccessPermission();
-        mLog.trace("getAggressiveHandover uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("getAggressiveHandover uid=%").c(Binder.getCallingUid()).flush();
         return mWifiStateMachine.getAggressiveHandover();
     }
 
     @Override
     public void setAllowScansWithTraffic(int enabled) {
         enforceAccessPermission();
-        mLog.trace("setAllowScansWithTraffic uid=% enabled=%")
+        mLog.info("setAllowScansWithTraffic uid=% enabled=%")
                 .c(Binder.getCallingUid())
                 .c(enabled).flush();
         mWifiStateMachine.setAllowScansWithTraffic(enabled);
@@ -2432,14 +2437,14 @@
     @Override
     public int getAllowScansWithTraffic() {
         enforceAccessPermission();
-        mLog.trace("getAllowScansWithTraffic uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("getAllowScansWithTraffic uid=%").c(Binder.getCallingUid()).flush();
         return mWifiStateMachine.getAllowScansWithTraffic();
     }
 
     @Override
     public boolean setEnableAutoJoinWhenAssociated(boolean enabled) {
         enforceChangePermission();
-        mLog.trace("setEnableAutoJoinWhenAssociated uid=% enabled=%")
+        mLog.info("setEnableAutoJoinWhenAssociated uid=% enabled=%")
                 .c(Binder.getCallingUid())
                 .c(enabled).flush();
         return mWifiStateMachine.setEnableAutoJoinWhenAssociated(enabled);
@@ -2448,7 +2453,7 @@
     @Override
     public boolean getEnableAutoJoinWhenAssociated() {
         enforceAccessPermission();
-        mLog.trace("getEnableAutoJoinWhenAssociated uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("getEnableAutoJoinWhenAssociated uid=%").c(Binder.getCallingUid()).flush();
         return mWifiStateMachine.getEnableAutoJoinWhenAssociated();
     }
 
@@ -2457,7 +2462,7 @@
     public WifiConnectionStatistics getConnectionStatistics() {
         enforceAccessPermission();
         enforceReadCredentialPermission();
-        mLog.trace("getConnectionStatistics uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("getConnectionStatistics uid=%").c(Binder.getCallingUid()).flush();
         if (mWifiStateMachineChannel != null) {
             return mWifiStateMachine.syncGetConnectionStatistics(mWifiStateMachineChannel);
         } else {
@@ -2469,7 +2474,7 @@
     @Override
     public void factoryReset() {
         enforceConnectivityInternalPermission();
-        mLog.trace("factoryReset uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("factoryReset uid=%").c(Binder.getCallingUid()).flush();
         if (mUserManager.hasUserRestriction(UserManager.DISALLOW_NETWORK_RESET)) {
             return;
         }
@@ -2543,7 +2548,7 @@
     @Override
     public Network getCurrentNetwork() {
         enforceAccessPermission();
-        mLog.trace("getCurrentNetwork uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("getCurrentNetwork uid=%").c(Binder.getCallingUid()).flush();
         return mWifiStateMachine.getCurrentNetwork();
     }
 
@@ -2575,9 +2580,9 @@
     @Override
     public void enableWifiConnectivityManager(boolean enabled) {
         enforceConnectivityInternalPermission();
-        mLog.trace("enableWifiConnectivityManager uid=% enabled=%")
-            .c(Binder.getCallingUid())
-            .c(enabled).flush();
+        mLog.info("enableWifiConnectivityManager uid=% enabled=%")
+                .c(Binder.getCallingUid())
+                .c(enabled).flush();
         mWifiStateMachine.enableWifiConnectivityManager(enabled);
     }
 
@@ -2589,7 +2594,7 @@
     @Override
     public byte[] retrieveBackupData() {
         enforceNetworkSettingsPermission();
-        mLog.trace("retrieveBackupData uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("retrieveBackupData uid=%").c(Binder.getCallingUid()).flush();
         if (mWifiStateMachineChannel == null) {
             Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
             return null;
@@ -2634,7 +2639,7 @@
     @Override
     public void restoreBackupData(byte[] data) {
         enforceNetworkSettingsPermission();
-        mLog.trace("restoreBackupData uid=%").c(Binder.getCallingUid()).flush();
+        mLog.info("restoreBackupData uid=%").c(Binder.getCallingUid()).flush();
         if (mWifiStateMachineChannel == null) {
             Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
             return;
diff --git a/service/java/com/android/server/wifi/WifiStateMachine.java b/service/java/com/android/server/wifi/WifiStateMachine.java
index bf5d08b..ac7d748 100644
--- a/service/java/com/android/server/wifi/WifiStateMachine.java
+++ b/service/java/com/android/server/wifi/WifiStateMachine.java
@@ -31,6 +31,7 @@
 
 import android.Manifest;
 import android.app.ActivityManager;
+import android.app.AppGlobals;
 import android.app.PendingIntent;
 import android.bluetooth.BluetoothAdapter;
 import android.content.BroadcastReceiver;
@@ -82,6 +83,7 @@
 import android.net.wifi.p2p.IWifiP2pManager;
 import android.os.BatteryStats;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.INetworkManagementService;
@@ -260,7 +262,7 @@
         if (mVerboseLoggingEnabled) {
             Log.e(TAG, "onRssiThresholdBreach event. Cur Rssi = " + curRssi);
         }
-        sendMessage(CMD_RSSI_THRESHOLD_BREACH, curRssi);
+        sendMessage(CMD_RSSI_THRESHOLD_BREACHED, curRssi);
     }
 
     public void processRssiThreshold(byte curRssi, int reason) {
@@ -278,7 +280,7 @@
                 // the rssi thresholds and raised event to host. This would be eggregious if this
                 // value is invalid
                 mWifiInfo.setRssi(curRssi);
-                updateCapabilities(getCurrentWifiConfiguration());
+                updateCapabilities();
                 int ret = startRssiMonitoringOffload(maxRssi, minRssi);
                 Log.d(TAG, "Re-program RSSI thresholds for " + smToString(reason) +
                         ": [" + minRssi + ", " + maxRssi + "], curRssi=" + curRssi + " ret=" + ret);
@@ -364,7 +366,7 @@
     private final Object mDhcpResultsLock = new Object();
     private DhcpResults mDhcpResults;
 
-    // NOTE: Do not return to clients - use #getWiFiInfoForUid(int)
+    // NOTE: Do not return to clients - see syncRequestConnectionInfo()
     private final WifiInfo mWifiInfo;
     private NetworkInfo mNetworkInfo;
     private final NetworkCapabilities mDfltNetworkCapabilities;
@@ -691,7 +693,7 @@
     static final int CMD_STOP_RSSI_MONITORING_OFFLOAD                   = BASE + 163;
 
     /* used to indicated RSSI threshold breach in hw */
-    static final int CMD_RSSI_THRESHOLD_BREACH                          = BASE + 164;
+    static final int CMD_RSSI_THRESHOLD_BREACHED                        = BASE + 164;
 
     /* Enable/Disable WifiConnectivityManager */
     static final int CMD_ENABLE_WIFI_CONNECTIVITY_MANAGER               = BASE + 166;
@@ -1319,7 +1321,7 @@
 
     /**
      * Initiates connection to a network specified by the user/app. This method checks if the
-     * requesting app holds the WIFI_CONFIG_OVERRIDE permission.
+     * requesting app holds the NETWORK_SETTINGS permission.
      *
      * @param netId Id network to initiate connection.
      * @param uid UID of the app requesting the connection.
@@ -1348,7 +1350,7 @@
             logi("connectToUserSelectNetwork already connecting/connected=" + netId);
         } else {
             mWifiConnectivityManager.prepareForForcedConnection(netId);
-            startConnectToNetwork(netId, SUPPLICANT_BSSID_ANY);
+            startConnectToNetwork(netId, uid, SUPPLICANT_BSSID_ANY);
         }
         return true;
     }
@@ -1754,8 +1756,38 @@
      *
      * @return a {@link WifiInfo} object containing information about the current connection
      */
-    public WifiInfo syncRequestConnectionInfo() {
-        return getWiFiInfoForUid(Binder.getCallingUid());
+    public WifiInfo syncRequestConnectionInfo(String callingPackage) {
+        int uid = Binder.getCallingUid();
+        WifiInfo result = new WifiInfo(mWifiInfo);
+        if (uid == Process.myUid()) return result;
+        boolean hideBssidAndSsid = true;
+        result.setMacAddress(WifiInfo.DEFAULT_MAC_ADDRESS);
+
+        IPackageManager packageManager = AppGlobals.getPackageManager();
+
+        try {
+            if (packageManager.checkUidPermission(Manifest.permission.LOCAL_MAC_ADDRESS,
+                    uid) == PackageManager.PERMISSION_GRANTED) {
+                result.setMacAddress(mWifiInfo.getMacAddress());
+            }
+            final WifiConfiguration currentWifiConfiguration = getCurrentWifiConfiguration();
+            if (mWifiPermissionsUtil.canAccessFullConnectionInfo(
+                    currentWifiConfiguration,
+                    callingPackage,
+                    uid,
+                    Build.VERSION_CODES.O)) {
+                hideBssidAndSsid = false;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error checking receiver permission", e);
+        } catch (SecurityException e) {
+            Log.e(TAG, "Security exception checking receiver permission", e);
+        }
+        if (hideBssidAndSsid) {
+            result.setBSSID(WifiInfo.DEFAULT_MAC_ADDRESS);
+            result.setSSID(WifiSsid.createFromHex(null));
+        }
+        return result;
     }
 
     public WifiInfo getWifiInfo() {
@@ -1843,8 +1875,8 @@
     /**
      * Initiate a reconnection to AP
      */
-    public void reconnectCommand() {
-        sendMessage(CMD_RECONNECT);
+    public void reconnectCommand(WorkSource workSource) {
+        sendMessage(CMD_RECONNECT, workSource);
     }
 
     /**
@@ -2719,7 +2751,7 @@
                 break;
             case CMD_START_RSSI_MONITORING_OFFLOAD:
             case CMD_STOP_RSSI_MONITORING_OFFLOAD:
-            case CMD_RSSI_THRESHOLD_BREACH:
+            case CMD_RSSI_THRESHOLD_BREACHED:
                 sb.append(" rssi=");
                 sb.append(Integer.toString(msg.arg1));
                 sb.append(" thresholds=");
@@ -2980,13 +3012,13 @@
              */
             int newSignalLevel = WifiManager.calculateSignalLevel(newRssi, WifiManager.RSSI_LEVELS);
             if (newSignalLevel != mLastSignalLevel) {
-                updateCapabilities(getCurrentWifiConfiguration());
+                updateCapabilities();
                 sendRssiChangeBroadcast(newRssi);
             }
             mLastSignalLevel = newSignalLevel;
         } else {
             mWifiInfo.setRssi(WifiInfo.INVALID_RSSI);
-            updateCapabilities(getCurrentWifiConfiguration());
+            updateCapabilities();
         }
 
         if (newLinkSpeed != null) {
@@ -3142,29 +3174,6 @@
         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
     }
 
-    private WifiInfo getWiFiInfoForUid(int uid) {
-        WifiInfo result = new WifiInfo(mWifiInfo);
-        if (Binder.getCallingUid() == Process.myUid()) {
-            return result;
-        }
-
-        result.setMacAddress(WifiInfo.DEFAULT_MAC_ADDRESS);
-
-        IBinder binder = mFacade.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);
@@ -3260,12 +3269,13 @@
         }
 
         mWifiInfo.setBSSID(stateChangeResult.BSSID);
-
         mWifiInfo.setSSID(stateChangeResult.wifiSsid);
-        WifiConfiguration config = getCurrentWifiConfiguration();
+
+        final WifiConfiguration config = getCurrentWifiConfiguration();
         if (config != null) {
-            // Set meteredHint to true if the access network type of the connecting/connected AP
-            // is a chargeable public network.
+            mWifiInfo.setEphemeral(config.ephemeral);
+
+            // Set meteredHint if scan result says network is expensive
             ScanDetailCache scanDetailCache = mWifiConfigManager.getScanDetailCacheForNetwork(
                     config.networkId);
             if (scanDetailCache != null) {
@@ -3279,15 +3289,9 @@
                     }
                 }
             }
-
-            mWifiInfo.setEphemeral(config.ephemeral);
-            if (!mWifiInfo.getMeteredHint()) { // don't override the value if already set.
-                mWifiInfo.setMeteredHint(config.meteredHint);
-            }
         }
 
         mSupplicantStateTracker.sendMessage(Message.obtain(message));
-
         return state;
     }
 
@@ -3427,11 +3431,12 @@
     }
 
     /**
-     * Inform other components (WifiMetrics, WifiDiagnostics, etc.) that the current connection attempt
-     * has concluded.
+     * Inform other components (WifiMetrics, WifiDiagnostics, WifiConnectivityManager, etc.) that
+     * the current connection attempt has concluded.
      */
     private void reportConnectionAttemptEnd(int level2FailureCode, int connectivityFailureCode) {
         mWifiMetrics.endConnectionEvent(level2FailureCode, connectivityFailureCode);
+        mWifiConnectivityManager.handleConnectionAttemptEnded(level2FailureCode);
         switch (level2FailureCode) {
             case WifiMetrics.ConnectionEvent.FAILURE_NONE:
                 // Ideally, we'd wait until IP reachability has been confirmed. this code falls
@@ -3478,11 +3483,20 @@
                         mWifiInfo + " got: " + addr);
             }
         }
+
         mWifiInfo.setInetAddress(addr);
-        if (!mWifiInfo.getMeteredHint()) { // don't override the value if already set.
-            mWifiInfo.setMeteredHint(dhcpResults.hasMeteredHint());
-            updateCapabilities(getCurrentWifiConfiguration());
+
+        final WifiConfiguration config = getCurrentWifiConfiguration();
+        if (config != null) {
+            mWifiInfo.setEphemeral(config.ephemeral);
         }
+
+        // Set meteredHint if DHCP result says network is metered
+        if (dhcpResults.hasMeteredHint()) {
+            mWifiInfo.setMeteredHint(true);
+        }
+
+        updateCapabilities(config);
     }
 
     private void handleSuccessfulIpConfiguration() {
@@ -3968,9 +3982,7 @@
                     deleteNetworkConfigAndSendReply(message, true);
                     break;
                 case WifiManager.SAVE_NETWORK:
-                    messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
-                    replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED,
-                            WifiManager.BUSY);
+                    saveNetworkConfigAndSendReply(message);
                     break;
                 case WifiManager.START_WPS:
                     replyToMessage(message, WifiManager.WPS_FAILED,
@@ -4106,11 +4118,14 @@
                 case CMD_CLIENT_INTERFACE_BINDER_DEATH:
                     Log.e(TAG, "wificond died unexpectedly. Triggering recovery");
                     mWifiMetrics.incrementNumWificondCrashes();
+                    mWifiDiagnostics.captureBugReportData(
+                            WifiDiagnostics.REPORT_REASON_WIFICOND_CRASH);
                     mWifiInjector.getSelfRecovery().trigger(SelfRecovery.REASON_WIFICOND_CRASH);
                     break;
                 case CMD_VENDOR_HAL_HWBINDER_DEATH:
                     Log.e(TAG, "Vendor HAL died unexpectedly. Triggering recovery");
                     mWifiMetrics.incrementNumHalCrashes();
+                    mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_HAL_CRASH);
                     mWifiInjector.getSelfRecovery().trigger(SelfRecovery.REASON_HAL_CRASH);
                     break;
                 case CMD_DIAGS_CONNECT_TIMEOUT:
@@ -4263,10 +4278,6 @@
                     mLastSignalLevel = -1;
 
                     mWifiInfo.setMacAddress(mWifiNative.getMacAddress());
-                    // Attempt to migrate data out of legacy store.
-                    if (!mWifiConfigManager.migrateFromLegacyStore()) {
-                        Log.e(TAG, "Failed to migrate from legacy config store");
-                    }
                     initializeWpsDetails();
                     sendSupplicantConnectionChangedBroadcast(true);
                     transitionTo(mSupplicantStartedState);
@@ -4526,7 +4537,7 @@
                     mEnableAutoJoinWhenAssociated = allowed;
                     if (!old_state && allowed && mScreenOn
                             && getCurrentState() == mConnectedState) {
-                        mWifiConnectivityManager.forceConnectivityScan();
+                        mWifiConnectivityManager.forceConnectivityScan(WIFI_WORK_SOURCE);
                     }
                     break;
                 case CMD_SELECT_TX_POWER_SCENARIO:
@@ -4813,7 +4824,7 @@
 
             // Notify PasspointManager of Passpoint network connected event.
             WifiConfiguration currentNetwork = getCurrentWifiConfiguration();
-            if (currentNetwork.isPasspoint()) {
+            if (currentNetwork != null && currentNetwork.isPasspoint()) {
                 mPasspointManager.onPasspointNetworkConnected(currentNetwork.FQDN);
             }
        }
@@ -4854,7 +4865,7 @@
             return null;
         }
 
-        return scanDetailCache.get(BSSID);
+        return scanDetailCache.getScanResult(BSSID);
     }
 
     String getCurrentBSSID() {
@@ -4944,8 +4955,10 @@
                     mWifiConfigManager.updateNetworkSelectionStatus(mTargetNetworkId,
                             WifiConfiguration.NetworkSelectionStatus
                             .DISABLED_ASSOCIATION_REJECTION);
+                    mWifiConfigManager.setRecentFailureAssociationStatus(mTargetNetworkId,
+                            reasonCode);
                     mSupplicantStateTracker.sendMessage(WifiMonitor.ASSOCIATION_REJECTION_EVENT);
-                    //If rejection occurred while Metrics is tracking a ConnnectionEvent, end it.
+                    // If rejection occurred while Metrics is tracking a ConnnectionEvent, end it.
                     reportConnectionAttemptEnd(
                             WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_REJECTION,
                             WifiMetricsProto.ConnectionEvent.HLF_NONE);
@@ -4973,6 +4986,7 @@
                     }
                     mWifiConfigManager.updateNetworkSelectionStatus(
                             mTargetNetworkId, disableReason);
+                    mWifiConfigManager.clearRecentFailureReason(mTargetNetworkId);
                     //If failure occurred while Metrics is tracking a ConnnectionEvent, end it.
                     reportConnectionAttemptEnd(
                             WifiMetrics.ConnectionEvent.FAILURE_AUTHENTICATION_FAILURE,
@@ -5146,7 +5160,8 @@
                             mPasspointManager.getMatchingOsuProviders((ScanResult) message.obj));
                     break;
                 case CMD_RECONNECT:
-                    mWifiConnectivityManager.forceConnectivityScan();
+                    WorkSource workSource = (WorkSource) message.obj;
+                    mWifiConnectivityManager.forceConnectivityScan(workSource);
                     break;
                 case CMD_REASSOCIATE:
                     lastConnectAttemptTimestamp = mClock.getWallClockMillis();
@@ -5166,7 +5181,23 @@
                 case CMD_START_CONNECT:
                     /* connect command coming from auto-join */
                     netId = message.arg1;
+                    int uid = message.arg2;
                     bssid = (String) message.obj;
+
+                    synchronized (mWifiReqCountLock) {
+                        if (!hasConnectionRequests()) {
+                            if (mNetworkAgent == null) {
+                                loge("CMD_START_CONNECT but no requests and not connected,"
+                                        + " bailing");
+                                break;
+                            } else if (!mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)) {
+                                loge("CMD_START_CONNECT but no requests and connected, but app "
+                                        + "does not have sufficient permissions, bailing");
+                                break;
+                            }
+                        }
+                    }
+
                     config = mWifiConfigManager.getConfiguredNetworkWithPassword(netId);
                     logd("CMD_START_CONNECT sup state "
                             + mSupplicantStateTracker.getSupplicantStateName()
@@ -5256,41 +5287,17 @@
                     replyToMessage(message, WifiManager.CONNECT_NETWORK_SUCCEEDED);
                     break;
                 case WifiManager.SAVE_NETWORK:
-                    config = (WifiConfiguration) message.obj;
-                    mWifiConnectionStatistics.numWifiManagerJoinAttempt++;
-                    if (config == null) {
-                        loge("SAVE_NETWORK with null configuration"
-                                + mSupplicantStateTracker.getSupplicantStateName()
-                                + " my state " + getCurrentState().getName());
-                        messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
-                        replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED,
-                                WifiManager.ERROR);
-                        break;
-                    }
-                    result = mWifiConfigManager.addOrUpdateNetwork(config, message.sendingUid);
-                    if (!result.isSuccess()) {
-                        loge("SAVE_NETWORK adding/updating config=" + config + " failed");
-                        messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
-                        replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED,
-                                WifiManager.ERROR);
-                        break;
-                    }
-                    if (!mWifiConfigManager.enableNetwork(
-                            result.getNetworkId(), false, message.sendingUid)) {
-                        loge("SAVE_NETWORK enabling config=" + config + " failed");
-                        messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
-                        replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED,
-                                WifiManager.ERROR);
-                        break;
-                    }
+                    result = saveNetworkConfigAndSendReply(message);
                     netId = result.getNetworkId();
-                    if (mWifiInfo.getNetworkId() == netId) {
+                    if (result.isSuccess() && mWifiInfo.getNetworkId() == netId) {
+                        mWifiConnectionStatistics.numWifiManagerJoinAttempt++;
                         if (result.hasCredentialChanged()) {
+                            config = (WifiConfiguration) message.obj;
                             // The network credentials changed and we're connected to this network,
                             // start a new connection with the updated credentials.
                             logi("SAVE_NETWORK credential changed for config=" + config.configKey()
                                     + ", Reconnecting.");
-                            startConnectToNetwork(netId, SUPPLICANT_BSSID_ANY);
+                            startConnectToNetwork(netId, message.sendingUid, SUPPLICANT_BSSID_ANY);
                         } else {
                             if (result.hasProxyChanged()) {
                                 log("Reconfiguring proxy on connection");
@@ -5308,8 +5315,6 @@
                             }
                         }
                     }
-                    broadcastWifiCredentialChanged(WifiManager.WIFI_CREDENTIAL_SAVED, config);
-                    replyToMessage(message, WifiManager.SAVE_NETWORK_SUCCEEDED);
                     break;
                 case WifiManager.FORGET_NETWORK:
                     if (!deleteNetworkConfigAndSendReply(message, true)) {
@@ -5392,6 +5397,7 @@
                 case WifiMonitor.NETWORK_CONNECTION_EVENT:
                     if (mVerboseLoggingEnabled) log("Network connection established");
                     mLastNetworkId = lookupFrameworkNetworkId(message.arg1);
+                    mWifiConfigManager.clearRecentFailureReason(mLastNetworkId);
                     mLastBssid = (String) message.obj;
                     reasonCode = message.arg2;
                     // TODO: This check should not be needed after WifiStateMachinePrime refactor.
@@ -5408,7 +5414,7 @@
                         ScanDetailCache scanDetailCache =
                                 mWifiConfigManager.getScanDetailCacheForNetwork(config.networkId);
                         if (scanDetailCache != null && mLastBssid != null) {
-                            ScanResult scanResult = scanDetailCache.get(mLastBssid);
+                            ScanResult scanResult = scanDetailCache.getScanResult(mLastBssid);
                             if (scanResult != null) {
                                 mWifiInfo.setFrequency(scanResult.frequency);
                             }
@@ -5496,28 +5502,34 @@
         }
     }
 
+    public void updateCapabilities() {
+        updateCapabilities(getCurrentWifiConfiguration());
+    }
+
     private void updateCapabilities(WifiConfiguration config) {
-        NetworkCapabilities networkCapabilities = new NetworkCapabilities(mDfltNetworkCapabilities);
-        if (config != null) {
-            if (config.ephemeral) {
-                networkCapabilities.removeCapability(
-                        NetworkCapabilities.NET_CAPABILITY_TRUSTED);
-            } else {
-                networkCapabilities.addCapability(
-                        NetworkCapabilities.NET_CAPABILITY_TRUSTED);
-            }
+        final NetworkCapabilities result = new NetworkCapabilities(mDfltNetworkCapabilities);
 
-            networkCapabilities.setSignalStrength(
-                    (mWifiInfo.getRssi() != WifiInfo.INVALID_RSSI)
-                    ? mWifiInfo.getRssi()
-                    : NetworkCapabilities.SIGNAL_STRENGTH_UNSPECIFIED);
+        if (mWifiInfo != null && !mWifiInfo.isEphemeral()) {
+            result.addCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED);
+        } else {
+            result.removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED);
         }
 
-        if (mWifiInfo.getMeteredHint()) {
-            networkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+        if (mWifiInfo != null && !WifiConfiguration.isMetered(config, mWifiInfo)) {
+            result.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+        } else {
+            result.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
         }
 
-        mNetworkAgent.sendNetworkCapabilities(networkCapabilities);
+        if (mWifiInfo != null && mWifiInfo.getRssi() != WifiInfo.INVALID_RSSI) {
+            result.setSignalStrength(mWifiInfo.getRssi());
+        } else {
+            result.setSignalStrength(NetworkCapabilities.SIGNAL_STRENGTH_UNSPECIFIED);
+        }
+
+        if (mNetworkAgent != null) {
+            mNetworkAgent.sendNetworkCapabilities(result);
+        }
     }
 
     /**
@@ -5810,8 +5822,15 @@
                     reportConnectionAttemptEnd(
                             WifiMetrics.ConnectionEvent.FAILURE_NONE,
                             WifiMetricsProto.ConnectionEvent.HLF_NONE);
-                    sendConnectedState();
-                    transitionTo(mConnectedState);
+                    if (getCurrentWifiConfiguration() == null) {
+                        // The current config may have been removed while we were connecting,
+                        // trigger a disconnect to clear up state.
+                        mWifiNative.disconnect();
+                        transitionTo(mDisconnectingState);
+                    } else {
+                        sendConnectedState();
+                        transitionTo(mConnectedState);
+                    }
                     break;
                 case CMD_IP_CONFIGURATION_LOST:
                     // Get Link layer stats so that we get fresh tx packet counters.
@@ -5961,7 +5980,7 @@
                             ScanDetailCache scanDetailCache = mWifiConfigManager
                                     .getScanDetailCacheForNetwork(config.networkId);
                             if (scanDetailCache != null) {
-                                ScanResult scanResult = scanDetailCache.get(mLastBssid);
+                                ScanResult scanResult = scanDetailCache.getScanResult(mLastBssid);
                                 if (scanResult != null) {
                                     mWifiInfo.setFrequency(scanResult.frequency);
                                 }
@@ -5971,13 +5990,16 @@
                     }
                     break;
                 case CMD_START_RSSI_MONITORING_OFFLOAD:
-                case CMD_RSSI_THRESHOLD_BREACH:
+                case CMD_RSSI_THRESHOLD_BREACHED:
                     byte currRssi = (byte) message.arg1;
                     processRssiThreshold(currRssi, message.what);
                     break;
                 case CMD_STOP_RSSI_MONITORING_OFFLOAD:
                     stopRssiMonitoringOffload();
                     break;
+                case CMD_RECONNECT:
+                    log(" Ignore CMD_RECONNECT request because wifi is already connected");
+                    break;
                 case CMD_RESET_SIM_NETWORKS:
                     if (message.arg1 == 0 // sim was removed
                             && mLastNetworkId != WifiConfiguration.INVALID_NETWORK_ID) {
@@ -6121,7 +6143,7 @@
         WifiConfiguration config = getCurrentWifiConfiguration();
         if (shouldEvaluateWhetherToSendExplicitlySelected(config)) {
             boolean prompt =
-                    mWifiPermissionsUtil.checkConfigOverridePermission(config.lastConnectUid);
+                    mWifiPermissionsUtil.checkNetworkSettingsPermission(config.lastConnectUid);
             if (mVerboseLoggingEnabled) {
                 log("Network selected by UID " + config.lastConnectUid + " prompt=" + prompt);
             }
@@ -6132,7 +6154,9 @@
                 if (mVerboseLoggingEnabled) {
                     log("explictlySelected acceptUnvalidated=" + config.noInternetAccessExpected);
                 }
-                mNetworkAgent.explicitlySelected(config.noInternetAccessExpected);
+                if (mNetworkAgent != null) {
+                    mNetworkAgent.explicitlySelected(config.noInternetAccessExpected);
+                }
             }
         }
 
@@ -6504,13 +6528,17 @@
                         dstMac = NativeUtil.macAddressToByteArray(dstMacStr);
                     } catch (NullPointerException | IllegalArgumentException e) {
                         loge("Can't find MAC address for next hop to " + pkt.dstAddress);
-                        mNetworkAgent.onPacketKeepaliveEvent(slot,
-                                ConnectivityManager.PacketKeepalive.ERROR_INVALID_IP_ADDRESS);
+                        if (mNetworkAgent != null) {
+                            mNetworkAgent.onPacketKeepaliveEvent(slot,
+                                    ConnectivityManager.PacketKeepalive.ERROR_INVALID_IP_ADDRESS);
+                        }
                         break;
                     }
                     pkt.dstMac = dstMac;
                     int result = startWifiIPPacketOffload(slot, pkt, intervalSeconds);
-                    mNetworkAgent.onPacketKeepaliveEvent(slot, result);
+                    if (mNetworkAgent != null) {
+                        mNetworkAgent.onPacketKeepaliveEvent(slot, result);
+                    }
                     break;
                 }
                 default:
@@ -6763,7 +6791,11 @@
                     // Ignore intermediate success, wait for full connection
                     break;
                 case WifiMonitor.NETWORK_CONNECTION_EVENT:
-                    if (loadNetworksFromSupplicantAfterWps()) {
+                    Pair<Boolean, Integer> loadResult = loadNetworksFromSupplicantAfterWps();
+                    boolean success = loadResult.first;
+                    int netId = loadResult.second;
+                    if (success) {
+                        message.arg1 = netId;
                         replyToMessage(mSourceMessage, WifiManager.WPS_COMPLETED);
                     } else {
                         replyToMessage(mSourceMessage, WifiManager.WPS_FAILED,
@@ -6820,6 +6852,8 @@
                 case WifiManager.CONNECT_NETWORK:
                 case CMD_ENABLE_NETWORK:
                 case CMD_RECONNECT:
+                    log(" Ignore CMD_RECONNECT request because wps is running");
+                    return HANDLED;
                 case CMD_REASSOCIATE:
                     deferMessage(message);
                     break;
@@ -6859,13 +6893,17 @@
 
         /**
          * Load network config from wpa_supplicant after WPS is complete.
+         * @return a boolean (true if load was successful) and integer (Network Id of currently
+         *          connected network, can be {@link WifiConfiguration#INVALID_NETWORK_ID} despite
+         *          successful loading, if multiple networks in supplicant) pair.
          */
-        private boolean loadNetworksFromSupplicantAfterWps() {
+        private Pair<Boolean, Integer> loadNetworksFromSupplicantAfterWps() {
             Map<String, WifiConfiguration> configs = new HashMap<>();
             SparseArray<Map<String, String>> extras = new SparseArray<>();
+            int netId = WifiConfiguration.INVALID_NETWORK_ID;
             if (!mWifiNative.migrateNetworksFromSupplicant(configs, extras)) {
                 loge("Failed to load networks from wpa_supplicant after Wps");
-                return false;
+                return Pair.create(false, WifiConfiguration.INVALID_NETWORK_ID);
             }
             for (Map.Entry<String, WifiConfiguration> entry : configs.entrySet()) {
                 WifiConfiguration config = entry.getValue();
@@ -6876,15 +6914,17 @@
                         config, mSourceMessage.sendingUid);
                 if (!result.isSuccess()) {
                     loge("Failed to add network after WPS: " + entry.getValue());
-                    return false;
+                    return Pair.create(false, WifiConfiguration.INVALID_NETWORK_ID);
                 }
                 if (!mWifiConfigManager.enableNetwork(
                         result.getNetworkId(), true, mSourceMessage.sendingUid)) {
-                    loge("Failed to enable network after WPS: " + entry.getValue());
-                    return false;
+                    Log.wtf(TAG, "Failed to enable network after WPS: " + entry.getValue());
+                    return Pair.create(false, WifiConfiguration.INVALID_NETWORK_ID);
                 }
+                netId = result.getNetworkId();
             }
-            return true;
+            return Pair.create(true,
+                    configs.size() == 1 ? netId : WifiConfiguration.INVALID_NETWORK_ID);
         }
     }
 
@@ -7081,14 +7121,11 @@
      * Automatically connect to the network specified
      *
      * @param networkId ID of the network to connect to
+     * @param uid UID of the app triggering the connection.
      * @param bssid BSSID of the network
      */
-    public void startConnectToNetwork(int networkId, String bssid) {
-        synchronized (mWifiReqCountLock) {
-            if (hasConnectionRequests()) {
-                sendMessage(CMD_START_CONNECT, networkId, 0, bssid);
-            }
-        }
+    public void startConnectToNetwork(int networkId, int uid, String bssid) {
+        sendMessage(CMD_START_CONNECT, networkId, uid, bssid);
     }
 
     /**
@@ -7173,6 +7210,43 @@
         }
     }
 
+    /**
+     * Private method to handle calling WifiConfigManager to add & enable network configs and reply
+     * to the message from the sender of the outcome.
+     *
+     * @return NetworkUpdateResult with networkId of the added/updated configuration. Will return
+     * {@link WifiConfiguration#INVALID_NETWORK_ID} in case of error.
+     */
+    private NetworkUpdateResult saveNetworkConfigAndSendReply(Message message) {
+        WifiConfiguration config = (WifiConfiguration) message.obj;
+        if (config == null) {
+            loge("SAVE_NETWORK with null configuration "
+                    + mSupplicantStateTracker.getSupplicantStateName()
+                    + " my state " + getCurrentState().getName());
+            messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
+            replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED, WifiManager.ERROR);
+            return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
+        }
+        NetworkUpdateResult result =
+                mWifiConfigManager.addOrUpdateNetwork(config, message.sendingUid);
+        if (!result.isSuccess()) {
+            loge("SAVE_NETWORK adding/updating config=" + config + " failed");
+            messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
+            replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED, WifiManager.ERROR);
+            return result;
+        }
+        if (!mWifiConfigManager.enableNetwork(
+                result.getNetworkId(), false, message.sendingUid)) {
+            loge("SAVE_NETWORK enabling config=" + config + " failed");
+            messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
+            replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED, WifiManager.ERROR);
+            return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
+        }
+        broadcastWifiCredentialChanged(WifiManager.WIFI_CREDENTIAL_SAVED, config);
+        replyToMessage(message, WifiManager.SAVE_NETWORK_SUCCEEDED);
+        return result;
+    }
+
     private static String getLinkPropertiesSummary(LinkProperties lp) {
         List<String> attributes = new ArrayList<>(6);
         if (lp.hasIPv4Address()) {
diff --git a/service/java/com/android/server/wifi/WificondControl.java b/service/java/com/android/server/wifi/WificondControl.java
index a877c09..b6104a8 100644
--- a/service/java/com/android/server/wifi/WificondControl.java
+++ b/service/java/com/android/server/wifi/WificondControl.java
@@ -32,6 +32,7 @@
 import com.android.server.wifi.hotspot2.NetworkDetail;
 import com.android.server.wifi.util.InformationElementUtil;
 import com.android.server.wifi.util.NativeUtil;
+import com.android.server.wifi.util.ScanResultUtil;
 import com.android.server.wifi.wificond.ChannelSettings;
 import com.android.server.wifi.wificond.HiddenNetwork;
 import com.android.server.wifi.wificond.NativeScanResult;
@@ -50,8 +51,16 @@
     private boolean mVerboseLoggingEnabled = false;
 
     private static final String TAG = "WificondControl";
+
+    /* Get scan results for a single scan */
+    public static final int SCAN_TYPE_SINGLE_SCAN = 0;
+
+    /* Get scan results for Pno Scan */
+    public static final int SCAN_TYPE_PNO_SCAN = 1;
+
     private WifiInjector mWifiInjector;
     private WifiMonitor mWifiMonitor;
+    private final CarrierNetworkConfig mCarrierNetworkConfig;
 
     // Cached wificond binder handlers.
     private IWificond mWificond;
@@ -78,9 +87,11 @@
         }
     }
 
-    WificondControl(WifiInjector wifiInjector, WifiMonitor wifiMonitor) {
+    WificondControl(WifiInjector wifiInjector, WifiMonitor wifiMonitor,
+            CarrierNetworkConfig carrierNetworkConfig) {
         mWifiInjector = wifiInjector;
         mWifiMonitor = wifiMonitor;
+        mCarrierNetworkConfig = carrierNetworkConfig;
     }
 
     private class PnoScanEventHandler extends IPnoScanEvent.Stub {
@@ -88,12 +99,25 @@
         public void OnPnoNetworkFound() {
             Log.d(TAG, "Pno scan result event");
             mWifiMonitor.broadcastPnoScanResultEvent(mClientInterfaceName);
+            mWifiInjector.getWifiMetrics().incrementPnoFoundNetworkEventCount();
         }
 
         @Override
         public void OnPnoScanFailed() {
             Log.d(TAG, "Pno Scan failed event");
-            // Nothing to do for now.
+            mWifiInjector.getWifiMetrics().incrementPnoScanFailedCount();
+        }
+
+        @Override
+        public void OnPnoScanOverOffloadStarted() {
+            Log.d(TAG, "Pno scan over offload started");
+            mWifiInjector.getWifiMetrics().incrementPnoScanStartedOverOffloadCount();
+        }
+
+        @Override
+        public void OnPnoScanOverOffloadFailed(int reason) {
+            Log.d(TAG, "Pno scan over offload failed");
+            mWifiInjector.getWifiMetrics().incrementPnoScanFailedOverOffloadCount();
         }
     }
 
@@ -318,14 +342,19 @@
     * @return Returns an ArrayList of ScanDetail.
     * Returns an empty ArrayList on failure.
     */
-    public ArrayList<ScanDetail> getScanResults() {
+    public ArrayList<ScanDetail> getScanResults(int scanType) {
         ArrayList<ScanDetail> results = new ArrayList<>();
         if (mWificondScanner == null) {
             Log.e(TAG, "No valid wificond scanner interface handler");
             return results;
         }
         try {
-            NativeScanResult[] nativeResults = mWificondScanner.getScanResults();
+            NativeScanResult[] nativeResults;
+            if (scanType == SCAN_TYPE_SINGLE_SCAN) {
+                nativeResults = mWificondScanner.getScanResults();
+            } else {
+                nativeResults = mWificondScanner.getPnoScanResults();
+            }
             for (NativeScanResult result : nativeResults) {
                 WifiSsid wifiSsid = WifiSsid.createFromByteArray(result.ssid);
                 String bssid;
@@ -345,15 +374,26 @@
                         new InformationElementUtil.Capabilities();
                 capabilities.from(ies, result.capability);
                 String flags = capabilities.generateCapabilitiesString();
-                NetworkDetail networkDetail =
-                        new NetworkDetail(bssid, ies, null, result.frequency);
-
-                if (!wifiSsid.toString().equals(networkDetail.getTrimmedSSID())) {
-                    Log.e(TAG, "Inconsistent SSID on BSSID: " + bssid);
+                NetworkDetail networkDetail;
+                try {
+                    networkDetail = new NetworkDetail(bssid, ies, null, result.frequency);
+                } catch (IllegalArgumentException e) {
+                    Log.e(TAG, "Illegal argument for scan result with bssid: " + bssid, e);
                     continue;
                 }
+
                 ScanDetail scanDetail = new ScanDetail(networkDetail, wifiSsid, bssid, flags,
                         result.signalMbm / 100, result.frequency, result.tsf, ies, null);
+                // Update carrier network info if this AP's SSID is associated with a carrier Wi-Fi
+                // network and it uses EAP.
+                if (ScanResultUtil.isScanResultForEapNetwork(scanDetail.getScanResult())
+                        && mCarrierNetworkConfig.isCarrierNetwork(wifiSsid.toString())) {
+                    scanDetail.getScanResult().isCarrierAp = true;
+                    scanDetail.getScanResult().carrierApEapType =
+                            mCarrierNetworkConfig.getNetworkEapType(wifiSsid.toString());
+                    scanDetail.getScanResult().carrierName =
+                            mCarrierNetworkConfig.getCarrierName(wifiSsid.toString());
+                }
                 results.add(scanDetail);
             }
         } catch (RemoteException e1) {
@@ -441,9 +481,14 @@
         }
 
         try {
-            return mWificondScanner.startPnoScan(settings);
+            boolean success = mWificondScanner.startPnoScan(settings);
+            mWifiInjector.getWifiMetrics().incrementPnoScanStartAttempCount();
+            if (!success) {
+                mWifiInjector.getWifiMetrics().incrementPnoScanFailedCount();
+            }
+            return success;
         } catch (RemoteException e1) {
-            Log.e(TAG, "Failed to stop pno scan due to remote exception");
+            Log.e(TAG, "Failed to start pno scan due to remote exception");
         }
         return false;
     }
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java b/service/java/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java
index 04bf2e0..efeca65 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java
@@ -33,6 +33,7 @@
 import android.net.NetworkRequest;
 import android.net.NetworkSpecifier;
 import android.net.RouteInfo;
+import android.net.wifi.aware.WifiAwareAgentNetworkSpecifier;
 import android.net.wifi.aware.WifiAwareManager;
 import android.net.wifi.aware.WifiAwareNetworkSpecifier;
 import android.net.wifi.aware.WifiAwareUtils;
@@ -59,9 +60,11 @@
 import java.util.Arrays;
 import java.util.Enumeration;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
 
 /**
  * Manages Aware data-path lifetime: interface creation/deletion, data-path setup and tear-down.
@@ -85,8 +88,8 @@
     private static final int NETWORK_FACTORY_SIGNAL_STRENGTH_AVAIL = 1;
 
     private final WifiAwareStateManager mMgr;
-    private final NetworkInterfaceWrapper mNiWrapper = new NetworkInterfaceWrapper();
-    private final NetworkCapabilities mNetworkCapabilitiesFilter = new NetworkCapabilities();
+    public NetworkInterfaceWrapper mNiWrapper = new NetworkInterfaceWrapper();
+    private static final NetworkCapabilities sNetworkCapabilitiesFilter = new NetworkCapabilities();
     private final Set<String> mInterfaces = new HashSet<>();
     private final Map<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation>
             mNetworkRequestsCache = new ArrayMap<>();
@@ -95,7 +98,7 @@
     private WifiPermissionsWrapper mPermissionsWrapper;
     private Looper mLooper;
     private WifiAwareNetworkFactory mNetworkFactory;
-    private INetworkManagementService mNwService;
+    public INetworkManagementService mNwService;
 
     public WifiAwareDataPathStateManager(WifiAwareStateManager mgr) {
         mMgr = mgr;
@@ -114,19 +117,19 @@
         mPermissionsWrapper = permissionsWrapper;
         mLooper = looper;
 
-        mNetworkCapabilitiesFilter.clearAll();
-        mNetworkCapabilitiesFilter.addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE);
-        mNetworkCapabilitiesFilter
+        sNetworkCapabilitiesFilter.clearAll();
+        sNetworkCapabilitiesFilter.addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE);
+        sNetworkCapabilitiesFilter
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED);
-        mNetworkCapabilitiesFilter.setNetworkSpecifier(new MatchAllNetworkSpecifier());
-        mNetworkCapabilitiesFilter.setLinkUpstreamBandwidthKbps(NETWORK_FACTORY_BANDWIDTH_AVAIL);
-        mNetworkCapabilitiesFilter.setLinkDownstreamBandwidthKbps(NETWORK_FACTORY_BANDWIDTH_AVAIL);
-        mNetworkCapabilitiesFilter.setSignalStrength(NETWORK_FACTORY_SIGNAL_STRENGTH_AVAIL);
+        sNetworkCapabilitiesFilter.setNetworkSpecifier(new MatchAllNetworkSpecifier());
+        sNetworkCapabilitiesFilter.setLinkUpstreamBandwidthKbps(NETWORK_FACTORY_BANDWIDTH_AVAIL);
+        sNetworkCapabilitiesFilter.setLinkDownstreamBandwidthKbps(NETWORK_FACTORY_BANDWIDTH_AVAIL);
+        sNetworkCapabilitiesFilter.setSignalStrength(NETWORK_FACTORY_SIGNAL_STRENGTH_AVAIL);
 
-        mNetworkFactory = new WifiAwareNetworkFactory(looper, context, mNetworkCapabilitiesFilter);
+        mNetworkFactory = new WifiAwareNetworkFactory(looper, context, sNetworkCapabilitiesFilter);
         mNetworkFactory.setScoreFilter(NETWORK_FACTORY_SCORE_AVAIL);
         mNetworkFactory.register();
 
@@ -146,6 +149,18 @@
         return null;
     }
 
+    private Map.Entry<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation>
+                getNetworkRequestByCanonicalDescriptor(CanonicalConnectionInfo cci) {
+        for (Map.Entry<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation> entry :
+                mNetworkRequestsCache.entrySet()) {
+            if (entry.getValue().getCanonicalDescriptor().equals(cci)) {
+                return entry;
+            }
+        }
+
+        return null;
+    }
+
     /**
      * Create all Aware data-path interfaces which are possible on the device - based on the
      * capabilities of the firmware.
@@ -247,7 +262,7 @@
             return;
         }
 
-        nnri.state = AwareNetworkRequestInformation.STATE_INITIATOR_WAIT_FOR_CONFIRM;
+        nnri.state = AwareNetworkRequestInformation.STATE_WAIT_FOR_CONFIRM;
         nnri.ndpId = ndpId;
     }
 
@@ -309,6 +324,8 @@
              * - The discovery session (pub/sub ID) must match.
              * - The peer MAC address (if specified - i.e. non-null) must match. A null peer MAC ==
              *   accept (otherwise matching) requests from any peer MAC.
+             * - The request must be pending (i.e. we could have completed requests for the same
+             *   parameters)
              */
             if (entry.getValue().pubSubId != 0 && entry.getValue().pubSubId != pubSubId) {
                 continue;
@@ -319,6 +336,11 @@
                 continue;
             }
 
+            if (entry.getValue().state
+                    != AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_REQUEST) {
+                continue;
+            }
+
             networkSpecifier = entry.getKey();
             nnri = entry.getValue();
             break;
@@ -411,7 +433,7 @@
             return;
         }
 
-        nnri.state = AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_CONFIRM;
+        nnri.state = AwareNetworkRequestInformation.STATE_WAIT_FOR_CONFIRM;
     }
 
     /**
@@ -452,18 +474,8 @@
         AwareNetworkRequestInformation nnri = nnriE.getValue();
 
         // validate state
-        if (nnri.networkSpecifier.role == WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
-                && nnri.state != AwareNetworkRequestInformation.STATE_INITIATOR_WAIT_FOR_CONFIRM) {
-            Log.w(TAG, "onDataPathConfirm: INITIATOR in invalid state=" + nnri.state);
-            mNetworkRequestsCache.remove(networkSpecifier);
-            if (accept) {
-                mMgr.endDataPath(ndpId);
-            }
-            return networkSpecifier;
-        }
-        if (nnri.networkSpecifier.role == WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER
-                && nnri.state != AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_CONFIRM) {
-            Log.w(TAG, "onDataPathConfirm: RESPONDER in invalid state=" + nnri.state);
+        if (nnri.state != AwareNetworkRequestInformation.STATE_WAIT_FOR_CONFIRM) {
+            Log.w(TAG, "onDataPathConfirm: invalid state=" + nnri.state);
             mNetworkRequestsCache.remove(networkSpecifier);
             if (accept) {
                 mMgr.endDataPath(ndpId);
@@ -472,38 +484,45 @@
         }
 
         if (accept) {
-            nnri.state = (nnri.networkSpecifier.role
-                    == WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR)
-                    ? AwareNetworkRequestInformation.STATE_INITIATOR_CONFIRMED
-                    : AwareNetworkRequestInformation.STATE_RESPONDER_CONFIRMED;
+            nnri.state = AwareNetworkRequestInformation.STATE_CONFIRMED;
             nnri.peerDataMac = mac;
 
-            NetworkInfo networkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI_P2P, 0,
+            NetworkInfo networkInfo = new NetworkInfo(ConnectivityManager.TYPE_NONE, 0,
                     NETWORK_TAG, "");
             NetworkCapabilities networkCapabilities = new NetworkCapabilities(
-                    mNetworkCapabilitiesFilter);
+                    sNetworkCapabilitiesFilter);
             LinkProperties linkProperties = new LinkProperties();
 
-            try {
-                mNwService.setInterfaceUp(nnri.interfaceName);
-                mNwService.enableIpv6(nnri.interfaceName);
-            } catch (Exception e) { // NwService throws runtime exceptions for errors
-                Log.e(TAG, "onDataPathConfirm: ACCEPT nnri=" + nnri + ": can't configure network - "
-                        + e);
-                mMgr.endDataPath(ndpId);
-                return networkSpecifier;
+            boolean interfaceUsedByAnotherNdp = isInterfaceUpAndUsedByAnotherNdp(nnri);
+            if (!interfaceUsedByAnotherNdp) {
+                try {
+                    mNwService.setInterfaceUp(nnri.interfaceName);
+                    mNwService.enableIpv6(nnri.interfaceName);
+                } catch (Exception e) { // NwService throws runtime exceptions for errors
+                    Log.e(TAG, "onDataPathConfirm: ACCEPT nnri=" + nnri
+                            + ": can't configure network - "
+                            + e);
+                    mMgr.endDataPath(ndpId);
+                    nnri.state = AwareNetworkRequestInformation.STATE_TERMINATING;
+                    return networkSpecifier;
+                }
+            } else {
+                if (VDBG) {
+                    Log.v(TAG, "onDataPathConfirm: interface already configured: "
+                            + nnri.interfaceName);
+                }
             }
 
-            if (!mNiWrapper.configureAgentProperties(nnri, networkSpecifier, ndpId, networkInfo,
-                    networkCapabilities, linkProperties)) {
+            if (!mNiWrapper.configureAgentProperties(nnri, nnri.equivalentSpecifiers, ndpId,
+                    networkInfo, networkCapabilities, linkProperties)) {
                 return networkSpecifier;
             }
 
             nnri.networkAgent = new WifiAwareNetworkAgent(mLooper, mContext,
                     AGENT_TAG_PREFIX + nnri.ndpId,
-                    new NetworkInfo(ConnectivityManager.TYPE_WIFI_P2P, 0, NETWORK_TAG, ""),
+                    new NetworkInfo(ConnectivityManager.TYPE_NONE, 0, NETWORK_TAG, ""),
                     networkCapabilities, linkProperties, NETWORK_FACTORY_SCORE_AVAIL,
-                    networkSpecifier, ndpId);
+                    nnri);
             nnri.networkAgent.sendNetworkInfo(networkInfo);
 
             mAwareMetrics.recordNdpStatus(NanStatusType.SUCCESS, networkSpecifier.isOutOfBand(),
@@ -541,13 +560,14 @@
             return;
         }
 
-        tearDownInterface(nnriE.getValue());
-        if (nnriE.getValue().state == AwareNetworkRequestInformation.STATE_RESPONDER_CONFIRMED
-                || nnriE.getValue().state
-                == AwareNetworkRequestInformation.STATE_INITIATOR_CONFIRMED) {
+        tearDownInterfaceIfPossible(nnriE.getValue());
+        if (nnriE.getValue().state == AwareNetworkRequestInformation.STATE_CONFIRMED
+                || nnriE.getValue().state == AwareNetworkRequestInformation.STATE_TERMINATING) {
             mAwareMetrics.recordNdpSessionDuration(nnriE.getValue().startTimestamp);
         }
         mNetworkRequestsCache.remove(nnriE.getKey());
+
+        mNetworkFactory.tickleConnectivityIfWaiting();
     }
 
     /**
@@ -557,7 +577,7 @@
         if (VDBG) Log.v(TAG, "onAwareDownCleanupDataPaths");
 
         for (AwareNetworkRequestInformation nnri : mNetworkRequestsCache.values()) {
-            tearDownInterface(nnri);
+            tearDownInterfaceIfPossible(nnri);
         }
         mNetworkRequestsCache.clear();
     }
@@ -584,13 +604,26 @@
                 nnri.networkSpecifier.isOutOfBand(), nnri.startTimestamp);
 
         mMgr.endDataPath(nnri.ndpId);
+        nnri.state = AwareNetworkRequestInformation.STATE_TERMINATING;
     }
 
     private class WifiAwareNetworkFactory extends NetworkFactory {
+        // Request received while waiting for confirmation that a canonically identical data-path
+        // (NDP) is in the process of being terminated
+        private boolean mWaitingForTermination = false;
+
         WifiAwareNetworkFactory(Looper looper, Context context, NetworkCapabilities filter) {
             super(looper, context, NETWORK_TAG, filter);
         }
 
+        public void tickleConnectivityIfWaiting() {
+            if (mWaitingForTermination) {
+                if (VDBG) Log.v(TAG, "tickleConnectivityIfWaiting: was waiting!");
+                mWaitingForTermination = false;
+                reevaluateAllRequests();
+            }
+        }
+
         @Override
         public boolean acceptRequest(NetworkRequest request, int score) {
             if (VDBG) {
@@ -628,7 +661,12 @@
             if (nnri != null) {
                 if (DBG) {
                     Log.d(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request
-                            + " - already in cache!?");
+                            + " - already in cache with state=" + nnri.state);
+                }
+
+                if (nnri.state == AwareNetworkRequestInformation.STATE_TERMINATING) {
+                    mWaitingForTermination = true;
+                    return false;
                 }
 
                 // seems to happen after a network agent is created - trying to rematch all
@@ -644,10 +682,22 @@
                 return false;
             }
 
-            // TODO (b/63635780) support more then a single concurrent NDP
-            if (mNetworkRequestsCache.size() > 0) {
-                Log.e(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request
-                        + " - >1 concurrent NDPs aren't supported (yet).");
+            // check to see if a canonical version exists
+            Map.Entry<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation> primaryRequest =
+                    getNetworkRequestByCanonicalDescriptor(nnri.getCanonicalDescriptor());
+            if (primaryRequest != null) {
+                if (VDBG) {
+                    Log.v(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request
+                            + ", already has a primary request=" + primaryRequest.getKey()
+                            + " with state=" + primaryRequest.getValue().state);
+                }
+
+                if (primaryRequest.getValue().state
+                        == AwareNetworkRequestInformation.STATE_TERMINATING) {
+                    mWaitingForTermination = true;
+                } else {
+                    primaryRequest.getValue().updateToSupportNewRequest(networkSpecifier);
+                }
                 return false;
             }
 
@@ -676,18 +726,17 @@
                 return;
             }
 
+            if (nnri.state != AwareNetworkRequestInformation.STATE_IDLE) {
+                if (DBG) {
+                    Log.d(TAG, "WifiAwareNetworkFactory.needNetworkFor: networkRequest="
+                            + networkRequest + " - already in progress");
+                    // TODO: understand how/when can be called again/while in progress (seems
+                    // to be related to score re-calculation after a network agent is created)
+                }
+                return;
+            }
             if (nnri.networkSpecifier.role
                     == WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR) {
-                if (nnri.state != AwareNetworkRequestInformation.STATE_INITIATOR_IDLE) {
-                    if (DBG) {
-                        Log.d(TAG, "WifiAwareNetworkFactory.needNetworkFor: networkRequest="
-                                + networkRequest + " - already in progress");
-                        // TODO: understand how/when can be called again/while in progress (seems
-                        // to be related to score re-calculation after a network agent is created)
-                    }
-                    return;
-                }
-
                 nnri.interfaceName = selectInterfaceForRequest(nnri);
                 if (nnri.interfaceName == null) {
                     Log.w(TAG, "needNetworkFor: request " + networkSpecifier
@@ -704,16 +753,6 @@
                         AwareNetworkRequestInformation.STATE_INITIATOR_WAIT_FOR_REQUEST_RESPONSE;
                 nnri.startTimestamp = SystemClock.elapsedRealtime();
             } else {
-                if (nnri.state != AwareNetworkRequestInformation.STATE_RESPONDER_IDLE) {
-                    if (DBG) {
-                        Log.d(TAG, "WifiAwareNetworkFactory.needNetworkFor: networkRequest="
-                                + networkRequest + " - already in progress");
-                        // TODO: understand how/when can be called again/while in progress (seems
-                        // to be related to score re-calculation after a network agent is created)
-                    }
-                    return;
-                }
-
                 nnri.state = AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_REQUEST;
             }
         }
@@ -749,45 +788,52 @@
                 return;
             }
 
-            if (nnri.networkSpecifier.role == WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
-                    && nnri.state
-                    > AwareNetworkRequestInformation.STATE_INITIATOR_WAIT_FOR_REQUEST_RESPONSE) {
-                mMgr.endDataPath(nnri.ndpId);
+            /*
+             * Since there's no agent it means we're in the process of setting up the NDP.
+             * However, it is possible that there were other equivalent requests for this NDP. We
+             * should keep going in that case.
+             */
+            nnri.removeSupportForRequest(networkSpecifier);
+            if (nnri.equivalentSpecifiers.isEmpty()) {
+                if (VDBG) {
+                    Log.v(TAG, "releaseNetworkFor: there are no further requests, networkRequest="
+                            + networkRequest);
+                }
+                if (nnri.ndpId != 0) { // 0 is never a valid ID!
+                    if (VDBG) Log.v(TAG, "releaseNetworkFor: in progress NDP being terminated");
+                    mMgr.endDataPath(nnri.ndpId);
+                    nnri.state = AwareNetworkRequestInformation.STATE_TERMINATING;
+                }
+            } else {
+                if (VDBG) {
+                    Log.v(TAG, "releaseNetworkFor: equivalent requests exist - not terminating "
+                            + "networkRequest=" + networkRequest);
+                }
             }
-            if (nnri.networkSpecifier.role == WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER
-                    && nnri.state
-                    > AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_REQUEST) {
-                mMgr.endDataPath(nnri.ndpId);
-            }
-
-            // Will get a callback (on both initiator and responder) when data-path actually
-            // terminated. At that point will inform the agent and will clear the cache.
         }
     }
 
     private class WifiAwareNetworkAgent extends NetworkAgent {
         private NetworkInfo mNetworkInfo;
-        private WifiAwareNetworkSpecifier mNetworkSpecifier;
-        private int mNdpId;
+        private AwareNetworkRequestInformation mAwareNetworkRequestInfo;
 
         WifiAwareNetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
                 NetworkCapabilities nc, LinkProperties lp, int score,
-                WifiAwareNetworkSpecifier networkSpecifier, int ndpId) {
+                AwareNetworkRequestInformation anri) {
             super(looper, context, logTag, ni, nc, lp, score);
 
             mNetworkInfo = ni;
-            mNetworkSpecifier = networkSpecifier;
-            mNdpId = ndpId;
+            mAwareNetworkRequestInfo = anri;
         }
 
         @Override
         protected void unwanted() {
             if (VDBG) {
-                Log.v(TAG, "WifiAwareNetworkAgent.unwanted: networkSpecifier=" + mNetworkSpecifier
-                        + ", ndpId=" + mNdpId);
+                Log.v(TAG, "WifiAwareNetworkAgent.unwanted: request=" + mAwareNetworkRequestInfo);
             }
 
-            mMgr.endDataPath(mNdpId);
+            mMgr.endDataPath(mAwareNetworkRequestInfo.ndpId);
+            mAwareNetworkRequestInfo.state = AwareNetworkRequestInformation.STATE_TERMINATING;
 
             // Will get a callback (on both initiator and responder) when data-path actually
             // terminated. At that point will inform the agent and will clear the cache.
@@ -795,8 +841,8 @@
 
         void reconfigureAgentAsDisconnected() {
             if (VDBG) {
-                Log.v(TAG, "WifiAwareNetworkAgent.reconfigureAgentAsDisconnected: networkSpecifier="
-                        + mNetworkSpecifier + ", ndpId=" + mNdpId);
+                Log.v(TAG, "WifiAwareNetworkAgent.reconfigureAgentAsDisconnected: request="
+                        + mAwareNetworkRequestInfo);
             }
 
             mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null, "");
@@ -804,15 +850,23 @@
         }
     }
 
-    private void tearDownInterface(AwareNetworkRequestInformation nnri) {
-        if (VDBG) Log.v(TAG, "tearDownInterface: nnri=" + nnri);
+    private void tearDownInterfaceIfPossible(AwareNetworkRequestInformation nnri) {
+        if (VDBG) Log.v(TAG, "tearDownInterfaceIfPossible: nnri=" + nnri);
 
-        if (nnri.interfaceName != null && !nnri.interfaceName.isEmpty()) {
-            try {
-                mNwService.setInterfaceDown(nnri.interfaceName);
-            } catch (Exception e) { // NwService throws runtime exceptions for errors
-                Log.e(TAG,
-                        "tearDownInterface: nnri=" + nnri + ": can't bring interface down - " + e);
+        if (!TextUtils.isEmpty(nnri.interfaceName)) {
+            boolean interfaceUsedByAnotherNdp = isInterfaceUpAndUsedByAnotherNdp(nnri);
+            if (interfaceUsedByAnotherNdp) {
+                if (VDBG) {
+                    Log.v(TAG, "tearDownInterfaceIfPossible: interfaceName=" + nnri.interfaceName
+                            + ", still in use - not turning down");
+                }
+            } else {
+                try {
+                    mNwService.setInterfaceDown(nnri.interfaceName);
+                } catch (Exception e) { // NwService throws runtime exceptions for errors
+                    Log.e(TAG, "tearDownInterfaceIfPossible: nnri=" + nnri
+                            + ": can't bring interface down - " + e);
+                }
             }
         }
 
@@ -821,19 +875,60 @@
         }
     }
 
-    /**
-     * Select one of the existing interfaces for the new network request.
-     *
-     * TODO: for now there is only a single interface - simply pick it.
-     */
-    private String selectInterfaceForRequest(AwareNetworkRequestInformation req) {
-        Iterator<String> it = mInterfaces.iterator();
-        if (it.hasNext()) {
-            return it.next();
+    private boolean isInterfaceUpAndUsedByAnotherNdp(AwareNetworkRequestInformation nri) {
+        for (AwareNetworkRequestInformation lnri : mNetworkRequestsCache.values()) {
+            if (lnri == nri) {
+                continue;
+            }
+
+            if (nri.interfaceName.equals(lnri.interfaceName) && (
+                    lnri.state == AwareNetworkRequestInformation.STATE_CONFIRMED
+                            || lnri.state == AwareNetworkRequestInformation.STATE_TERMINATING)) {
+                return true;
+            }
         }
 
-        Log.e(TAG, "selectInterfaceForRequest: req=" + req + " - but no interfaces available!");
+        return false;
+    }
 
+    /**
+     * Select one of the existing interfaces for the new network request. A request is canonical
+     * (otherwise it wouldn't be executed).
+     *
+     * Construct a list of all interfaces currently used to communicate to the peer. The remaining
+     * interfaces are available for use for this request - if none are left then the request should
+     * fail (signaled to the caller by returning a null).
+     */
+    private String selectInterfaceForRequest(AwareNetworkRequestInformation req) {
+        SortedSet<String> potential = new TreeSet<>(mInterfaces);
+        Set<String> used = new HashSet<>();
+
+        if (VDBG) {
+            Log.v(TAG, "selectInterfaceForRequest: req=" + req + ", mNetworkRequestsCache="
+                    + mNetworkRequestsCache);
+        }
+
+        for (AwareNetworkRequestInformation nnri : mNetworkRequestsCache.values()) {
+            if (nnri == req) {
+                continue;
+            }
+
+            if (Arrays.equals(req.peerDiscoveryMac, nnri.peerDiscoveryMac)) {
+                used.add(nnri.interfaceName);
+            }
+        }
+
+        if (VDBG) {
+            Log.v(TAG, "selectInterfaceForRequest: potential=" + potential + ", used=" + used);
+        }
+
+        for (String ifName: potential) {
+            if (!used.contains(ifName)) {
+                return ifName;
+            }
+        }
+
+        Log.e(TAG, "selectInterfaceForRequest: req=" + req + " - no interfaces available!");
         return null;
     }
 
@@ -853,16 +948,13 @@
      */
     @VisibleForTesting
     public static class AwareNetworkRequestInformation {
-        static final int STATE_INITIATOR_IDLE = 100;
-        static final int STATE_INITIATOR_WAIT_FOR_REQUEST_RESPONSE = 101;
-        static final int STATE_INITIATOR_WAIT_FOR_CONFIRM = 102;
-        static final int STATE_INITIATOR_CONFIRMED = 103;
-
-        static final int STATE_RESPONDER_IDLE = 200;
-        static final int STATE_RESPONDER_WAIT_FOR_REQUEST = 201;
-        static final int STATE_RESPONDER_WAIT_FOR_RESPOND_RESPONSE = 202;
-        static final int STATE_RESPONDER_WAIT_FOR_CONFIRM = 203;
-        static final int STATE_RESPONDER_CONFIRMED = 204;
+        static final int STATE_IDLE = 100;
+        static final int STATE_WAIT_FOR_CONFIRM = 101;
+        static final int STATE_CONFIRMED = 102;
+        static final int STATE_INITIATOR_WAIT_FOR_REQUEST_RESPONSE = 103;
+        static final int STATE_RESPONDER_WAIT_FOR_REQUEST = 104;
+        static final int STATE_RESPONDER_WAIT_FOR_RESPOND_RESPONSE = 105;
+        static final int STATE_TERMINATING = 106;
 
         public int state;
 
@@ -871,13 +963,55 @@
         public int pubSubId = 0;
         public int peerInstanceId = 0;
         public byte[] peerDiscoveryMac = null;
-        public int ndpId;
+        public int ndpId = 0; // 0 is never a valid ID!
         public byte[] peerDataMac;
         public WifiAwareNetworkSpecifier networkSpecifier;
         public long startTimestamp = 0; // request is made (initiator) / get request (responder)
 
         public WifiAwareNetworkAgent networkAgent;
 
+        /* A collection of specifiers which are equivalent to the current request and are
+         * supported by it's agent. This list DOES include the original (first) network specifier
+         * (which is stored separately above).
+         */
+        public Set<WifiAwareNetworkSpecifier> equivalentSpecifiers = new HashSet<>();
+
+        void updateToSupportNewRequest(WifiAwareNetworkSpecifier ns) {
+            if (VDBG) Log.v(TAG, "updateToSupportNewRequest: ns=" + ns);
+            if (equivalentSpecifiers.add(ns) && state == STATE_CONFIRMED) {
+                if (networkAgent == null) {
+                    Log.wtf(TAG, "updateToSupportNewRequest: null agent in CONFIRMED state!?");
+                    return;
+                }
+
+                networkAgent.sendNetworkCapabilities(getNetworkCapabilities());
+            }
+        }
+
+        void removeSupportForRequest(WifiAwareNetworkSpecifier ns) {
+            if (VDBG) Log.v(TAG, "removeSupportForRequest: ns=" + ns);
+            equivalentSpecifiers.remove(ns);
+
+            // we will not update the agent:
+            // 1. this will only get called before the agent is created
+            // 2. connectivity service does not allow (WTF) updates with reduced capabilities
+        }
+
+        private NetworkCapabilities getNetworkCapabilities() {
+            NetworkCapabilities nc = new NetworkCapabilities(sNetworkCapabilitiesFilter);
+            nc.setNetworkSpecifier(new WifiAwareAgentNetworkSpecifier(equivalentSpecifiers.toArray(
+                    new WifiAwareNetworkSpecifier[equivalentSpecifiers.size()])));
+            return nc;
+        }
+
+        /**
+         * Returns a canonical descriptor for the network request.
+         */
+        CanonicalConnectionInfo getCanonicalDescriptor() {
+            return new CanonicalConnectionInfo(peerDiscoveryMac, networkSpecifier.pmk,
+                    networkSpecifier.sessionId, networkSpecifier.passphrase);
+        }
+
         static AwareNetworkRequestInformation processNetworkSpecifier(WifiAwareNetworkSpecifier ns,
                 WifiAwareStateManager mgr, WifiPermissionsWrapper permissionWrapper) {
             int uid, pubSubId = 0;
@@ -1001,14 +1135,13 @@
 
             // create container and populate
             AwareNetworkRequestInformation nnri = new AwareNetworkRequestInformation();
-            nnri.state = (ns.role == WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR)
-                    ? AwareNetworkRequestInformation.STATE_INITIATOR_IDLE
-                    : AwareNetworkRequestInformation.STATE_RESPONDER_IDLE;
+            nnri.state = AwareNetworkRequestInformation.STATE_IDLE;
             nnri.uid = uid;
             nnri.pubSubId = pubSubId;
             nnri.peerInstanceId = peerInstanceId;
             nnri.peerDiscoveryMac = peerMac;
             nnri.networkSpecifier = ns;
+            nnri.equivalentSpecifiers.add(ns);
 
             return nnri;
         }
@@ -1025,7 +1158,69 @@
                     ", ndpId=").append(ndpId).append(", peerDataMac=").append(
                     peerDataMac == null ? ""
                             : String.valueOf(HexEncoding.encode(peerDataMac))).append(
-                    ", startTimestamp=").append(startTimestamp);
+                    ", startTimestamp=").append(startTimestamp).append(", equivalentSpecifiers=[");
+            for (WifiAwareNetworkSpecifier ns: equivalentSpecifiers) {
+                sb.append(ns.toString()).append(", ");
+            }
+            return sb.append("]").toString();
+        }
+    }
+
+    /**
+     * A canonical (unique) descriptor of the peer connection.
+     */
+    static class CanonicalConnectionInfo {
+        CanonicalConnectionInfo(byte[] peerDiscoveryMac, byte[] pmk, int sessionId,
+                String passphrase) {
+            this.peerDiscoveryMac = peerDiscoveryMac;
+            this.pmk = pmk;
+            this.sessionId = sessionId;
+            this.passphrase = passphrase;
+        }
+
+        public final byte[] peerDiscoveryMac;
+
+        /*
+         * Security configuration matching:
+         * - open: pmk/passphrase = null
+         * - pmk: pmk != null, passphrase = null
+         * - passphrase: passphrase != null, sessionId used (==0 for OOB), pmk=null
+         */
+        public final byte[] pmk;
+
+        public final int sessionId;
+        public final String passphrase;
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(Arrays.hashCode(peerDiscoveryMac), Arrays.hashCode(pmk), sessionId,
+                passphrase);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+
+            if (!(obj instanceof CanonicalConnectionInfo)) {
+                return false;
+            }
+
+            CanonicalConnectionInfo lhs = (CanonicalConnectionInfo) obj;
+
+            return Arrays.equals(peerDiscoveryMac, lhs.peerDiscoveryMac) && Arrays.equals(pmk,
+                    lhs.pmk) && TextUtils.equals(passphrase, lhs.passphrase)
+                    && sessionId == lhs.sessionId;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder("CanonicalConnectionInfo: [");
+            sb.append("peerDiscoveryMac=").append(peerDiscoveryMac == null ? ""
+                    : String.valueOf(HexEncoding.encode(peerDiscoveryMac))).append("pmk=").append(
+                    pmk == null ? "" : "*").append("sessionId=").append(sessionId).append(
+                    "passphrase=").append(passphrase == null ? "" : "*").append("]");
             return sb.toString();
         }
     }
@@ -1040,8 +1235,9 @@
          * name. Delegated to enable mocking.
          */
         public boolean configureAgentProperties(AwareNetworkRequestInformation nnri,
-                WifiAwareNetworkSpecifier networkSpecifier, int ndpId, NetworkInfo networkInfo,
-                NetworkCapabilities networkCapabilities, LinkProperties linkProperties) {
+                Set<WifiAwareNetworkSpecifier> networkSpecifiers, int ndpId,
+                NetworkInfo networkInfo, NetworkCapabilities networkCapabilities,
+                LinkProperties linkProperties) {
             // find link-local address
             InetAddress linkLocal = null;
             NetworkInterface ni;
@@ -1051,12 +1247,14 @@
                 Log.e(TAG, "onDataPathConfirm: ACCEPT nnri=" + nnri
                         + ": can't get network interface - " + e);
                 mMgr.endDataPath(ndpId);
+                nnri.state = AwareNetworkRequestInformation.STATE_TERMINATING;
                 return false;
             }
             if (ni == null) {
                 Log.e(TAG, "onDataPathConfirm: ACCEPT nnri=" + nnri
                         + ": can't get network interface (null)");
                 mMgr.endDataPath(ndpId);
+                nnri.state = AwareNetworkRequestInformation.STATE_TERMINATING;
                 return false;
             }
             Enumeration<InetAddress> addresses = ni.getInetAddresses();
@@ -1071,6 +1269,7 @@
             if (linkLocal == null) {
                 Log.e(TAG, "onDataPathConfirm: ACCEPT nnri=" + nnri + ": no link local addresses");
                 mMgr.endDataPath(ndpId);
+                nnri.state = AwareNetworkRequestInformation.STATE_TERMINATING;
                 return false;
             }
 
@@ -1078,7 +1277,8 @@
             networkInfo.setIsAvailable(true);
             networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null, null);
 
-            networkCapabilities.setNetworkSpecifier(networkSpecifier);
+            networkCapabilities.setNetworkSpecifier(new WifiAwareAgentNetworkSpecifier(
+                    networkSpecifiers.toArray(new WifiAwareNetworkSpecifier[0])));
 
             linkProperties.setInterfaceName(nnri.interfaceName);
             linkProperties.addLinkAddress(new LinkAddress(linkLocal, 64));
@@ -1095,7 +1295,7 @@
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("WifiAwareDataPathStateManager:");
         pw.println("  mInterfaces: " + mInterfaces);
-        pw.println("  mNetworkCapabilitiesFilter: " + mNetworkCapabilitiesFilter);
+        pw.println("  sNetworkCapabilitiesFilter: " + sNetworkCapabilitiesFilter);
         pw.println("  mNetworkRequestsCache: " + mNetworkRequestsCache);
         pw.println("  mNetworkFactory:");
         mNetworkFactory.dump(fd, pw, args);
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareMetrics.java b/service/java/com/android/server/wifi/aware/WifiAwareMetrics.java
index 02eaf5d..c45c6dc 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareMetrics.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareMetrics.java
@@ -320,10 +320,7 @@
                 networkRequestCache.values()) {
             if (anri.state
                     != WifiAwareDataPathStateManager.AwareNetworkRequestInformation
-                    .STATE_INITIATOR_CONFIRMED
-                    && anri.state
-                    != WifiAwareDataPathStateManager.AwareNetworkRequestInformation
-                    .STATE_RESPONDER_CONFIRMED) {
+                    .STATE_CONFIRMED) {
                 continue; // only count completed (up-and-running) NDPs
             }
 
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareNativeCallback.java b/service/java/com/android/server/wifi/aware/WifiAwareNativeCallback.java
index 05721af..2121160 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareNativeCallback.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareNativeCallback.java
@@ -167,11 +167,6 @@
                     capabilities.maxSubscribeInterfaceAddresses;
             frameworkCapabilities.supportedCipherSuites = capabilities.supportedCipherSuites;
 
-            // TODO (b/63635780, b/63635857): enable framework support of >1 NDI and >1 NDP per NDI
-            // Until then: force corresponding capabilities to 1.
-            frameworkCapabilities.maxNdiInterfaces = 1;
-            frameworkCapabilities.maxNdpSessions = 1;
-
             mWifiAwareStateManager.onCapabilitiesUpdateResponse(id, frameworkCapabilities);
         } else {
             Log.e(TAG, "notifyCapabilitiesResponse: error code=" + status.status + " ("
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareRttStateManager.java b/service/java/com/android/server/wifi/aware/WifiAwareRttStateManager.java
index 74f34b3..9d0441f 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareRttStateManager.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareRttStateManager.java
@@ -50,6 +50,7 @@
 
     private final SparseArray<WifiAwareClientState> mPendingOperations = new SparseArray<>();
     private AsyncChannel mAsyncChannel;
+    private Context mContext;
 
     /**
      * Initializes the connection to the RTT service.
@@ -74,7 +75,7 @@
     public void startWithRttService(Context context, Looper looper, IRttManager service) {
         Messenger messenger;
         try {
-            messenger = service.getMessenger();
+            messenger = service.getMessenger(null, new int[1]);
         } catch (RemoteException e) {
             Log.e(TAG, "start(): not able to getMessenger() of WIFI_RTT_SERVICE");
             return;
@@ -82,6 +83,7 @@
 
         mAsyncChannel = new AsyncChannel();
         mAsyncChannel.connect(context, new AwareRttHandler(looper), messenger);
+        mContext = context;
     }
 
     private WifiAwareClientState getAndRemovePendingOperationClient(int rangingId) {
@@ -125,7 +127,8 @@
             switch (msg.what) {
                 case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
                     if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
-                        mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
+                        mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION,
+                                new RttManager.RttClient(mContext.getPackageName()));
                     } else {
                         Log.e(TAG, "Failed to set up channel connection to RTT service");
                         mAsyncChannel = null;
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareShellCommand.java b/service/java/com/android/server/wifi/aware/WifiAwareShellCommand.java
index 41eef69..a2f6046 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareShellCommand.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareShellCommand.java
@@ -55,6 +55,7 @@
                 for (DelegatedShellCommand dsc: mDelegatedCommands.values()) {
                     dsc.onReset();
                 }
+                return 0;
             } else {
                 DelegatedShellCommand delegatedCmd = null;
                 if (!TextUtils.isEmpty(cmd)) {
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareStateManager.java b/service/java/com/android/server/wifi/aware/WifiAwareStateManager.java
index 30aa42a..6ced948 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareStateManager.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareStateManager.java
@@ -204,7 +204,7 @@
     private volatile Characteristics mCharacteristics = null;
     private WifiAwareStateMachine mSm;
     private WifiAwareRttStateManager mRtt;
-    private WifiAwareDataPathStateManager mDataPathMgr;
+    public WifiAwareDataPathStateManager mDataPathMgr;
     private PowerManager mPowerManager;
 
     private final SparseArray<WifiAwareClientState> mClients = new SparseArray<>();
diff --git a/service/java/com/android/server/wifi/hotspot2/LegacyPasspointConfigParser.java b/service/java/com/android/server/wifi/hotspot2/LegacyPasspointConfigParser.java
deleted file mode 100644
index 31795f1..0000000
--- a/service/java/com/android/server/wifi/hotspot2/LegacyPasspointConfigParser.java
+++ /dev/null
@@ -1,513 +0,0 @@
-/*
- * Copyright (C) 2017 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.hotspot2;
-
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Pair;
-
-import java.io.BufferedReader;
-import java.io.FileReader;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Utility class for parsing legacy (N and older) Passpoint configuration file content
- * (/data/misc/wifi/PerProviderSubscription.conf).  In N and older, only Release 1 is supported.
- *
- * This class only retrieve the relevant Release 1 configuration fields that are not backed
- * elsewhere.  Below are relevant fields:
- * - FQDN (used for linking with configuration data stored elsewhere)
- * - Friendly Name
- * - Roaming Consortium
- * - Realm
- * - IMSI (for SIM credential)
- *
- * Below is an example content of a Passpoint configuration file:
- *
- * tree 3:1.2(urn:wfa:mo:hotspot2dot0-perprovidersubscription:1.0)
- * 8:MgmtTree+
- * 17:PerProviderSubscription+
- * 4:r1i1+
- * 6:HomeSP+
- * c:FriendlyName=d:Test Provider
- * 4:FQDN=8:test.net
- * 13:RoamingConsortiumOI=9:1234,5678
- * .
- * a:Credential+
- * 10:UsernamePassword+
- * 8:Username=4:user
- * 8:Password=4:pass
- *
- * 9:EAPMethod+
- * 7:EAPType=2:21
- * b:InnerMethod=3:PAP
- * .
- * .
- * 5:Realm=a:boingo.com
- * .
- * .
- * .
- * .
- *
- * Each string is prefixed with a "|StringBytesInHex|:".
- * '+' indicates start of a new internal node.
- * '.' indicates end of the current internal node.
- * '=' indicates "value" of a leaf node.
- *
- */
-public class LegacyPasspointConfigParser {
-    private static final String TAG = "LegacyPasspointConfigParser";
-
-    private static final String TAG_MANAGEMENT_TREE = "MgmtTree";
-    private static final String TAG_PER_PROVIDER_SUBSCRIPTION = "PerProviderSubscription";
-    private static final String TAG_HOMESP = "HomeSP";
-    private static final String TAG_FQDN = "FQDN";
-    private static final String TAG_FRIENDLY_NAME = "FriendlyName";
-    private static final String TAG_ROAMING_CONSORTIUM_OI = "RoamingConsortiumOI";
-    private static final String TAG_CREDENTIAL = "Credential";
-    private static final String TAG_REALM = "Realm";
-    private static final String TAG_SIM = "SIM";
-    private static final String TAG_IMSI = "IMSI";
-
-    private static final String LONG_ARRAY_SEPARATOR = ",";
-    private static final String END_OF_INTERNAL_NODE_INDICATOR = ".";
-    private static final char START_OF_INTERNAL_NODE_INDICATOR = '+';
-    private static final char STRING_PREFIX_INDICATOR = ':';
-    private static final char STRING_VALUE_INDICATOR = '=';
-
-    /**
-     * An abstraction for a node within a tree.  A node can be an internal node (contained
-     * children nodes) or a leaf node (contained a String value).
-     */
-    private abstract static class Node {
-        private final String mName;
-        Node(String name) {
-            mName = name;
-        }
-
-        /**
-         * @return the name of the node
-         */
-        public String getName() {
-            return mName;
-        }
-
-        /**
-         * Applies for internal node only.
-         *
-         * @return the list of children nodes.
-         */
-        public abstract List<Node> getChildren();
-
-        /**
-         * Applies for leaf node only.
-         *
-         * @return the string value of the node
-         */
-        public abstract String getValue();
-    }
-
-    /**
-     * Class representing an internal node of a tree.  It contained a list of child nodes.
-     */
-    private static class InternalNode extends Node {
-        private final List<Node> mChildren;
-        InternalNode(String name, List<Node> children) {
-            super(name);
-            mChildren = children;
-        }
-
-        @Override
-        public List<Node> getChildren() {
-            return mChildren;
-        }
-
-        @Override
-        public String getValue() {
-            return null;
-        }
-    }
-
-    /**
-     * Class representing a leaf node of a tree.  It contained a String type value.
-     */
-    private static class LeafNode extends Node {
-        private final String mValue;
-        LeafNode(String name, String value) {
-            super(name);
-            mValue = value;
-        }
-
-        @Override
-        public List<Node> getChildren() {
-            return null;
-        }
-
-        @Override
-        public String getValue() {
-            return mValue;
-        }
-    }
-
-    public LegacyPasspointConfigParser() {}
-
-    /**
-     * Parse the legacy Passpoint configuration file content, only retrieve the relevant
-     * configurations that are not saved elsewhere.
-     *
-     * For both N and M, only Release 1 is supported. Most of the configurations are saved
-     * elsewhere as part of the {@link android.net.wifi.WifiConfiguration} data.
-     * The configurations needed from the legacy Passpoint configuration file are:
-     *
-     * - FQDN - needed to be able to link to the associated {@link WifiConfiguration} data
-     * - Friendly Name
-     * - Roaming Consortium OIs
-     * - Realm
-     * - IMSI (for SIM credential)
-     *
-     * Make this function non-static so that it can be mocked during unit test.
-     *
-     * @param fileName The file name of the configuration file
-     * @return Map of FQDN to {@link LegacyPasspointConfig}
-     * @throws IOException
-     */
-    public Map<String, LegacyPasspointConfig> parseConfig(String fileName)
-            throws IOException {
-        Map<String, LegacyPasspointConfig> configs = new HashMap<>();
-        BufferedReader in = new BufferedReader(new FileReader(fileName));
-        in.readLine();      // Ignore the first line which contained the header.
-
-        // Convert the configuration data to a management tree represented by a root {@link Node}.
-        Node root = buildNode(in);
-
-        if (root == null || root.getChildren() == null) {
-            Log.d(TAG, "Empty configuration data");
-            return configs;
-        }
-
-        // Verify root node name.
-        if (!TextUtils.equals(TAG_MANAGEMENT_TREE, root.getName())) {
-            throw new IOException("Unexpected root node: " + root.getName());
-        }
-
-        // Process and retrieve the configuration from each PPS (PerProviderSubscription) node.
-        List<Node> ppsNodes = root.getChildren();
-        for (Node ppsNode : ppsNodes) {
-            LegacyPasspointConfig config = processPpsNode(ppsNode);
-            configs.put(config.mFqdn, config);
-        }
-        return configs;
-    }
-
-    /**
-     * Build a {@link Node} from the current line in the buffer.  A node can be an internal
-     * node (ends with '+') or a leaf node.
-     *
-     * @param in Input buffer to read data from
-     * @return {@link Node} representing the current line
-     * @throws IOException
-     */
-    private static Node buildNode(BufferedReader in) throws IOException {
-        // Read until non-empty line.
-        String currentLine = null;
-        while ((currentLine = in.readLine()) != null) {
-            if (!currentLine.isEmpty()) {
-                break;
-            }
-        }
-
-        // Return null if EOF is reached.
-        if (currentLine == null) {
-            return null;
-        }
-
-        // Remove the leading and the trailing whitespaces.
-        currentLine = currentLine.trim();
-
-        // Check for the internal node terminator.
-        if (TextUtils.equals(END_OF_INTERNAL_NODE_INDICATOR, currentLine)) {
-            return null;
-        }
-
-        // Parse the name-value of the current line.  The value will be null if the current line
-        // is not a leaf node (e.g. line ends with a '+').
-        // Each line is encoded in UTF-8.
-        Pair<String, String> nameValuePair =
-                parseLine(currentLine.getBytes(StandardCharsets.UTF_8));
-        if (nameValuePair.second != null) {
-            return new LeafNode(nameValuePair.first, nameValuePair.second);
-        }
-
-        // Parse the children contained under this internal node.
-        List<Node> children = new ArrayList<>();
-        Node child = null;
-        while ((child = buildNode(in)) != null) {
-            children.add(child);
-        }
-        return new InternalNode(nameValuePair.first, children);
-    }
-
-    /**
-     * Process a PPS (PerProviderSubscription) node to retrieve Passpoint configuration data.
-     *
-     * @param ppsNode The PPS node to process
-     * @return {@link LegacyPasspointConfig}
-     * @throws IOException
-     */
-    private static LegacyPasspointConfig processPpsNode(Node ppsNode) throws IOException {
-        if (ppsNode.getChildren() == null || ppsNode.getChildren().size() != 1) {
-            throw new IOException("PerProviderSubscription node should contain "
-                    + "one instance node");
-        }
-
-        if (!TextUtils.equals(TAG_PER_PROVIDER_SUBSCRIPTION, ppsNode.getName())) {
-            throw new IOException("Unexpected name for PPS node: " + ppsNode.getName());
-        }
-
-        // Retrieve the PPS instance node.
-        Node instanceNode = ppsNode.getChildren().get(0);
-        if (instanceNode.getChildren() == null) {
-            throw new IOException("PPS instance node doesn't contained any children");
-        }
-
-        // Process and retrieve the relevant configurations under the PPS instance node.
-        LegacyPasspointConfig config = new LegacyPasspointConfig();
-        for (Node node : instanceNode.getChildren()) {
-            switch (node.getName()) {
-                case TAG_HOMESP:
-                    processHomeSPNode(node, config);
-                    break;
-                case TAG_CREDENTIAL:
-                    processCredentialNode(node, config);
-                    break;
-                default:
-                    Log.d(TAG, "Ignore uninterested field under PPS instance: " + node.getName());
-                    break;
-            }
-        }
-        if (config.mFqdn == null) {
-            throw new IOException("PPS instance missing FQDN");
-        }
-        return config;
-    }
-
-    /**
-     * Process a HomeSP node to retrieve configuration data into the given |config|.
-     *
-     * @param homeSpNode The HomeSP node to process
-     * @param config The config object to fill in the data
-     * @throws IOException
-     */
-    private static void processHomeSPNode(Node homeSpNode, LegacyPasspointConfig config)
-            throws IOException {
-        if (homeSpNode.getChildren() == null) {
-            throw new IOException("HomeSP node should contain at least one child node");
-        }
-
-        for (Node node : homeSpNode.getChildren()) {
-            switch (node.getName()) {
-                case TAG_FQDN:
-                    config.mFqdn = getValue(node);
-                    break;
-                case TAG_FRIENDLY_NAME:
-                    config.mFriendlyName = getValue(node);
-                    break;
-                case TAG_ROAMING_CONSORTIUM_OI:
-                    config.mRoamingConsortiumOis = parseLongArray(getValue(node));
-                    break;
-                default:
-                    Log.d(TAG, "Ignore uninterested field under HomeSP: " + node.getName());
-                    break;
-            }
-        }
-    }
-
-    /**
-     * Process a Credential node to retrieve configuration data into the given |config|.
-     *
-     * @param credentialNode The Credential node to process
-     * @param config The config object to fill in the data
-     * @throws IOException
-     */
-    private static void processCredentialNode(Node credentialNode,
-            LegacyPasspointConfig config)
-            throws IOException {
-        if (credentialNode.getChildren() == null) {
-            throw new IOException("Credential node should contain at least one child node");
-        }
-
-        for (Node node : credentialNode.getChildren()) {
-            switch (node.getName()) {
-                case TAG_REALM:
-                    config.mRealm = getValue(node);
-                    break;
-                case TAG_SIM:
-                    processSimNode(node, config);
-                    break;
-                default:
-                    Log.d(TAG, "Ignore uninterested field under Credential: " + node.getName());
-                    break;
-            }
-        }
-    }
-
-    /**
-     * Process a SIM node to retrieve configuration data into the given |config|.
-     *
-     * @param simNode The SIM node to process
-     * @param config The config object to fill in the data
-     * @throws IOException
-     */
-    private static void processSimNode(Node simNode, LegacyPasspointConfig config)
-            throws IOException {
-        if (simNode.getChildren() == null) {
-            throw new IOException("SIM node should contain at least one child node");
-        }
-
-        for (Node node : simNode.getChildren()) {
-            switch (node.getName()) {
-                case TAG_IMSI:
-                    config.mImsi = getValue(node);
-                    break;
-                default:
-                    Log.d(TAG, "Ignore uninterested field under SIM: " + node.getName());
-                    break;
-            }
-        }
-    }
-
-    /**
-     * Parse the given line in the legacy Passpoint configuration file.
-     * A line can be in the following formats:
-     * 2:ab+         // internal node
-     * 2:ab=2:bc     // leaf node
-     * .             // end of internal node
-     *
-     * @param line The line to parse
-     * @return name-value pair, a value of null indicates internal node
-     * @throws IOException
-     */
-    private static Pair<String, String> parseLine(byte[] lineBytes) throws IOException {
-        Pair<String, Integer> nameIndexPair = parseString(lineBytes, 0);
-        int currentIndex = nameIndexPair.second;
-        try {
-            if (lineBytes[currentIndex] == START_OF_INTERNAL_NODE_INDICATOR) {
-                return Pair.create(nameIndexPair.first, null);
-            }
-
-            if (lineBytes[currentIndex] != STRING_VALUE_INDICATOR) {
-                throw new IOException("Invalid line - missing both node and value indicator: "
-                        + new String(lineBytes, StandardCharsets.UTF_8));
-            }
-        } catch (IndexOutOfBoundsException e) {
-            throw new IOException("Invalid line - " + e.getMessage() + ": "
-                    + new String(lineBytes, StandardCharsets.UTF_8));
-        }
-        Pair<String, Integer> valueIndexPair = parseString(lineBytes, currentIndex + 1);
-        return Pair.create(nameIndexPair.first, valueIndexPair.first);
-    }
-
-    /**
-     * Parse a string value in the given line from the given start index.
-     * A string value is in the following format:
-     * |HexByteLength|:|String|
-     *
-     * The length value indicates the number of UTF-8 bytes in hex for the given string.
-     *
-     * For example: 3:abc
-     *
-     * @param lineBytes The UTF-8 bytes of the line to parse
-     * @param startIndex The start index from the given line to parse from
-     * @return Pair of a string value and an index pointed to character after the string value
-     * @throws IOException
-     */
-    private static Pair<String, Integer> parseString(byte[] lineBytes, int startIndex)
-            throws IOException {
-        // Locate the index that separate length and the string value.
-        int prefixIndex = -1;
-        for (int i = startIndex; i < lineBytes.length; i++) {
-            if (lineBytes[i] == STRING_PREFIX_INDICATOR) {
-                prefixIndex = i;
-                break;
-            }
-        }
-        if (prefixIndex == -1) {
-            throw new IOException("Invalid line - missing string prefix: "
-                    + new String(lineBytes, StandardCharsets.UTF_8));
-        }
-
-        try {
-            String lengthStr = new String(lineBytes, startIndex, prefixIndex - startIndex,
-                    StandardCharsets.UTF_8);
-            int length = Integer.parseInt(lengthStr, 16);
-            int strStartIndex = prefixIndex + 1;
-            // The length might account for bytes for the whitespaces, since the whitespaces are
-            // already trimmed, ignore them.
-            if ((strStartIndex + length) > lineBytes.length) {
-                length = lineBytes.length - strStartIndex;
-            }
-            return Pair.create(
-                    new String(lineBytes, strStartIndex, length, StandardCharsets.UTF_8),
-                    strStartIndex + length);
-        } catch (NumberFormatException | IndexOutOfBoundsException e) {
-            throw new IOException("Invalid line - " + e.getMessage() + ": "
-                    + new String(lineBytes, StandardCharsets.UTF_8));
-        }
-    }
-
-    /**
-     * Parse a long array from the given string.
-     *
-     * @param str The string to parse
-     * @return long[]
-     * @throws IOException
-     */
-    private static long[] parseLongArray(String str)
-            throws IOException {
-        String[] strArray = str.split(LONG_ARRAY_SEPARATOR);
-        long[] longArray = new long[strArray.length];
-        for (int i = 0; i < longArray.length; i++) {
-            try {
-                longArray[i] = Long.parseLong(strArray[i], 16);
-            } catch (NumberFormatException e) {
-                throw new IOException("Invalid long integer value: " + strArray[i]);
-            }
-        }
-        return longArray;
-    }
-
-    /**
-     * Get the String value of the given node.  An IOException will be thrown if the given
-     * node doesn't contain a String value (internal node).
-     *
-     * @param node The node to get the value from
-     * @return String
-     * @throws IOException
-     */
-    private static String getValue(Node node) throws IOException {
-        if (node.getValue() == null) {
-            throw new IOException("Attempt to retreive value from non-leaf node: "
-                    + node.getName());
-        }
-        return node.getValue();
-    }
-}
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointNetworkEvaluator.java b/service/java/com/android/server/wifi/hotspot2/PasspointNetworkEvaluator.java
index 6ff5ee9..fb81316 100644
--- a/service/java/com/android/server/wifi/hotspot2/PasspointNetworkEvaluator.java
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointNetworkEvaluator.java
@@ -115,6 +115,13 @@
         if (currentNetwork != null && TextUtils.equals(currentNetwork.SSID,
                 ScanResultUtil.createQuotedSSID(bestNetwork.mScanDetail.getSSID()))) {
             localLog("Staying with current Passpoint network " + currentNetwork.SSID);
+
+            // Update current network with the latest scan info.
+            mWifiConfigManager.setNetworkCandidateScanResult(currentNetwork.networkId,
+                    bestNetwork.mScanDetail.getScanResult(), 0);
+            mWifiConfigManager.updateScanDetailForNetwork(currentNetwork.networkId,
+                    bestNetwork.mScanDetail);
+
             connectableNetworks.add(Pair.create(bestNetwork.mScanDetail, currentNetwork));
             return currentNetwork;
         }
diff --git a/service/java/com/android/server/wifi/p2p/SupplicantP2pIfaceCallback.java b/service/java/com/android/server/wifi/p2p/SupplicantP2pIfaceCallback.java
index 802f643..b7a4b3b 100644
--- a/service/java/com/android/server/wifi/p2p/SupplicantP2pIfaceCallback.java
+++ b/service/java/com/android/server/wifi/p2p/SupplicantP2pIfaceCallback.java
@@ -30,8 +30,6 @@
 import com.android.server.wifi.p2p.WifiP2pServiceImpl.P2pStatus;
 import com.android.server.wifi.util.NativeUtil;
 
-import libcore.util.HexEncoding;
-
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -97,7 +95,6 @@
             byte[] wfdDeviceInfo) {
         WifiP2pDevice device = new WifiP2pDevice();
         device.deviceName = deviceName;
-
         if (deviceName == null) {
             Log.e(TAG, "Missing device name.");
             return;
@@ -111,8 +108,7 @@
         }
 
         try {
-            device.primaryDeviceType = new String(HexEncoding.encode(
-                    primaryDeviceType, 0, primaryDeviceType.length));
+            device.primaryDeviceType = NativeUtil.wpsDevTypeStringFromByteArray(primaryDeviceType);
         } catch (Exception e) {
             Log.e(TAG, "Could not encode device primary type.", e);
             return;
@@ -134,7 +130,6 @@
         mMonitor.broadcastP2pDeviceFound(mInterface, device);
     }
 
-
     /**
      * Used to indicate that a P2P device has been lost.
      *
diff --git a/service/java/com/android/server/wifi/scanner/HalWifiScannerImpl.java b/service/java/com/android/server/wifi/scanner/HalWifiScannerImpl.java
index 0fadd80..7d0ccba 100644
--- a/service/java/com/android/server/wifi/scanner/HalWifiScannerImpl.java
+++ b/service/java/com/android/server/wifi/scanner/HalWifiScannerImpl.java
@@ -42,7 +42,6 @@
     private final WifiNative mWifiNative;
     private final ChannelHelper mChannelHelper;
     private final WificondScannerImpl mWificondScannerDelegate;
-    private final boolean mHalBasedPnoSupported;
 
     public HalWifiScannerImpl(Context context, WifiNative wifiNative, WifiMonitor wifiMonitor,
                               Looper looper, Clock clock) {
@@ -51,9 +50,6 @@
         mWificondScannerDelegate =
                 new WificondScannerImpl(context, wifiNative, wifiMonitor, mChannelHelper,
                         looper, clock);
-
-        // We are not going to support HAL ePNO currently.
-        mHalBasedPnoSupported = false;
     }
 
     @Override
@@ -121,38 +117,22 @@
     @Override
     public boolean setHwPnoList(WifiNative.PnoSettings settings,
             WifiNative.PnoEventHandler eventHandler) {
-        if (mHalBasedPnoSupported) {
-            return mWifiNative.setPnoList(settings, eventHandler);
-        } else {
-            return mWificondScannerDelegate.setHwPnoList(settings, eventHandler);
-        }
+        return mWificondScannerDelegate.setHwPnoList(settings, eventHandler);
     }
 
     @Override
     public boolean resetHwPnoList() {
-        if (mHalBasedPnoSupported) {
-            return mWifiNative.resetPnoList();
-        } else {
-            return mWificondScannerDelegate.resetHwPnoList();
-        }
+        return mWificondScannerDelegate.resetHwPnoList();
     }
 
     @Override
     public boolean isHwPnoSupported(boolean isConnectedPno) {
-        if (mHalBasedPnoSupported) {
-            return true;
-        } else {
-            return mWificondScannerDelegate.isHwPnoSupported(isConnectedPno);
-        }
+        return mWificondScannerDelegate.isHwPnoSupported(isConnectedPno);
     }
 
     @Override
     public boolean shouldScheduleBackgroundScanForHwPno() {
-        if (mHalBasedPnoSupported) {
-            return true;
-        } else {
-            return mWificondScannerDelegate.shouldScheduleBackgroundScanForHwPno();
-        }
+        return mWificondScannerDelegate.shouldScheduleBackgroundScanForHwPno();
     }
 
     @Override
diff --git a/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java b/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
index ab2a5dc..4b8e284 100644
--- a/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
+++ b/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
@@ -2129,20 +2129,21 @@
             pw.println();
             pw.println("Latest scan results:");
             List<ScanResult> scanResults = mSingleScanStateMachine.getCachedScanResultsAsList();
-            long nowMs = System.currentTimeMillis();
+            long nowMs = mClock.getElapsedSinceBootMillis();
             if (scanResults != null && scanResults.size() != 0) {
                 pw.println("    BSSID              Frequency  RSSI  Age(sec)   SSID "
                         + "                                Flags");
                 for (ScanResult r : scanResults) {
+                    long timeStampMs = r.timestamp / 1000;
                     String age;
-                    if (r.seen <= 0) {
+                    if (timeStampMs <= 0) {
                         age = "___?___";
-                    } else if (nowMs < r.seen) {
+                    } else if (nowMs < timeStampMs) {
                         age = "  0.000";
-                    } else if (r.seen < nowMs - 1000000) {
+                    } else if (timeStampMs < nowMs - 1000000) {
                         age = ">1000.0";
                     } else {
-                        age = String.format("%3.3f", (nowMs - r.seen) / 1000.0);
+                        age = String.format("%3.3f", (nowMs - timeStampMs) / 1000.0);
                     }
                     String ssid = r.SSID == null ? "" : r.SSID;
                     pw.printf("  %17s  %9d  %5d   %7s    %-32s  %s\n",
diff --git a/service/java/com/android/server/wifi/scanner/WificondScannerImpl.java b/service/java/com/android/server/wifi/scanner/WificondScannerImpl.java
index 84105ee..ed25e0f 100644
--- a/service/java/com/android/server/wifi/scanner/WificondScannerImpl.java
+++ b/service/java/com/android/server/wifi/scanner/WificondScannerImpl.java
@@ -422,11 +422,22 @@
                 mPendingSingleScanEventHandler = null;
             }
 
-            if ((newScanSettings.backgroundScanActive || newScanSettings.singleScanActive)
-                    && !allFreqs.isEmpty()) {
-                pauseHwPnoScan();
-                Set<Integer> freqs = allFreqs.getScanFreqs();
-                boolean success = mWifiNative.scan(freqs, hiddenNetworkSSIDSet);
+            if (newScanSettings.backgroundScanActive || newScanSettings.singleScanActive) {
+                boolean success = false;
+                Set<Integer> freqs;
+                if (!allFreqs.isEmpty()) {
+                    pauseHwPnoScan();
+                    freqs = allFreqs.getScanFreqs();
+                    success = mWifiNative.scan(freqs, hiddenNetworkSSIDSet);
+                    if (!success) {
+                        Log.e(TAG, "Failed to start scan, freqs=" + freqs);
+                    }
+                } else {
+                    // There is a scan request but no available channels could be scanned for.
+                    // We regard it as a scan failure in this case.
+                    Log.e(TAG, "Failed to start scan because there is "
+                            + "no available channel to scan for");
+                }
                 if (success) {
                     // TODO handle scan timeout
                     if (DBG) {
@@ -439,7 +450,6 @@
                             mClock.getElapsedSinceBootMillis() + SCAN_TIMEOUT_MS,
                             TIMEOUT_ALARM_TAG, mScanTimeoutListener, mEventHandler);
                 } else {
-                    Log.e(TAG, "Failed to start scan, freqs=" + freqs);
                     // indicate scan failure async
                     mEventHandler.post(new Runnable() {
                             public void run() {
@@ -538,7 +548,7 @@
                  // got a scan before we started scanning or after scan was canceled
                 return;
             }
-            mNativeScanResults = mWifiNative.getScanResults();
+            mNativeScanResults = mWifiNative.getPnoScanResults();
             List<ScanResult> hwPnoScanResults = new ArrayList<>();
             int numFilteredScanResults = 0;
             for (int i = 0; i < mNativeScanResults.size(); ++i) {
diff --git a/service/java/com/android/server/wifi/util/Matrix.java b/service/java/com/android/server/wifi/util/Matrix.java
new file mode 100644
index 0000000..bdf147e
--- /dev/null
+++ b/service/java/com/android/server/wifi/util/Matrix.java
@@ -0,0 +1,340 @@
+/*
+ * Copyright (C) 2017 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.util;
+
+/**
+ * Utility for doing basic matix calculations
+ */
+public class Matrix {
+    public final int n;
+    public final int m;
+    public final double[] mem;
+
+    /**
+     * Creates a new matrix, initialized to zeros
+     *
+     * @param rows - number of rows (n)
+     * @param cols - number of columns (m)
+     */
+    public Matrix(int rows, int cols) {
+        n = rows;
+        m = cols;
+        mem = new double[rows * cols];
+    }
+
+    /**
+     * Creates a new matrix using the provided array of values
+     * <p>
+     * Values are in row-major order.
+     *
+     * @param stride is the number of columns.
+     * @param values is the array of values.
+     * @throws IllegalArgumentException if length of values array not a multiple of stride
+     */
+    public Matrix(int stride, double[] values) {
+        n = (values.length + stride - 1) / stride;
+        m = stride;
+        mem = values;
+        if (mem.length != n * m) throw new IllegalArgumentException();
+    }
+
+    /**
+     * Creates a new matrix duplicating the given one
+     *
+     * @param that is the source Matrix.
+     */
+    public Matrix(Matrix that) {
+        n = that.n;
+        m = that.m;
+        mem = new double[that.mem.length];
+        for (int i = 0; i < mem.length; i++) {
+            mem[i] = that.mem[i];
+        }
+    }
+
+    /**
+     * Gets the matrix coefficient from row i, column j
+     *
+     * @param i row number
+     * @param j column number
+     * @return Coefficient at i,j
+     * @throws IndexOutOfBoundsException if an index is out of bounds
+     */
+    public double get(int i, int j) {
+        if (!(0 <= i && i < n && 0 <= j && j < m)) throw new IndexOutOfBoundsException();
+        return mem[i * m + j];
+    }
+
+    /**
+     * Store a matrix coefficient in row i, column j
+     *
+     * @param i row number
+     * @param j column number
+     * @param v Coefficient to store at i,j
+     * @throws IndexOutOfBoundsException if an index is out of bounds
+     */
+    public void put(int i, int j, double v) {
+        if (!(0 <= i && i < n && 0 <= j && j < m)) throw new IndexOutOfBoundsException();
+        mem[i * m + j] = v;
+    }
+
+    /**
+     * Forms the sum of two matrices, this and that
+     *
+     * @param that is the other matrix
+     * @return newly allocated matrix representing the sum of this and that
+     * @throws IllegalArgumentException if shapes differ
+     */
+    public Matrix plus(Matrix that) {
+        return plus(that, new Matrix(n, m));
+
+    }
+
+    /**
+     * Forms the sum of two matrices, this and that
+     *
+     * @param that   is the other matrix
+     * @param result is space to hold the result
+     * @return result, filled with the matrix sum
+     * @throws IllegalArgumentException if shapes differ
+     */
+    public Matrix plus(Matrix that, Matrix result) {
+        if (!(this.n == that.n && this.m == that.m && this.n == result.n && this.m == result.m)) {
+            throw new IllegalArgumentException();
+        }
+        for (int i = 0; i < mem.length; i++) {
+            result.mem[i] = this.mem[i] + that.mem[i];
+        }
+        return result;
+    }
+
+    /**
+     * Forms the difference of two matrices, this and that
+     *
+     * @param that is the other matrix
+     * @return newly allocated matrix representing the difference of this and that
+     * @throws IllegalArgumentException if shapes differ
+     */
+    public Matrix minus(Matrix that) {
+        return minus(that, new Matrix(n, m));
+    }
+
+    /**
+     * Forms the difference of two matrices, this and that
+     *
+     * @param that   is the other matrix
+     * @param result is space to hold the result
+     * @return result, filled with the matrix difference
+     * @throws IllegalArgumentException if shapes differ
+     */
+    public Matrix minus(Matrix that, Matrix result) {
+        if (!(this.n == that.n && this.m == that.m && this.n == result.n && this.m == result.m)) {
+            throw new IllegalArgumentException();
+        }
+        for (int i = 0; i < mem.length; i++) {
+            result.mem[i] = this.mem[i] - that.mem[i];
+        }
+        return result;
+    }
+
+    /**
+     * Forms the matrix product of two matrices, this and that
+     *
+     * @param that is the other matrix
+     * @return newly allocated matrix representing the matrix product of this and that
+     * @throws IllegalArgumentException if shapes are not conformant
+     */
+    public Matrix dot(Matrix that) {
+        return dot(that, new Matrix(this.n, that.m));
+    }
+
+    /**
+     * Forms the matrix product of two matrices, this and that
+     * <p>
+     * Caller supplies an object to contain the result, as well as scratch space
+     *
+     * @param that   is the other matrix
+     * @param result is space to hold the result
+     * @return result, filled with the matrix product
+     * @throws IllegalArgumentException if shapes are not conformant
+     */
+    public Matrix dot(Matrix that, Matrix result) {
+        if (!(this.n == result.n && this.m == that.n && that.m == result.m)) {
+            throw new IllegalArgumentException();
+        }
+        for (int i = 0; i < n; i++) {
+            for (int j = 0; j < that.m; j++) {
+                double s = 0.0;
+                for (int k = 0; k < m; k++) {
+                    s += this.get(i, k) * that.get(k, j);
+                }
+                result.put(i, j, s);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Forms the matrix transpose
+     *
+     * @return newly allocated transpose matrix
+     */
+    public Matrix transpose() {
+        return transpose(new Matrix(m, n));
+    }
+
+    /**
+     * Forms the matrix transpose
+     * <p>
+     * Caller supplies an object to contain the result
+     *
+     * @param result is space to hold the result
+     * @return result, filled with the matrix transpose
+     * @throws IllegalArgumentException if result shape is wrong
+     */
+    public Matrix transpose(Matrix result) {
+        if (!(this.n == result.m && this.m == result.n)) throw new IllegalArgumentException();
+        for (int i = 0; i < n; i++) {
+            for (int j = 0; j < m; j++) {
+                result.put(j, i, get(i, j));
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Forms the inverse of a square matrix
+     *
+     * @return newly allocated matrix representing the matrix inverse
+     * @throws ArithmeticException if the matrix is not invertible
+     */
+    public Matrix inverse() {
+        return inverse(new Matrix(n, m), new Matrix(n, 2 * m));
+    }
+
+    /**
+     * Forms the inverse of a square matrix
+     *
+     * @param result  is space to hold the result
+     * @param scratch is workspace of dimension n by 2*n
+     * @return result, filled with the matrix inverse
+     * @throws ArithmeticException if the matrix is not invertible
+     * @throws IllegalArgumentException if shape of scratch or result is wrong
+     */
+    public Matrix inverse(Matrix result, Matrix scratch) {
+        if (!(n == m && n == result.n && m == result.m && n == scratch.n && 2 * m == scratch.m)) {
+            throw new IllegalArgumentException();
+        }
+
+        for (int i = 0; i < n; i++) {
+            for (int j = 0; j < m; j++) {
+                scratch.put(i, j, get(i, j));
+                scratch.put(i, m + j, i == j ? 1.0 : 0.0);
+            }
+        }
+
+        for (int i = 0; i < n; i++) {
+            int ibest = i;
+            double vbest = Math.abs(scratch.get(ibest, ibest));
+            for (int ii = i + 1; ii < n; ii++) {
+                double v = Math.abs(scratch.get(ii, i));
+                if (v > vbest) {
+                    ibest = ii;
+                    vbest = v;
+                }
+            }
+            if (ibest != i) {
+                for (int j = 0; j < scratch.m; j++) {
+                    double t = scratch.get(i, j);
+                    scratch.put(i, j, scratch.get(ibest, j));
+                    scratch.put(ibest, j, t);
+                }
+            }
+            double d = scratch.get(i, i);
+            if (d == 0.0) throw new ArithmeticException("Singular matrix");
+            for (int j = 0; j < scratch.m; j++) {
+                scratch.put(i, j, scratch.get(i, j) / d);
+            }
+            for (int ii = i + 1; ii < n; ii++) {
+                d = scratch.get(ii, i);
+                for (int j = 0; j < scratch.m; j++) {
+                    scratch.put(ii, j, scratch.get(ii, j) - d * scratch.get(i, j));
+                }
+            }
+        }
+        for (int i = n - 1; i >= 0; i--) {
+            for (int ii = 0; ii < i; ii++) {
+                double d = scratch.get(ii, i);
+                for (int j = 0; j < scratch.m; j++) {
+                    scratch.put(ii, j, scratch.get(ii, j) - d * scratch.get(i, j));
+                }
+            }
+        }
+        for (int i = 0; i < result.n; i++) {
+            for (int j = 0; j < result.m; j++) {
+                result.put(i, j, scratch.get(i, m + j));
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Tests for equality
+     */
+    @Override
+    public boolean equals(Object that) {
+        if (this == that) return true;
+        if (!(that instanceof Matrix)) return false;
+        Matrix other = (Matrix) that;
+        if (n != other.n) return false;
+        if (m != other.m) return false;
+        for (int i = 0; i < mem.length; i++) {
+            if (mem[i] != other.mem[i]) return false;
+        }
+        return true;
+    }
+
+    /**
+     * Calculates a hash code
+     */
+    @Override
+    public int hashCode() {
+        int h = n * 101 + m;
+        for (int i = 0; i < mem.length; i++) {
+            h = h * 37 + Double.hashCode(mem[i]);
+        }
+        return h;
+    }
+
+    /**
+     * Makes a string representation
+     *
+     * @return string like "[a, b; c, d]"
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder(n * m * 8);
+        sb.append("[");
+        for (int i = 0; i < mem.length; i++) {
+            if (i > 0) sb.append(i % m == 0 ? "; " : ", ");
+            sb.append(mem[i]);
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+
+}
diff --git a/service/java/com/android/server/wifi/util/NativeUtil.java b/service/java/com/android/server/wifi/util/NativeUtil.java
index 07c3f9b..84f9351 100644
--- a/service/java/com/android/server/wifi/util/NativeUtil.java
+++ b/service/java/com/android/server/wifi/util/NativeUtil.java
@@ -30,6 +30,7 @@
 import java.nio.charset.CharsetDecoder;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.Arrays;
 
 /**
  * Provide utility functions for native interfacing modules.
@@ -209,13 +210,13 @@
     /**
      * Converts an string to an arraylist of UTF_8 byte values.
      * These forms are acceptable:
-     * a) ASCII String encapsulated in quotes, or
+     * a) UTF-8 String encapsulated in quotes, or
      * b) Hex string with no delimiters.
      *
      * @param str String to be converted.
      * @throws IllegalArgumentException for null string.
      */
-    public static ArrayList<Byte> hexOrQuotedAsciiStringToBytes(String str) {
+    public static ArrayList<Byte> hexOrQuotedStringToBytes(String str) {
         if (str == null) {
             throw new IllegalArgumentException("null string");
         }
@@ -231,21 +232,21 @@
     /**
      * Converts an ArrayList<Byte> of UTF_8 byte values to string.
      * The string will either be:
-     * a) ASCII String encapsulated in quotes (if all the bytes are ASCII encodeable and non null),
+     * a) UTF-8 String encapsulated in quotes (if all the bytes are UTF-8 encodeable and non null),
      * or
      * b) Hex string with no delimiters.
      *
      * @param bytes List of bytes for ssid.
      * @throws IllegalArgumentException for null bytes.
      */
-    public static String bytesToHexOrQuotedAsciiString(ArrayList<Byte> bytes) {
+    public static String bytesToHexOrQuotedString(ArrayList<Byte> bytes) {
         if (bytes == null) {
             throw new IllegalArgumentException("null ssid bytes");
         }
         byte[] byteArray = byteArrayFromArrayList(bytes);
         // Check for 0's in the byte stream in which case we cannot convert this into a string.
         if (!bytes.contains(Byte.valueOf((byte) 0))) {
-            CharsetDecoder decoder = StandardCharsets.US_ASCII.newDecoder();
+            CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
             try {
                 CharBuffer decoded = decoder.decode(ByteBuffer.wrap(byteArray));
                 return "\"" + decoded.toString() + "\"";
@@ -258,20 +259,20 @@
     /**
      * Converts an ssid string to an arraylist of UTF_8 byte values.
      * These forms are acceptable:
-     * a) ASCII String encapsulated in quotes, or
+     * a) UTF-8 String encapsulated in quotes, or
      * b) Hex string with no delimiters.
      *
      * @param ssidStr String to be converted.
      * @throws IllegalArgumentException for null string.
      */
     public static ArrayList<Byte> decodeSsid(String ssidStr) {
-        return hexOrQuotedAsciiStringToBytes(ssidStr);
+        return hexOrQuotedStringToBytes(ssidStr);
     }
 
     /**
      * Converts an ArrayList<Byte> of UTF_8 byte values to ssid string.
      * The string will either be:
-     * a) ASCII String encapsulated in quotes (if all the bytes are ASCII encodeable and non null),
+     * a) UTF-8 String encapsulated in quotes (if all the bytes are UTF-8 encodeable and non null),
      * or
      * b) Hex string with no delimiters.
      *
@@ -279,7 +280,7 @@
      * @throws IllegalArgumentException for null bytes.
      */
     public static String encodeSsid(ArrayList<Byte> ssidBytes) {
-        return bytesToHexOrQuotedAsciiString(ssidBytes);
+        return bytesToHexOrQuotedString(ssidBytes);
     }
 
     /**
@@ -330,4 +331,16 @@
         }
         return new String(HexEncoding.encode(bytes)).toLowerCase();
     }
+
+    /**
+     * Converts an 8 byte array to a WPS device type string
+     * { 0, 1, 2, -1, 4, 5, 6, 7 } --> "1-02FF0405-1543";
+     */
+    public static String wpsDevTypeStringFromByteArray(byte[] devType) {
+        byte[] a = devType;
+        int x = ((a[0] & 0xFF) << 8) | (a[1] & 0xFF);
+        String y = new String(HexEncoding.encode(Arrays.copyOfRange(devType, 2, 6)));
+        int z = ((a[6] & 0xFF) << 8) | (a[7] & 0xFF);
+        return String.format("%d-%s-%d", x, y, z);
+    }
 }
diff --git a/service/java/com/android/server/wifi/util/WifiPermissionsUtil.java b/service/java/com/android/server/wifi/util/WifiPermissionsUtil.java
index c5eea7d..52c9618 100644
--- a/service/java/com/android/server/wifi/util/WifiPermissionsUtil.java
+++ b/service/java/com/android/server/wifi/util/WifiPermissionsUtil.java
@@ -17,16 +17,23 @@
 package com.android.server.wifi.util;
 
 import android.Manifest;
+import android.annotation.Nullable;
 import android.app.AppOpsManager;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.net.ConnectivityManager;
 import android.net.NetworkScoreManager;
+import android.net.NetworkScorerAppData;
+import android.net.wifi.WifiConfiguration;
+import android.os.Binder;
 import android.os.RemoteException;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.text.TextUtils;
 
+import com.android.server.wifi.FrameworkFacade;
 import com.android.server.wifi.WifiInjector;
 import com.android.server.wifi.WifiLog;
 import com.android.server.wifi.WifiSettingsStore;
@@ -45,6 +52,7 @@
     private final UserManager mUserManager;
     private final WifiSettingsStore mSettingsStore;
     private final NetworkScoreManager mNetworkScoreManager;
+    private final FrameworkFacade mFrameworkFacade;
     private WifiLog mLog;
 
     public WifiPermissionsUtil(WifiPermissionsWrapper wifiPermissionsWrapper,
@@ -57,6 +65,7 @@
         mSettingsStore = settingsStore;
         mLog = wifiInjector.makeLog(TAG);
         mNetworkScoreManager = networkScoreManager;
+        mFrameworkFacade = wifiInjector.getFrameworkFacade();
     }
 
     /**
@@ -113,10 +122,29 @@
         }
     }
 
+
+    /**
+     * Checks that calling process has android.Manifest.permission.ACCESS_COARSE_LOCATION
+     * and a corresponding app op is allowed for this package and uid.
+     *
+     * @param pkgName PackageName of the application requesting access
+     * @param uid The uid of the package
+     */
+    public boolean checkCallersLocationPermission(String pkgName, int uid) {
+        // Coarse Permission implies Fine permission
+        if ((mWifiPermissionsWrapper.getUidPermission(
+                Manifest.permission.ACCESS_COARSE_LOCATION, uid)
+                == PackageManager.PERMISSION_GRANTED)
+                && checkAppOpAllowed(AppOpsManager.OP_COARSE_LOCATION, pkgName, uid)) {
+            return true;
+        }
+        return false;
+    }
+
     /**
      * API to determine if the caller has permissions to get
      * scan results.
-     * @param pkgName Packagename of the application requesting access
+     * @param pkgName package name of the application requesting access
      * @param uid The uid of the package
      * @param minVersion Minimum app API Version number to enforce location permission
      * @return boolean true or false if permissions is granted
@@ -148,7 +176,7 @@
         }
         // If the User or profile is current, permission is granted
         // Otherwise, uid must have INTERACT_ACROSS_USERS_FULL permission.
-        if (!isCurrentProfile(uid) && !checkInteractAcrossUsersFull(uid)) {
+        if (!canAccessUserProfile(uid)) {
             mLog.tC("Denied: Profile not permitted");
             return false;
         }
@@ -156,6 +184,77 @@
     }
 
     /**
+     * API to determine if the caller has permissions to get a {@link android.net.wifi.WifiInfo}
+     * instance containing the SSID and BSSID.
+     *
+     *
+     * @param currentConfig the currently connected WiFi config
+     * @param pkgName package name of the application requesting access
+     * @param uid The uid of the package
+     * @param minVersion Minimum app API Version number to enforce location permission
+     * @return boolean true if the SSID/BSSID can be sent to the user, false if they
+     *         should be hidden/removed.
+     */
+    public boolean canAccessFullConnectionInfo(@Nullable WifiConfiguration currentConfig,
+            String pkgName, int uid, int minVersion) throws SecurityException {
+        mAppOps.checkPackage(uid, pkgName);
+
+        // The User or profile must be current or the uid must
+        // have INTERACT_ACROSS_USERS_FULL permission.
+        if (!canAccessUserProfile(uid)) {
+            mLog.tC("Denied: Profile not permitted");
+            return false;
+        }
+
+        // If the caller has scan result access then they can also see the full connection info.
+        // Otherwise the caller must be the active use open wifi package and the current config
+        // must be for an open network.
+        return canAccessScanResults(pkgName, uid, minVersion)
+                || isUseOpenWifiPackageWithConnectionInfoAccess(currentConfig, pkgName);
+
+    }
+
+    /**
+     * Returns true if the given WiFi config is for an open network and the package is the active
+     * use open wifi app.
+     */
+    private boolean isUseOpenWifiPackageWithConnectionInfoAccess(
+            @Nullable WifiConfiguration currentConfig, String pkgName) {
+
+        // Access is only granted for open networks.
+        if (currentConfig == null) {
+            mLog.tC("Denied: WifiConfiguration is NULL.");
+            return false;
+        }
+
+        // Access is only granted for open networks.
+        if (!currentConfig.isOpenNetwork()) {
+            mLog.tC("Denied: The current config is not for an open network.");
+            return false;
+        }
+
+        // The USE_OPEN_WIFI_PACKAGE can access the full connection info details without
+        // scan result access.
+        if (!isUseOpenWifiPackage(pkgName)) {
+            mLog.tC("Denied: caller is not the current USE_OPEN_WIFI_PACKAGE");
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns true if the User or profile is current or the
+     * uid has the INTERACT_ACROSS_USERS_FULL permission.
+     */
+    private boolean canAccessUserProfile(int uid) {
+        if (!isCurrentProfile(uid) && !checkInteractAcrossUsersFull(uid)) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
      * Returns true if the caller holds PEERS_MAC_ADDRESS permission.
      */
     private boolean checkCallerHasPeersMacAddressPermission(int uid) {
@@ -172,6 +271,43 @@
     }
 
     /**
+     * Returns true if the given package is equal to the setting keyed by
+     * {@link Settings.Global#USE_OPEN_WIFI_PACKAGE} and the NetworkScoreManager
+     * has the package name set as the use open wifi package.
+     */
+    private boolean isUseOpenWifiPackage(String packageName) {
+        if (TextUtils.isEmpty(packageName)) {
+            return false;
+        }
+
+        // When the setting is enabled it's set to the package name of the use open wifi app.
+        final String useOpenWifiPkg =
+                mFrameworkFacade.getStringSetting(mContext, Settings.Global.USE_OPEN_WIFI_PACKAGE);
+        if (packageName.equals(useOpenWifiPkg)) {
+            // If the package name matches the setting then also confirm that the scorer is
+            // active and the package matches the expected use open wifi package from the scorer's
+            // perspective. The scorer can be active when the use open wifi feature is off so we
+            // can't rely on this check alone.
+            // TODO(b/67278755): Refactor this into an API similar to isCallerActiveScorer()
+            final NetworkScorerAppData appData;
+            final long token = Binder.clearCallingIdentity();
+            try {
+                appData = mNetworkScoreManager.getActiveScorer();
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+            if (appData != null) {
+                final ComponentName enableUseOpenWifiActivity =
+                        appData.getEnableUseOpenWifiActivity();
+                return enableUseOpenWifiActivity != null
+                        && packageName.equals(enableUseOpenWifiActivity.getPackageName());
+            }
+        }
+
+        return false;
+    }
+
+    /**
      * Returns true if Wifi scan operation is allowed for this caller
      * and package.
      */
@@ -193,19 +329,24 @@
      * current user.
      */
     private boolean isCurrentProfile(int uid) {
-        int currentUser = mWifiPermissionsWrapper.getCurrentUser();
-        int callingUserId = mWifiPermissionsWrapper.getCallingUserId(uid);
-        if (callingUserId == currentUser) {
-            return true;
-        } else {
-            List<UserInfo> userProfiles = mUserManager.getProfiles(currentUser);
-            for (UserInfo user: userProfiles) {
-                if (user.id == callingUserId) {
-                    return true;
+        final long token = Binder.clearCallingIdentity();
+        try {
+            int currentUser = mWifiPermissionsWrapper.getCurrentUser();
+            int callingUserId = mWifiPermissionsWrapper.getCallingUserId(uid);
+            if (callingUserId == currentUser) {
+                return true;
+            } else {
+                List<UserInfo> userProfiles = mUserManager.getProfiles(currentUser);
+                for (UserInfo user : userProfiles) {
+                    if (user.id == callingUserId) {
+                        return true;
+                    }
                 }
             }
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
         }
-        return false;
     }
 
     /**
@@ -237,20 +378,6 @@
         return pkgName.equals(mWifiPermissionsWrapper.getTopPkgName());
     }
 
-    /**
-     * Checks that calling process has android.Manifest.permission.ACCESS_COARSE_LOCATION
-     * and a corresponding app op is allowed for this package and uid.
-     */
-    private boolean checkCallersLocationPermission(String pkgName, int uid) {
-        // Coarse Permission implies Fine permission
-        if ((mWifiPermissionsWrapper.getUidPermission(
-                Manifest.permission.ACCESS_COARSE_LOCATION, uid)
-                == PackageManager.PERMISSION_GRANTED)
-                && checkAppOpAllowed(AppOpsManager.OP_COARSE_LOCATION, pkgName, uid)) {
-            return true;
-        }
-        return false;
-    }
     private boolean isLocationModeEnabled(String pkgName) {
         // Location mode check on applications that are later than version.
         return (mSettingsStore.getLocationModeSetting(mContext)
diff --git a/service/java/com/android/server/wifi/util/XmlUtil.java b/service/java/com/android/server/wifi/util/XmlUtil.java
index 853136b..f4c3ab1 100644
--- a/service/java/com/android/server/wifi/util/XmlUtil.java
+++ b/service/java/com/android/server/wifi/util/XmlUtil.java
@@ -332,6 +332,7 @@
         public static final String XML_TAG_NO_INTERNET_ACCESS_EXPECTED = "NoInternetAccessExpected";
         public static final String XML_TAG_USER_APPROVED = "UserApproved";
         public static final String XML_TAG_METERED_HINT = "MeteredHint";
+        public static final String XML_TAG_METERED_OVERRIDE = "MeteredOverride";
         public static final String XML_TAG_USE_EXTERNAL_SCORES = "UseExternalScores";
         public static final String XML_TAG_NUM_ASSOCIATION = "NumAssociation";
         public static final String XML_TAG_CREATOR_UID = "CreatorUid";
@@ -445,6 +446,7 @@
                     configuration.noInternetAccessExpected);
             XmlUtil.writeNextValue(out, XML_TAG_USER_APPROVED, configuration.userApproved);
             XmlUtil.writeNextValue(out, XML_TAG_METERED_HINT, configuration.meteredHint);
+            XmlUtil.writeNextValue(out, XML_TAG_METERED_OVERRIDE, configuration.meteredOverride);
             XmlUtil.writeNextValue(
                     out, XML_TAG_USE_EXTERNAL_SCORES, configuration.useExternalScores);
             XmlUtil.writeNextValue(out, XML_TAG_NUM_ASSOCIATION, configuration.numAssociation);
@@ -591,6 +593,9 @@
                     case XML_TAG_METERED_HINT:
                         configuration.meteredHint = (boolean) value;
                         break;
+                    case XML_TAG_METERED_OVERRIDE:
+                        configuration.meteredOverride = (int) value;
+                        break;
                     case XML_TAG_USE_EXTERNAL_SCORES:
                         configuration.useExternalScores = (boolean) value;
                         break;
diff --git a/service/jni/com_android_server_wifi_WifiNative.cpp b/service/jni/com_android_server_wifi_WifiNative.cpp
index cf35028..09a5ebf 100644
--- a/service/jni/com_android_server_wifi_WifiNative.cpp
+++ b/service/jni/com_android_server_wifi_WifiNative.cpp
@@ -24,7 +24,7 @@
 #include <nativehelper/JniConstants.h>
 #include <nativehelper/ScopedBytes.h>
 #include <nativehelper/ScopedUtfChars.h>
-#include <nativehelper/jni.h>
+#include <jni.h>
 
 #include "jni_helper.h"
 
diff --git a/service/jni/jni_helper.cpp b/service/jni/jni_helper.cpp
index 150f694..60110c9 100644
--- a/service/jni/jni_helper.cpp
+++ b/service/jni/jni_helper.cpp
@@ -18,7 +18,6 @@
 
 #include <hardware_legacy/wifi_hal.h>
 #include <nativehelper/ScopedUtfChars.h>
-#include <nativehelper/jni.h>
 #include <utils/Log.h>
 #include <utils/String16.h>
 #include <utils/misc.h>
diff --git a/service/jni/jni_helper.h b/service/jni/jni_helper.h
index febb901..82b5c70 100644
--- a/service/jni/jni_helper.h
+++ b/service/jni/jni_helper.h
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include <jni.h>
+
 namespace android {
 
 /* JNI Helpers for wifi_hal to WifiNative bridge implementation */
diff --git a/tests/wifitests/coverage.sh b/tests/wifitests/coverage.sh
index 43c97c6..3ed71ad 100755
--- a/tests/wifitests/coverage.sh
+++ b/tests/wifitests/coverage.sh
@@ -1,8 +1,7 @@
 #!/usr/bin/env bash
 
-if [[ ! ( ($# == 1) || ($# == 2 && ($2 == "HTML" || $2 == "XML" || $2 == "CSV"))) ]]; then
-  echo "$0: usage: coverage.sh OUTPUT_DIR [REPORT_TYPE]"
-  echo "REPORT_TYPE [HTML | XML | CSV] : the type of the report (default is HTML)"
+if [[ ! ($# == 1) ]]; then
+  echo "$0: usage: coverage.sh OUTPUT_DIR"
   exit 1
 fi
 
@@ -11,32 +10,38 @@
   exit 1
 fi
 
-REPORTER_JAR=$ANDROID_BUILD_TOP/prebuilts/sdk/tools/jack-jacoco-reporter.jar
+cd "$(dirname $0)" #cd to directory containing this script
+
+
+REPORTER_JAR=$ANDROID_HOST_OUT/framework/jacoco-cli.jar
 
 OUTPUT_DIR=$1
-if [[ $# == 2 ]]; then
-  REPORT_TYPE=$2
-else
-  REPORT_TYPE="HTML"
-fi
 
 echo "Running tests and generating coverage report"
 echo "Output dir: $OUTPUT_DIR"
-echo "Report type: $REPORT_TYPE"
 
 REMOTE_COVERAGE_OUTPUT_FILE=/data/data/com.android.server.wifi.test/files/coverage.ec
 COVERAGE_OUTPUT_FILE=$OUTPUT_DIR/wifi_coverage.ec
-COVERAGE_METADATA_FILE=$ANDROID_BUILD_TOP/out/target/common/obj/APPS/FrameworksWifiTests_intermediates/coverage.em
 
 set -e # fail early
-
-echo "+ EMMA_INSTRUMENT_STATIC=true mmma -j32 $ANDROID_BUILD_TOP/frameworks/opt/net/wifi/tests"
-# NOTE Don't actually run the command above since this shell doesn't inherit functions from the
-#      caller.
-EMMA_INSTRUMENT_STATIC=true make -j32 -C $ANDROID_BUILD_TOP -f build/core/main.mk MODULES-IN-frameworks-opt-net-wifi-tests
-
 set -x # print commands
 
+# build this module so we can run its tests, and
+# build system/core so we can invoke `adb`, and
+# build jacoco-report-classes.jar so we can generate the report
+make \
+  EMMA_INSTRUMENT=true \
+  EMMA_INSTRUMENT_FRAMEWORK=false \
+  EMMA_INSTRUMENT_STATIC=true \
+  ANDROID_COMPILE_WITH_JACK=false \
+  SKIP_BOOT_JARS_CHECK=true \
+  -j32 \
+  -C $ANDROID_BUILD_TOP \
+  -f build/core/main.mk \
+  MODULES-IN-frameworks-opt-net-wifi-tests \
+  MODULES-IN-system-core \
+  FrameworksWifiTests
+
 adb root
 adb wait-for-device
 
@@ -51,9 +56,12 @@
 adb pull $REMOTE_COVERAGE_OUTPUT_FILE $COVERAGE_OUTPUT_FILE
 
 java -jar $REPORTER_JAR \
-  --report-dir $OUTPUT_DIR \
-  --metadata-file $COVERAGE_METADATA_FILE \
-  --coverage-file $COVERAGE_OUTPUT_FILE \
-  --report-type $REPORT_TYPE \
-  --source-dir $ANDROID_BUILD_TOP/frameworks/opt/net/wifi/tests/wifitests/src \
-  --source-dir $ANDROID_BUILD_TOP/frameworks/opt/net/wifi/service/java
+  report \
+  -classfiles $ANDROID_PRODUCT_OUT/../../common/obj/APPS/FrameworksWifiTests_intermediates/jacoco/report-resources/jacoco-report-classes.jar \
+  -html $OUTPUT_DIR \
+  -sourcefiles $ANDROID_BUILD_TOP/frameworks/opt/net/wifi/tests/wifitests/src -sourcefiles $ANDROID_BUILD_TOP/frameworks/opt/net/wifi/service/java \
+  -name wifi-coverage \
+  $COVERAGE_OUTPUT_FILE
+
+echo Created report at $OUTPUT_DIR/index.html
+
diff --git a/tests/wifitests/src/com/android/server/wifi/CarrierNetworkConfigTest.java b/tests/wifitests/src/com/android/server/wifi/CarrierNetworkConfigTest.java
new file mode 100644
index 0000000..f8d2e52
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/CarrierNetworkConfigTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2017 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 static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.wifi.EAPConstants;
+import android.net.wifi.WifiEnterpriseConfig;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Base64;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.CarrierNetworkConfig}.
+ */
+@SmallTest
+public class CarrierNetworkConfigTest {
+    private static final String TEST_SSID = "Test SSID";
+    private static final int TEST_STANDARD_EAP_TYPE = EAPConstants.EAP_SIM;
+    private static final int TEST_INTERNAL_EAP_TYPE = WifiEnterpriseConfig.Eap.SIM;
+    private static final int TEST_SUBSCRIPTION_ID = 1;
+    private static final String TEST_CARRIER_NAME = "Test Carrier";
+    private static final SubscriptionInfo TEST_SUBSCRIPTION_INFO =
+            new SubscriptionInfo(TEST_SUBSCRIPTION_ID, null, 0, TEST_CARRIER_NAME, null, 0, 0,
+                    null, 0, null, 0, 0, null);
+
+    @Mock Context mContext;
+    @Mock CarrierConfigManager mCarrierConfigManager;
+    @Mock SubscriptionManager mSubscriptionManager;
+    BroadcastReceiver mBroadcastReceiver;
+    CarrierNetworkConfig mCarrierNetworkConfig;
+
+    /**
+     * Generate and return a carrier config for testing
+     *
+     * @param ssid The SSID of the carrier network
+     * @param eapType The EAP type of the carrier network
+     * @return {@link PersistableBundle} containing carrier config
+     */
+    private PersistableBundle generateTestConfig(String ssid, int eapType) {
+        PersistableBundle bundle = new PersistableBundle();
+        String networkConfig =
+                new String(Base64.encode(ssid.getBytes(), Base64.DEFAULT)) + "," + eapType;
+        bundle.putStringArray(CarrierConfigManager.KEY_CARRIER_WIFI_STRING_ARRAY,
+                new String[] {networkConfig});
+        return bundle;
+    }
+
+    /**
+     * Method to initialize mocks for tests.
+     */
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE))
+                .thenReturn(mCarrierConfigManager);
+        when(mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE))
+                .thenReturn(mSubscriptionManager);
+        when(mCarrierConfigManager.getConfigForSubId(TEST_SUBSCRIPTION_ID))
+                .thenReturn(generateTestConfig(TEST_SSID, TEST_STANDARD_EAP_TYPE));
+        when(mSubscriptionManager.getActiveSubscriptionInfoList())
+                .thenReturn(Arrays.asList(new SubscriptionInfo[] {TEST_SUBSCRIPTION_INFO}));
+        mCarrierNetworkConfig = new CarrierNetworkConfig(mContext);
+        ArgumentCaptor<BroadcastReceiver> receiver =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        verify(mContext).registerReceiver(receiver.capture(), any(IntentFilter.class));
+        mBroadcastReceiver = receiver.getValue();
+        reset(mCarrierConfigManager);
+    }
+
+    /**
+     * Verify that {@link CarrierNetworkConfig#isCarrierNetwork} will return true and
+     * {@link CarrierNetworkConfig#getNetworkEapType} will return the corresponding EAP type
+     * when the given SSID is associated with a carrier network.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void getExistingCarrierNetworkInfo() throws Exception {
+        assertTrue(mCarrierNetworkConfig.isCarrierNetwork(TEST_SSID));
+        assertEquals(TEST_INTERNAL_EAP_TYPE, mCarrierNetworkConfig.getNetworkEapType(TEST_SSID));
+        assertEquals(TEST_CARRIER_NAME, mCarrierNetworkConfig.getCarrierName(TEST_SSID));
+    }
+
+    /**
+     * Verify that {@link CarrierNetworkConfig#isCarrierNetwork} will return false and
+     * {@link CarrierNetworkConfig#getNetworkEapType} will return -1 when the given SSID is not
+     * associated with any carrier network.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void getNonCarrierNetworkInfo() throws Exception {
+        String dummySsid = "Dummy SSID";
+        assertFalse(mCarrierNetworkConfig.isCarrierNetwork(dummySsid));
+        assertEquals(-1, mCarrierNetworkConfig.getNetworkEapType(dummySsid));
+    }
+
+    /**
+     * Verify that the carrier network config is updated when
+     * {@link CarrierConfigManager#ACTION_CARRIER_CONFIG_CHANGED} intent is received.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void receivedCarrierConfigChangedIntent() throws Exception {
+        String updatedSsid = "Updated SSID";
+        int updatedStandardEapType = EAPConstants.EAP_AKA;
+        int updatedInternalEapType = WifiEnterpriseConfig.Eap.AKA;
+        String updatedCarrierName = "Updated Carrier";
+        SubscriptionInfo updatedSubscriptionInfo = new SubscriptionInfo(TEST_SUBSCRIPTION_ID,
+                null, 0, updatedCarrierName, null, 0, 0, null, 0, null, 0, 0, null);
+        when(mSubscriptionManager.getActiveSubscriptionInfoList())
+                .thenReturn(Arrays.asList(new SubscriptionInfo[] {updatedSubscriptionInfo}));
+        when(mCarrierConfigManager.getConfigForSubId(TEST_SUBSCRIPTION_ID))
+                .thenReturn(generateTestConfig(updatedSsid, updatedStandardEapType));
+        mBroadcastReceiver.onReceive(mContext,
+                new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
+
+        // Verify that original SSID no longer associated with a carrier network.
+        assertFalse(mCarrierNetworkConfig.isCarrierNetwork(TEST_SSID));
+        assertEquals(-1, mCarrierNetworkConfig.getNetworkEapType(TEST_SSID));
+        assertEquals(null, mCarrierNetworkConfig.getCarrierName(TEST_SSID));
+
+        // Verify that updated SSID is associated with a carrier network.
+        assertTrue(mCarrierNetworkConfig.isCarrierNetwork(updatedSsid));
+        assertEquals(updatedInternalEapType, mCarrierNetworkConfig.getNetworkEapType(updatedSsid));
+        assertEquals(updatedCarrierName, mCarrierNetworkConfig.getCarrierName(updatedSsid));
+    }
+
+    /**
+     * Verify that the carrier network config is not updated when non
+     * {@link CarrierConfigManager#ACTION_CARRIER_CONFIG_CHANGED} intent is received.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void receivedNonCarrierConfigChangedIntent() throws Exception {
+        mBroadcastReceiver.onReceive(mContext, new Intent("dummyIntent"));
+        verify(mCarrierConfigManager, never()).getConfig();
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/NetworkListStoreDataTest.java b/tests/wifitests/src/com/android/server/wifi/NetworkListStoreDataTest.java
index 01257c1..19a92b8 100644
--- a/tests/wifitests/src/com/android/server/wifi/NetworkListStoreDataTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/NetworkListStoreDataTest.java
@@ -16,9 +16,13 @@
 
 package com.android.server.wifi;
 
+import static android.os.Process.SYSTEM_UID;
+
 import static org.junit.Assert.*;
 import static org.mockito.Mockito.*;
 
+import android.content.Context;
+import android.content.pm.PackageManager;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiEnterpriseConfig;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -29,6 +33,8 @@
 
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
@@ -49,6 +55,7 @@
     private static final String TEST_SSID = "WifiConfigStoreDataSSID_";
     private static final String TEST_CONNECT_CHOICE = "XmlUtilConnectChoice";
     private static final long TEST_CONNECT_CHOICE_TIMESTAMP = 0x4566;
+    private static final String TEST_CREATOR_NAME = "CreatorName";
     private static final String SINGLE_OPEN_NETWORK_DATA_XML_STRING_FORMAT =
             "<Network>\n"
                     + "<WifiConfiguration>\n"
@@ -75,10 +82,11 @@
                     + "<boolean name=\"NoInternetAccessExpected\" value=\"false\" />\n"
                     + "<int name=\"UserApproved\" value=\"0\" />\n"
                     + "<boolean name=\"MeteredHint\" value=\"false\" />\n"
+                    + "<int name=\"MeteredOverride\" value=\"0\" />\n"
                     + "<boolean name=\"UseExternalScores\" value=\"false\" />\n"
                     + "<int name=\"NumAssociation\" value=\"0\" />\n"
                     + "<int name=\"CreatorUid\" value=\"%d\" />\n"
-                    + "<null name=\"CreatorName\" />\n"
+                    + "<string name=\"CreatorName\">%s</string>\n"
                     + "<null name=\"CreationTime\" />\n"
                     + "<int name=\"LastUpdateUid\" value=\"-1\" />\n"
                     + "<null name=\"LastUpdateName\" />\n"
@@ -125,10 +133,11 @@
                     + "<boolean name=\"NoInternetAccessExpected\" value=\"false\" />\n"
                     + "<int name=\"UserApproved\" value=\"0\" />\n"
                     + "<boolean name=\"MeteredHint\" value=\"false\" />\n"
+                    + "<int name=\"MeteredOverride\" value=\"0\" />\n"
                     + "<boolean name=\"UseExternalScores\" value=\"false\" />\n"
                     + "<int name=\"NumAssociation\" value=\"0\" />\n"
                     + "<int name=\"CreatorUid\" value=\"%d\" />\n"
-                    + "<null name=\"CreatorName\" />\n"
+                    + "<string name=\"CreatorName\">%s</string>\n"
                     + "<null name=\"CreationTime\" />\n"
                     + "<int name=\"LastUpdateUid\" value=\"-1\" />\n"
                     + "<null name=\"LastUpdateName\" />\n"
@@ -168,10 +177,15 @@
                     + "</Network>\n";
 
     private NetworkListStoreData mNetworkListStoreData;
+    @Mock private Context mContext;
+    @Mock private PackageManager mPackageManager;
 
     @Before
     public void setUp() throws Exception {
-        mNetworkListStoreData = new NetworkListStoreData();
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mPackageManager.getNameForUid(anyInt())).thenReturn(TEST_CREATOR_NAME);
+        mNetworkListStoreData = new NetworkListStoreData(mContext);
     }
 
     /**
@@ -219,11 +233,13 @@
      */
     private List<WifiConfiguration> getTestNetworksConfig(boolean shared) {
         WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        openNetwork.creatorName = TEST_CREATOR_NAME;
         openNetwork.shared = shared;
         openNetwork.setIpConfiguration(
                 WifiConfigurationTestUtil.createDHCPIpConfigurationWithNoProxy());
         WifiConfiguration eapNetwork = WifiConfigurationTestUtil.createEapNetwork();
         eapNetwork.shared = shared;
+        eapNetwork.creatorName = TEST_CREATOR_NAME;
         eapNetwork.setIpConfiguration(
                 WifiConfigurationTestUtil.createDHCPIpConfigurationWithNoProxy());
         List<WifiConfiguration> networkList = new ArrayList<>();
@@ -245,11 +261,11 @@
         String openNetworkXml = String.format(SINGLE_OPEN_NETWORK_DATA_XML_STRING_FORMAT,
                 openNetwork.configKey().replaceAll("\"", "&quot;"),
                 openNetwork.SSID.replaceAll("\"", "&quot;"),
-                openNetwork.shared, openNetwork.creatorUid);
+                openNetwork.shared, openNetwork.creatorUid, openNetwork.creatorName);
         String eapNetworkXml = String.format(SINGLE_EAP_NETWORK_DATA_XML_STRING_FORMAT,
                 eapNetwork.configKey().replaceAll("\"", "&quot;"),
                 eapNetwork.SSID.replaceAll("\"", "&quot;"),
-                eapNetwork.shared, eapNetwork.creatorUid);
+                eapNetwork.shared, eapNetwork.creatorUid, openNetwork.creatorName);
         return (openNetworkXml + eapNetworkXml).getBytes(StandardCharsets.UTF_8);
     }
 
@@ -418,7 +434,8 @@
         byte[] xmlData = String.format(SINGLE_OPEN_NETWORK_DATA_XML_STRING_FORMAT,
                 "InvalidConfigKey",
                 openNetwork.SSID.replaceAll("\"", "&quot;"),
-                openNetwork.shared, openNetwork.creatorUid).getBytes(StandardCharsets.UTF_8);
+                openNetwork.shared, openNetwork.creatorUid, openNetwork.creatorName)
+            .getBytes(StandardCharsets.UTF_8);
         deserializeData(xmlData, true);
     }
 
@@ -446,4 +463,96 @@
             assertNotEquals(eapNetwork.SSID, network.SSID);
         }
     }
+
+    /**
+     * Verify that a saved network config with invalid creatorUid resets it to
+     * {@link android.os.Process#SYSTEM_UID}.
+     */
+    public void parseNetworkWithInvalidCreatorUidResetsToSystem() throws Exception {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        openNetwork.creatorUid = -1;
+        // Return null for invalid uid.
+        when(mPackageManager.getNameForUid(eq(openNetwork.creatorUid))).thenReturn(null);
+
+        byte[] xmlData = String.format(SINGLE_OPEN_NETWORK_DATA_XML_STRING_FORMAT,
+                openNetwork.configKey().replaceAll("\"", "&quot;"),
+                openNetwork.SSID.replaceAll("\"", "&quot;"),
+                openNetwork.shared, openNetwork.creatorUid, openNetwork.creatorName)
+            .getBytes(StandardCharsets.UTF_8);
+        List<WifiConfiguration> deserializedNetworks = deserializeData(xmlData, true);
+        assertEquals(1, deserializedNetworks.size());
+        assertEquals(openNetwork.configKey(), deserializedNetworks.get(0).configKey());
+        assertEquals(SYSTEM_UID, deserializedNetworks.get(0).creatorUid);
+        assertEquals(TEST_CREATOR_NAME, deserializedNetworks.get(0).creatorName);
+    }
+
+    /**
+     * Verify that a saved network config with invalid creatorName resets it to the package name
+     * provided {@link PackageManager} for the creatorUid.
+     */
+    public void parseNetworkWithInvalidCreatorNameResetsToPackageNameForCreatorUid()
+            throws Exception {
+        String badCreatorName = "bad";
+        String correctCreatorName = "correct";
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        openNetwork.creatorUid = 1324422;
+        openNetwork.creatorName = badCreatorName;
+        when(mPackageManager.getNameForUid(eq(openNetwork.creatorUid)))
+            .thenReturn(correctCreatorName);
+
+        byte[] xmlData = String.format(SINGLE_OPEN_NETWORK_DATA_XML_STRING_FORMAT,
+                openNetwork.configKey().replaceAll("\"", "&quot;"),
+                openNetwork.SSID.replaceAll("\"", "&quot;"),
+                openNetwork.shared, openNetwork.creatorUid, openNetwork.creatorName)
+            .getBytes(StandardCharsets.UTF_8);
+        List<WifiConfiguration> deserializedNetworks = deserializeData(xmlData, true);
+        assertEquals(1, deserializedNetworks.size());
+        assertEquals(openNetwork.configKey(), deserializedNetworks.get(0).configKey());
+        assertEquals(openNetwork.creatorUid, deserializedNetworks.get(0).creatorUid);
+        assertEquals(correctCreatorName, deserializedNetworks.get(0).creatorName);
+    }
+
+    /**
+     * Verify that a saved network config with invalid creatorName resets it to the package name
+     * provided {@link PackageManager} for the creatorUid.
+     */
+    public void parseNetworkWithNullCreatorNameResetsToPackageNameForCreatorUid()
+            throws Exception {
+        String correctCreatorName = "correct";
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        openNetwork.creatorUid = 1324422;
+        openNetwork.creatorName = null;
+        when(mPackageManager.getNameForUid(eq(openNetwork.creatorUid)))
+            .thenReturn(correctCreatorName);
+
+        byte[] xmlData = String.format(SINGLE_OPEN_NETWORK_DATA_XML_STRING_FORMAT,
+                openNetwork.configKey().replaceAll("\"", "&quot;"),
+                openNetwork.SSID.replaceAll("\"", "&quot;"),
+                openNetwork.shared, openNetwork.creatorUid, openNetwork.creatorName)
+            .getBytes(StandardCharsets.UTF_8);
+        List<WifiConfiguration> deserializedNetworks = deserializeData(xmlData, true);
+        assertEquals(1, deserializedNetworks.size());
+        assertEquals(openNetwork.configKey(), deserializedNetworks.get(0).configKey());
+        assertEquals(openNetwork.creatorUid, deserializedNetworks.get(0).creatorUid);
+        assertEquals(correctCreatorName, deserializedNetworks.get(0).creatorName);
+    }
+
+    /**
+     * Verify that a saved network config with valid creatorUid is preserved.
+     */
+    public void parseNetworkWithValidCreatorUid() throws Exception {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        openNetwork.creatorUid = 1324422;
+
+        byte[] xmlData = String.format(SINGLE_OPEN_NETWORK_DATA_XML_STRING_FORMAT,
+                openNetwork.configKey().replaceAll("\"", "&quot;"),
+                openNetwork.SSID.replaceAll("\"", "&quot;"),
+                openNetwork.shared, openNetwork.creatorUid, openNetwork.creatorName)
+            .getBytes(StandardCharsets.UTF_8);
+        List<WifiConfiguration> deserializedNetworks = deserializeData(xmlData, true);
+        assertEquals(1, deserializedNetworks.size());
+        assertEquals(openNetwork.configKey(), deserializedNetworks.get(0).configKey());
+        assertEquals(openNetwork.creatorUid, deserializedNetworks.get(0).creatorUid);
+        assertEquals(TEST_CREATOR_NAME, deserializedNetworks.get(0).creatorName);
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/OpenNetworkNotifierTest.java b/tests/wifitests/src/com/android/server/wifi/OpenNetworkNotifierTest.java
new file mode 100644
index 0000000..5a0b011
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/OpenNetworkNotifierTest.java
@@ -0,0 +1,702 @@
+/*
+ * Copyright (C) 2016 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 static com.android.server.wifi.OpenNetworkNotifier.DEFAULT_REPEAT_DELAY_SEC;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiManager;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.test.TestLooper;
+import android.provider.Settings;
+import android.util.ArraySet;
+
+import com.android.server.wifi.nano.WifiMetricsProto.ConnectToNetworkNotificationAndActionCount;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Unit tests for {@link OpenNetworkNotifier}.
+ */
+public class OpenNetworkNotifierTest {
+
+    private static final String TEST_SSID_1 = "Test SSID 1";
+    private static final String TEST_SSID_2 = "Test SSID 2";
+    private static final int MIN_RSSI_LEVEL = -127;
+
+    @Mock private Context mContext;
+    @Mock private Resources mResources;
+    @Mock private FrameworkFacade mFrameworkFacade;
+    @Mock private WifiMetrics mWifiMetrics;
+    @Mock private Clock mClock;
+    @Mock private WifiConfigStore mWifiConfigStore;
+    @Mock private WifiConfigManager mWifiConfigManager;
+    @Mock private NotificationManager mNotificationManager;
+    @Mock private WifiStateMachine mWifiStateMachine;
+    @Mock private OpenNetworkRecommender mOpenNetworkRecommender;
+    @Mock private ConnectToNetworkNotificationBuilder mNotificationBuilder;
+    @Mock private UserManager mUserManager;
+    private OpenNetworkNotifier mNotificationController;
+    private TestLooper mLooper;
+    private BroadcastReceiver mBroadcastReceiver;
+    private ContentObserver mContentObserver;
+    private ScanResult mDummyNetwork;
+    private List<ScanDetail> mOpenNetworks;
+    private Set<String> mBlacklistedSsids;
+
+
+    /** Initialize objects before each test run. */
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getSystemService(Context.NOTIFICATION_SERVICE))
+                .thenReturn(mNotificationManager);
+        when(mFrameworkFacade.getIntegerSetting(mContext,
+                Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1)).thenReturn(1);
+        when(mFrameworkFacade.getIntegerSetting(mContext,
+                Settings.Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, DEFAULT_REPEAT_DELAY_SEC))
+                .thenReturn(DEFAULT_REPEAT_DELAY_SEC);
+        when(mContext.getSystemService(Context.USER_SERVICE))
+                .thenReturn(mUserManager);
+        when(mContext.getResources()).thenReturn(mResources);
+        mDummyNetwork = new ScanResult();
+        mDummyNetwork.SSID = TEST_SSID_1;
+        mDummyNetwork.capabilities = "[ESS]";
+        mDummyNetwork.level = MIN_RSSI_LEVEL;
+        when(mOpenNetworkRecommender.recommendNetwork(any(), any())).thenReturn(mDummyNetwork);
+        mOpenNetworks = new ArrayList<>();
+        mOpenNetworks.add(new ScanDetail(mDummyNetwork, null /* networkDetail */));
+        mBlacklistedSsids = new ArraySet<>();
+
+        mLooper = new TestLooper();
+        mNotificationController = new OpenNetworkNotifier(
+                mContext, mLooper.getLooper(), mFrameworkFacade, mClock, mWifiMetrics,
+                mWifiConfigManager, mWifiConfigStore, mWifiStateMachine, mOpenNetworkRecommender,
+                mNotificationBuilder);
+        ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        verify(mContext).registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
+        mBroadcastReceiver = broadcastReceiverCaptor.getValue();
+        ArgumentCaptor<ContentObserver> observerCaptor =
+                ArgumentCaptor.forClass(ContentObserver.class);
+        verify(mFrameworkFacade).registerContentObserver(eq(mContext), any(Uri.class), eq(true),
+                observerCaptor.capture());
+        mContentObserver = observerCaptor.getValue();
+        mNotificationController.handleScreenStateChanged(true);
+    }
+
+    /**
+     * On {@link OpenNetworkNotifier} construction, WifiMetrics should track setting state.
+     */
+    @Test
+    public void onCreate_setWifiNetworksAvailableNotificationSettingState() {
+        verify(mWifiMetrics).setIsWifiNetworksAvailableNotificationEnabled(true);
+    }
+
+    /**
+     * When feature setting is toggled, WifiMetrics should track the disabled setting state.
+     */
+    @Test
+    public void onFeatureDisable_setWifiNetworksAvailableNotificationSettingDisabled() {
+        when(mFrameworkFacade.getIntegerSetting(mContext,
+                Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1)).thenReturn(0);
+        mContentObserver.onChange(false);
+
+        verify(mWifiMetrics).setIsWifiNetworksAvailableNotificationEnabled(false);
+    }
+
+    /**
+     * When scan results with open networks are handled, a notification is posted.
+     */
+    @Test
+    public void handleScanResults_hasOpenNetworks_notificationDisplayed() {
+        mNotificationController.handleScanResults(mOpenNetworks);
+
+        verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
+        verify(mNotificationBuilder).createConnectToNetworkNotification(mDummyNetwork);
+        verify(mWifiMetrics).incrementConnectToNetworkNotification(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
+        verify(mNotificationManager).notify(anyInt(), any());
+    }
+
+    /**
+     * When scan results with no open networks are handled, a notification is not posted.
+     */
+    @Test
+    public void handleScanResults_emptyList_notificationNotDisplayed() {
+        mNotificationController.handleScanResults(new ArrayList<>());
+
+        verify(mOpenNetworkRecommender, never()).recommendNetwork(any(), any());
+        verify(mNotificationManager, never()).notify(anyInt(), any());
+    }
+
+    /**
+     * When the feature is disabled, no notifications are posted.
+     */
+    @Test
+    public void handleScanResults_featureDisabled_notificationNotDisplayed() {
+        when(mFrameworkFacade.getIntegerSetting(mContext,
+                Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1)).thenReturn(0);
+        mContentObserver.onChange(false);
+        mNotificationController.handleScanResults(new ArrayList<>());
+
+        verify(mOpenNetworkRecommender, never()).recommendNetwork(any(), any());
+        verify(mNotificationManager, never()).notify(anyInt(), any());
+    }
+
+    /**
+     * When a notification is showing and scan results with no open networks are handled, the
+     * notification is cleared.
+     */
+    @Test
+    public void handleScanResults_notificationShown_emptyList_notificationCleared() {
+        mNotificationController.handleScanResults(mOpenNetworks);
+
+        verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
+        verify(mNotificationBuilder).createConnectToNetworkNotification(mDummyNetwork);
+        verify(mWifiMetrics).incrementConnectToNetworkNotification(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
+        verify(mNotificationManager).notify(anyInt(), any());
+
+        mNotificationController.handleScanResults(new ArrayList<>());
+
+        verify(mNotificationManager).cancel(anyInt());
+    }
+
+    /**
+     * When a notification is showing and no recommendation is made for the new scan results, the
+     * notification is cleared.
+     */
+    @Test
+    public void handleScanResults_notificationShown_noRecommendation_notificationCleared() {
+        mNotificationController.handleScanResults(mOpenNetworks);
+
+        verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
+        verify(mNotificationBuilder).createConnectToNetworkNotification(mDummyNetwork);
+        verify(mWifiMetrics).incrementConnectToNetworkNotification(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
+        verify(mNotificationManager).notify(anyInt(), any());
+
+        when(mOpenNetworkRecommender.recommendNetwork(any(), any())).thenReturn(null);
+        mNotificationController.handleScanResults(mOpenNetworks);
+
+        verify(mNotificationManager).cancel(anyInt());
+    }
+
+    /**
+     * When a notification is showing, screen is off, and scan results with no open networks are
+     * handled, the notification is cleared.
+     */
+    @Test
+    public void handleScanResults_notificationShown_screenOff_emptyList_notificationCleared() {
+        mNotificationController.handleScanResults(mOpenNetworks);
+
+        verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
+        verify(mNotificationBuilder).createConnectToNetworkNotification(mDummyNetwork);
+        verify(mWifiMetrics).incrementConnectToNetworkNotification(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
+        verify(mNotificationManager).notify(anyInt(), any());
+
+        mNotificationController.handleScreenStateChanged(false);
+        mNotificationController.handleScanResults(new ArrayList<>());
+
+        verify(mNotificationManager).cancel(anyInt());
+    }
+
+    /**
+     * When {@link OpenNetworkNotifier#clearPendingNotification(boolean)} is called and a
+     * notification is shown, clear the notification.
+     */
+    @Test
+    public void clearPendingNotification_clearsNotificationIfOneIsShowing() {
+        mNotificationController.handleScanResults(mOpenNetworks);
+
+        verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
+        verify(mNotificationBuilder).createConnectToNetworkNotification(mDummyNetwork);
+        verify(mWifiMetrics).incrementConnectToNetworkNotification(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
+        verify(mNotificationManager).notify(anyInt(), any());
+
+        mNotificationController.clearPendingNotification(true);
+
+        verify(mNotificationManager).cancel(anyInt());
+    }
+
+    /**
+     * When {@link OpenNetworkNotifier#clearPendingNotification(boolean)} is called and a
+     * notification was not previously shown, do not clear the notification.
+     */
+    @Test
+    public void clearPendingNotification_doesNotClearNotificationIfNoneShowing() {
+        mNotificationController.clearPendingNotification(true);
+
+        verify(mNotificationManager, never()).cancel(anyInt());
+    }
+
+    /**
+     * When screen is off and notification is not displayed, notification is not posted on handling
+     * new scan results with open networks.
+     */
+    @Test
+    public void screenOff_notificationNotShowing_handleScanResults_notificationNotDisplayed() {
+        mNotificationController.handleScreenStateChanged(false);
+        mNotificationController.handleScanResults(mOpenNetworks);
+
+        verify(mOpenNetworkRecommender, never()).recommendNetwork(any(), any());
+        verify(mNotificationManager, never()).notify(anyInt(), any());
+    }
+
+    /**
+     * When screen is off and notification is displayed, the notification can be updated with a new
+     * recommendation.
+     */
+    @Test
+    public void screenOff_notificationShowing_handleScanResults_recommendationCanBeUpdated() {
+        mNotificationController.handleScanResults(mOpenNetworks);
+
+        verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
+        verify(mNotificationBuilder).createConnectToNetworkNotification(mDummyNetwork);
+        verify(mWifiMetrics).incrementConnectToNetworkNotification(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
+        verify(mNotificationManager).notify(anyInt(), any());
+
+        ScanResult newNetwork = new ScanResult();
+        newNetwork.SSID = TEST_SSID_2;
+        mDummyNetwork.capabilities = "[ESS]";
+        mDummyNetwork.level = MIN_RSSI_LEVEL;
+        mOpenNetworks.add(new ScanDetail(newNetwork, null /* networkDetail */));
+        when(mOpenNetworkRecommender.recommendNetwork(any(), any())).thenReturn(newNetwork);
+
+        mNotificationController.handleScreenStateChanged(false);
+        mNotificationController.handleScanResults(mOpenNetworks);
+
+        // Recommendation changed
+        verify(mOpenNetworkRecommender, times(2)).recommendNetwork(
+                mOpenNetworks, mBlacklistedSsids);
+        verify(mNotificationBuilder).createConnectToNetworkNotification(newNetwork);
+        verify(mWifiMetrics).incrementNumOpenNetworkRecommendationUpdates();
+        verify(mNotificationManager, times(2)).notify(anyInt(), any());
+    }
+
+    /**
+     * When a notification is posted and cleared without resetting delay, the next scan with open
+     * networks should not post another notification.
+     */
+    @Test
+    public void postNotification_clearNotificationWithoutDelayReset_shouldNotPostNotification() {
+        mNotificationController.handleScanResults(mOpenNetworks);
+
+        verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
+        verify(mNotificationBuilder).createConnectToNetworkNotification(mDummyNetwork);
+        verify(mWifiMetrics).incrementConnectToNetworkNotification(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
+        verify(mNotificationManager).notify(anyInt(), any());
+
+        mNotificationController.clearPendingNotification(false);
+
+        verify(mNotificationManager).cancel(anyInt());
+
+        mNotificationController.handleScanResults(mOpenNetworks);
+
+        // no new notification posted
+        verify(mNotificationManager).notify(anyInt(), any());
+    }
+
+    /**
+     * When a notification is posted and cleared without resetting delay, the next scan with open
+     * networks should post a notification.
+     */
+    @Test
+    public void postNotification_clearNotificationWithDelayReset_shouldPostNotification() {
+        mNotificationController.handleScanResults(mOpenNetworks);
+
+        verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
+        verify(mNotificationBuilder).createConnectToNetworkNotification(mDummyNetwork);
+        verify(mWifiMetrics).incrementConnectToNetworkNotification(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
+        verify(mNotificationManager).notify(anyInt(), any());
+
+        mNotificationController.clearPendingNotification(true);
+
+        mNotificationController.handleScanResults(mOpenNetworks);
+
+        verify(mOpenNetworkRecommender, times(2)).recommendNetwork(
+                mOpenNetworks, mBlacklistedSsids);
+        verify(mNotificationBuilder, times(2)).createConnectToNetworkNotification(mDummyNetwork);
+        verify(mWifiMetrics, times(2)).incrementConnectToNetworkNotification(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
+        verify(mNotificationManager, times(2)).notify(anyInt(), any());
+    }
+
+    /**
+     * When user dismissed notification and there is a recommended network, network ssid should be
+     * blacklisted.
+     */
+    @Test
+    public void userDismissedNotification_shouldBlacklistNetwork() {
+        mNotificationController.handleScanResults(mOpenNetworks);
+
+        verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
+        verify(mNotificationBuilder).createConnectToNetworkNotification(mDummyNetwork);
+        verify(mWifiMetrics).incrementConnectToNetworkNotification(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
+        verify(mNotificationManager).notify(anyInt(), any());
+
+        mBroadcastReceiver.onReceive(
+                mContext,
+                new Intent(ConnectToNetworkNotificationBuilder.ACTION_USER_DISMISSED_NOTIFICATION));
+
+        verify(mWifiConfigManager).saveToStore(false /* forceWrite */);
+
+        mNotificationController.clearPendingNotification(true);
+        List<ScanDetail> scanResults = mOpenNetworks;
+        mNotificationController.handleScanResults(scanResults);
+
+        Set<String> expectedBlacklist = new ArraySet<>();
+        expectedBlacklist.add(mDummyNetwork.SSID);
+        verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, expectedBlacklist);
+        verify(mWifiMetrics).setOpenNetworkRecommenderBlacklistSize(expectedBlacklist.size());
+    }
+
+    /**
+     * When a notification is posted and cleared without resetting delay, after the delay has passed
+     * the next scan with open networks should post a notification.
+     */
+    @Test
+    public void delaySet_delayPassed_shouldPostNotification() {
+        mNotificationController.handleScanResults(mOpenNetworks);
+
+        verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
+        verify(mNotificationBuilder).createConnectToNetworkNotification(mDummyNetwork);
+        verify(mWifiMetrics).incrementConnectToNetworkNotification(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
+        verify(mNotificationManager).notify(anyInt(), any());
+
+        mNotificationController.clearPendingNotification(false);
+
+        // twice the delay time passed
+        when(mClock.getWallClockMillis()).thenReturn(DEFAULT_REPEAT_DELAY_SEC * 1000L * 2);
+
+        mNotificationController.handleScanResults(mOpenNetworks);
+
+        verify(mOpenNetworkRecommender, times(2)).recommendNetwork(
+                mOpenNetworks, mBlacklistedSsids);
+        verify(mNotificationBuilder, times(2)).createConnectToNetworkNotification(mDummyNetwork);
+        verify(mWifiMetrics, times(2)).incrementConnectToNetworkNotification(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
+        verify(mNotificationManager, times(2)).notify(anyInt(), any());
+    }
+
+    /** Verifies that {@link UserManager#DISALLOW_CONFIG_WIFI} disables the feature. */
+    @Test
+    public void userHasDisallowConfigWifiRestriction_notificationNotDisplayed() {
+        when(mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, UserHandle.CURRENT))
+                .thenReturn(true);
+
+        mNotificationController.handleScanResults(mOpenNetworks);
+
+        verify(mOpenNetworkRecommender, never()).recommendNetwork(any(), any());
+        verify(mNotificationManager, never()).notify(anyInt(), any());
+    }
+
+    /** Verifies that {@link UserManager#DISALLOW_CONFIG_WIFI} clears the showing notification. */
+    @Test
+    public void userHasDisallowConfigWifiRestriction_showingNotificationIsCleared() {
+        mNotificationController.handleScanResults(mOpenNetworks);
+
+        verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
+        verify(mNotificationBuilder).createConnectToNetworkNotification(mDummyNetwork);
+        verify(mWifiMetrics).incrementConnectToNetworkNotification(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
+        verify(mNotificationManager).notify(anyInt(), any());
+
+        when(mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, UserHandle.CURRENT))
+                .thenReturn(true);
+
+        mNotificationController.handleScanResults(mOpenNetworks);
+
+        verify(mNotificationManager).cancel(anyInt());
+    }
+
+    /**
+     * {@link ConnectToNetworkNotificationBuilder#ACTION_CONNECT_TO_NETWORK} does not connect to
+     * any network if the initial notification is not showing.
+     */
+    @Test
+    public void actionConnectToNetwork_notificationNotShowing_doesNothing() {
+        mBroadcastReceiver.onReceive(mContext,
+                new Intent(ConnectToNetworkNotificationBuilder.ACTION_CONNECT_TO_NETWORK));
+
+        verify(mWifiStateMachine, never()).sendMessage(any(Message.class));
+    }
+
+    /**
+     * {@link ConnectToNetworkNotificationBuilder#ACTION_CONNECT_TO_NETWORK} connects to the
+     * currently recommended network if it exists.
+     */
+    @Test
+    public void actionConnectToNetwork_currentRecommendationExists_connectsAndPostsNotification() {
+        mNotificationController.handleScanResults(mOpenNetworks);
+
+        verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
+        // Initial Notification
+        verify(mNotificationBuilder).createConnectToNetworkNotification(mDummyNetwork);
+        verify(mWifiMetrics).incrementConnectToNetworkNotification(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
+        verify(mNotificationManager).notify(anyInt(), any());
+
+        mBroadcastReceiver.onReceive(mContext,
+                new Intent(ConnectToNetworkNotificationBuilder.ACTION_CONNECT_TO_NETWORK));
+
+        verify(mWifiStateMachine).sendMessage(any(Message.class));
+        // Connecting Notification
+        verify(mNotificationBuilder).createNetworkConnectingNotification(mDummyNetwork);
+        verify(mWifiMetrics).incrementConnectToNetworkNotification(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_CONNECTING_TO_NETWORK);
+        verify(mWifiMetrics).incrementConnectToNetworkNotificationAction(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK,
+                ConnectToNetworkNotificationAndActionCount.ACTION_CONNECT_TO_NETWORK);
+        verify(mNotificationManager, times(2)).notify(anyInt(), any());
+    }
+
+    /**
+     * {@link ConnectToNetworkNotificationBuilder#ACTION_PICK_WIFI_NETWORK} opens Wi-Fi settings
+     * if the recommendation notification is showing.
+     */
+    @Test
+    public void actionPickWifiNetwork_currentRecommendationExists_opensWifiSettings() {
+        mNotificationController.handleScanResults(mOpenNetworks);
+
+        verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
+        // Initial Notification
+        verify(mNotificationBuilder).createConnectToNetworkNotification(mDummyNetwork);
+        verify(mWifiMetrics).incrementConnectToNetworkNotification(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
+        verify(mNotificationManager).notify(anyInt(), any());
+
+        mBroadcastReceiver.onReceive(mContext,
+                new Intent(ConnectToNetworkNotificationBuilder.ACTION_PICK_WIFI_NETWORK));
+
+        ArgumentCaptor<Intent> pickerIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).startActivity(pickerIntentCaptor.capture());
+        assertEquals(pickerIntentCaptor.getValue().getAction(), Settings.ACTION_WIFI_SETTINGS);
+        verify(mWifiMetrics).incrementConnectToNetworkNotificationAction(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK,
+                ConnectToNetworkNotificationAndActionCount.ACTION_PICK_WIFI_NETWORK);
+    }
+
+    /**
+     * {@link OpenNetworkNotifier#handleWifiConnected()} does not post connected notification if
+     * the connecting notification is not showing
+     */
+    @Test
+    public void networkConnectionSuccess_wasNotInConnectingFlow_doesNothing() {
+        mNotificationController.handleWifiConnected();
+
+        verify(mNotificationManager, never()).notify(anyInt(), any());
+        verify(mWifiMetrics, never()).incrementConnectToNetworkNotification(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_CONNECTED_TO_NETWORK);
+    }
+
+    /**
+     * {@link OpenNetworkNotifier#handleWifiConnected()} clears notification that is not connecting.
+     */
+    @Test
+    public void networkConnectionSuccess_wasShowingNotification_clearsNotification() {
+        mNotificationController.handleScanResults(mOpenNetworks);
+
+        verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
+        // Initial Notification
+        verify(mNotificationBuilder).createConnectToNetworkNotification(mDummyNetwork);
+        verify(mWifiMetrics).incrementConnectToNetworkNotification(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
+        verify(mNotificationManager).notify(anyInt(), any());
+
+        mNotificationController.handleWifiConnected();
+
+        verify(mNotificationManager).cancel(anyInt());
+    }
+
+    /**
+     * {@link OpenNetworkNotifier#handleWifiConnected()} posts the connected notification if
+     * the connecting notification is showing.
+     */
+    @Test
+    public void networkConnectionSuccess_wasInConnectingFlow_postsConnectedNotification() {
+        mNotificationController.handleScanResults(mOpenNetworks);
+
+        verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
+        // Initial Notification
+        verify(mNotificationBuilder).createConnectToNetworkNotification(mDummyNetwork);
+        verify(mWifiMetrics).incrementConnectToNetworkNotification(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
+        verify(mNotificationManager).notify(anyInt(), any());
+
+        mBroadcastReceiver.onReceive(mContext,
+                new Intent(ConnectToNetworkNotificationBuilder.ACTION_CONNECT_TO_NETWORK));
+
+        // Connecting Notification
+        verify(mNotificationBuilder).createNetworkConnectingNotification(mDummyNetwork);
+        verify(mWifiMetrics).incrementConnectToNetworkNotification(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_CONNECTING_TO_NETWORK);
+        verify(mWifiMetrics).incrementConnectToNetworkNotificationAction(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK,
+                ConnectToNetworkNotificationAndActionCount.ACTION_CONNECT_TO_NETWORK);
+        verify(mNotificationManager, times(2)).notify(anyInt(), any());
+
+        mNotificationController.handleWifiConnected();
+
+        // Connected Notification
+        verify(mNotificationBuilder).createNetworkConnectedNotification(mDummyNetwork);
+        verify(mWifiMetrics).incrementConnectToNetworkNotification(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_CONNECTED_TO_NETWORK);
+        verify(mNotificationManager, times(3)).notify(anyInt(), any());
+    }
+
+    /**
+     * {@link OpenNetworkNotifier#handleConnectionFailure()} posts the Failed to Connect
+     * notification if the connecting notification is showing.
+     */
+    @Test
+    public void networkConnectionFailure_wasNotInConnectingFlow_doesNothing() {
+        mNotificationController.handleConnectionFailure();
+
+        verify(mNotificationManager, never()).notify(anyInt(), any());
+        verify(mWifiMetrics, never()).incrementConnectToNetworkNotification(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_FAILED_TO_CONNECT);
+    }
+
+    /**
+     * {@link OpenNetworkNotifier#handleConnectionFailure()} posts the Failed to Connect
+     * notification if the connecting notification is showing.
+     */
+    @Test
+    public void networkConnectionFailure_wasInConnectingFlow_postsFailedToConnectNotification() {
+        mNotificationController.handleScanResults(mOpenNetworks);
+
+        verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
+        // Initial Notification
+        verify(mNotificationBuilder).createConnectToNetworkNotification(mDummyNetwork);
+        verify(mWifiMetrics).incrementConnectToNetworkNotification(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
+        verify(mNotificationManager).notify(anyInt(), any());
+
+        mBroadcastReceiver.onReceive(mContext,
+                new Intent(ConnectToNetworkNotificationBuilder.ACTION_CONNECT_TO_NETWORK));
+
+        // Connecting Notification
+        verify(mNotificationBuilder).createNetworkConnectingNotification(mDummyNetwork);
+        verify(mWifiMetrics).incrementConnectToNetworkNotification(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_CONNECTING_TO_NETWORK);
+        verify(mWifiMetrics).incrementConnectToNetworkNotificationAction(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK,
+                ConnectToNetworkNotificationAndActionCount.ACTION_CONNECT_TO_NETWORK);
+        verify(mNotificationManager, times(2)).notify(anyInt(), any());
+
+        mNotificationController.handleConnectionFailure();
+
+        // Failed to Connect Notification
+        verify(mNotificationBuilder).createNetworkFailedNotification();
+        verify(mWifiMetrics).incrementConnectToNetworkNotification(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_FAILED_TO_CONNECT);
+        verify(mNotificationManager, times(3)).notify(anyInt(), any());
+    }
+
+    /**
+     * When a {@link WifiManager#CONNECT_NETWORK_FAILED} is received from the connection callback
+     * of {@link WifiStateMachine#sendMessage(Message)}, a Failed to Connect notification should
+     * be posted. On tapping this notification, Wi-Fi Settings should be launched.
+     */
+    @Test
+    public void connectionFailedCallback_postsFailedToConnectNotification() throws RemoteException {
+        mNotificationController.handleScanResults(mOpenNetworks);
+
+        verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
+        // Initial Notification
+        verify(mNotificationBuilder).createConnectToNetworkNotification(mDummyNetwork);
+        verify(mWifiMetrics).incrementConnectToNetworkNotification(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
+        verify(mNotificationManager).notify(anyInt(), any());
+
+        mBroadcastReceiver.onReceive(mContext,
+                new Intent(ConnectToNetworkNotificationBuilder.ACTION_CONNECT_TO_NETWORK));
+
+        ArgumentCaptor<Message> connectMessageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mWifiStateMachine).sendMessage(connectMessageCaptor.capture());
+        Message connectMessage = connectMessageCaptor.getValue();
+
+        // Connecting Notification
+        verify(mNotificationBuilder).createNetworkConnectingNotification(mDummyNetwork);
+        verify(mWifiMetrics).incrementConnectToNetworkNotification(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_CONNECTING_TO_NETWORK);
+        verify(mWifiMetrics).incrementConnectToNetworkNotificationAction(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK,
+                ConnectToNetworkNotificationAndActionCount.ACTION_CONNECT_TO_NETWORK);
+        verify(mNotificationManager, times(2)).notify(anyInt(), any());
+
+        Message connectFailedMsg = Message.obtain();
+        connectFailedMsg.what = WifiManager.CONNECT_NETWORK_FAILED;
+        connectMessage.replyTo.send(connectFailedMsg);
+        mLooper.dispatchAll();
+
+        // Failed to Connect Notification
+        verify(mNotificationBuilder).createNetworkFailedNotification();
+        verify(mWifiMetrics).incrementConnectToNetworkNotification(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_FAILED_TO_CONNECT);
+        verify(mWifiMetrics).incrementNumOpenNetworkConnectMessageFailedToSend();
+        verify(mNotificationManager, times(3)).notify(anyInt(), any());
+
+        mBroadcastReceiver.onReceive(mContext,
+                new Intent(ConnectToNetworkNotificationBuilder
+                        .ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE));
+
+        ArgumentCaptor<Intent> pickerIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).startActivity(pickerIntentCaptor.capture());
+        assertEquals(pickerIntentCaptor.getValue().getAction(), Settings.ACTION_WIFI_SETTINGS);
+        verify(mWifiMetrics).incrementConnectToNetworkNotificationAction(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_FAILED_TO_CONNECT,
+                ConnectToNetworkNotificationAndActionCount
+                        .ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/OpenNetworkRecommenderTest.java b/tests/wifitests/src/com/android/server/wifi/OpenNetworkRecommenderTest.java
new file mode 100644
index 0000000..720ec37
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/OpenNetworkRecommenderTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2017 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.net.wifi.ScanResult;
+import android.util.ArraySet;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Tests for {@link OpenNetworkRecommender}.
+ */
+public class OpenNetworkRecommenderTest {
+
+    private static final String TEST_SSID_1 = "Test SSID 1";
+    private static final String TEST_SSID_2 = "Test SSID 2";
+    private static final int MIN_RSSI_LEVEL = -127;
+
+    private OpenNetworkRecommender mOpenNetworkRecommender;
+    private Set<String> mBlacklistedSsids;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mOpenNetworkRecommender = new OpenNetworkRecommender();
+        mBlacklistedSsids = new ArraySet<>();
+    }
+
+    private List<ScanDetail> createOpenScanResults(String... ssids) {
+        List<ScanDetail> scanResults = new ArrayList<>();
+        for (String ssid : ssids) {
+            ScanResult scanResult = new ScanResult();
+            scanResult.SSID = ssid;
+            scanResult.capabilities = "[ESS]";
+            scanResults.add(new ScanDetail(scanResult, null /* networkDetail */));
+        }
+        return scanResults;
+    }
+
+    /** If list of open networks contain only one network, that network should be returned. */
+    @Test
+    public void onlyNetworkIsRecommended() {
+        List<ScanDetail> scanResults = createOpenScanResults(TEST_SSID_1);
+        scanResults.get(0).getScanResult().level = MIN_RSSI_LEVEL;
+
+        ScanResult actual = mOpenNetworkRecommender.recommendNetwork(
+                scanResults, mBlacklistedSsids);
+        ScanResult expected = scanResults.get(0).getScanResult();
+        assertEquals(expected, actual);
+    }
+
+    /** Verifies that the network with the highest rssi is recommended. */
+    @Test
+    public void networkWithHighestRssiIsRecommended() {
+        List<ScanDetail> scanResults = createOpenScanResults(TEST_SSID_1, TEST_SSID_2);
+        scanResults.get(0).getScanResult().level = MIN_RSSI_LEVEL;
+        scanResults.get(1).getScanResult().level = MIN_RSSI_LEVEL + 1;
+
+        ScanResult actual = mOpenNetworkRecommender.recommendNetwork(
+                scanResults, mBlacklistedSsids);
+        ScanResult expected = scanResults.get(1).getScanResult();
+        assertEquals(expected, actual);
+    }
+
+    /**
+     * If the best available open network is blacklisted, no networks should be recommended.
+     */
+    @Test
+    public void blacklistBestNetworkSsid_shouldNeverRecommendNetwork() {
+        List<ScanDetail> scanResults = createOpenScanResults(TEST_SSID_1, TEST_SSID_2);
+        scanResults.get(0).getScanResult().level = MIN_RSSI_LEVEL + 1;
+        scanResults.get(1).getScanResult().level = MIN_RSSI_LEVEL;
+        mBlacklistedSsids.add(TEST_SSID_1);
+
+        ScanResult actual = mOpenNetworkRecommender.recommendNetwork(
+                scanResults, mBlacklistedSsids);
+        assertNull(actual);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/RttServiceTest.java b/tests/wifitests/src/com/android/server/wifi/RttServiceTest.java
index 689ade8..7300cb2 100644
--- a/tests/wifitests/src/com/android/server/wifi/RttServiceTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/RttServiceTest.java
@@ -41,6 +41,7 @@
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.internal.util.test.BidirectionalAsyncChannel;
+import com.android.server.wifi.util.WifiPermissionsUtil;
 
 import org.junit.After;
 import org.junit.Before;
@@ -69,6 +70,8 @@
     WifiInjector mWifiInjector;
     @Mock
     IWificond mWificond;
+    @Mock
+    WifiPermissionsUtil mWifiPermissionsUtil;
 
     RttService.RttServiceImpl mRttServiceImpl;
     ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor = ArgumentCaptor
@@ -80,6 +83,9 @@
         mLooper = new TestLooper();
         when(mWifiInjector.makeWificond()).thenReturn(mWificond);
         when(mWifiInjector.getWifiNative()).thenReturn(mWifiNative);
+        when(mWifiInjector.getWifiPermissionsUtil()).thenReturn(mWifiPermissionsUtil);
+        when(mWifiPermissionsUtil.checkCallersLocationPermission(any(), anyInt()))
+                .thenReturn(true);
         mRttServiceImpl = new RttService.RttServiceImpl(mContext, mLooper.getLooper(),
                 mWifiInjector);
         mRttServiceImpl.startService();
@@ -100,7 +106,7 @@
     // Create and connect a bi-directional async channel.
     private BidirectionalAsyncChannel connectChannel(Handler handler) {
         BidirectionalAsyncChannel channel = new BidirectionalAsyncChannel();
-        channel.connect(mLooper.getLooper(), mRttServiceImpl.getMessenger(),
+        channel.connect(mLooper.getLooper(), mRttServiceImpl.getMessenger(null, new int[1]),
                 handler);
         mLooper.dispatchAll();
         channel.assertConnected();
@@ -271,6 +277,22 @@
     }
 
     /**
+     * Test RTT fails without proper location permission
+     */
+    @Test
+    public void testEnableResponderFailureNoPermission() throws Exception {
+        when(mWifiPermissionsUtil.checkCallersLocationPermission(any(), anyInt()))
+                .thenReturn(false);
+        startWifi();
+        Handler handler = mock(Handler.class);
+        BidirectionalAsyncChannel channel = connectChannel(handler);
+        Message message = sendEnableResponder(channel, handler, CLIENT_KEY1, null);
+        // RTT operations failed without proper permission.
+        assertEquals("expected permission denied, but got " + message.what,
+                RttManager.REASON_PERMISSION_DENIED, message.arg1);
+    }
+
+    /**
      * Test RTT ranging with empty RttParams.
      */
     @Test
diff --git a/tests/wifitests/src/com/android/server/wifi/SsidSetStoreDataTest.java b/tests/wifitests/src/com/android/server/wifi/SsidSetStoreDataTest.java
new file mode 100644
index 0000000..606b825
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/SsidSetStoreDataTest.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2017 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.SsidSetStoreData}.
+ */
+public class SsidSetStoreDataTest {
+    private static final String TEST_NOTIFIER_NAME = "TestNetwork";
+    private static final String TEST_SSID1 = "SSID 1";
+    private static final String TEST_SSID2 = "SSID 2";
+    private static final String TEST_SSID_SET_XML_STRING =
+            "<set name=\"SSIDSet\">\n"
+                    + "<string>" + TEST_SSID1 + "</string>\n"
+                    + "<string>" + TEST_SSID2 + "</string>\n"
+                    + "</set>\n";
+    private static final byte[] TEST_SSID_SET_XML_BYTES =
+            TEST_SSID_SET_XML_STRING.getBytes(StandardCharsets.UTF_8);
+
+    @Mock SsidSetStoreData.DataSource mDataSource;
+    SsidSetStoreData mSsidSetStoreData;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mSsidSetStoreData = new SsidSetStoreData(TEST_NOTIFIER_NAME, mDataSource);
+    }
+
+    /**
+     * Helper function for serializing configuration data to a XML block.
+     *
+     * @param shared Flag indicating serializing shared or user configurations
+     * @return byte[] of the XML data
+     * @throws Exception
+     */
+    private byte[] serializeData(boolean shared) throws Exception {
+        final XmlSerializer out = new FastXmlSerializer();
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        out.setOutput(outputStream, StandardCharsets.UTF_8.name());
+        mSsidSetStoreData.serializeData(out, shared);
+        out.flush();
+        return outputStream.toByteArray();
+    }
+
+    /**
+     * Helper function for parsing configuration data from a XML block.
+     *
+     * @param data XML data to parse from
+     * @param shared Flag indicating parsing of shared or user configurations
+     * @throws Exception
+     */
+    private void deserializeData(byte[] data, boolean shared) throws Exception {
+        final XmlPullParser in = Xml.newPullParser();
+        final ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
+        in.setInput(inputStream, StandardCharsets.UTF_8.name());
+        mSsidSetStoreData.deserializeData(in, in.getDepth(), shared);
+    }
+
+    /**
+     * Verify that a XmlPullParserException will be thrown when attempting to serialize data
+     * to the share store.
+     *
+     * @throws Exception
+     */
+    @Test(expected = XmlPullParserException.class)
+    public void serializeShareData() throws Exception {
+        serializeData(true /* shared */);
+    }
+
+    /**
+     * Verify that a XmlPullParserException will be thrown when attempting to deserialize
+     * data from the share store.
+     *
+     * @throws Exception
+     */
+    @Test(expected = XmlPullParserException.class)
+    public void deserializeShareData() throws Exception {
+        deserializeData(new byte[0], true /* shared */);
+    }
+
+    /**
+     * Verify that serializing the user store data without any configuration doesn't cause any
+     * crash and no data should be serialized.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void serializeEmptyConfigs() throws Exception {
+        when(mDataSource.getSsids()).thenReturn(new HashSet<String>());
+        assertEquals(0, serializeData(false /* shared */).length);
+    }
+
+    /**
+     * Verify that parsing an empty data doesn't cause any crash and no configuration should
+     * be deserialized.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void deserializeEmptyStoreData() throws Exception {
+        deserializeData(new byte[0], false /* shared */);
+        verify(mDataSource, never()).setSsids(any(Set.class));
+    }
+
+    /**
+     * Verify that {@link SsidSetStoreData} does not support share data.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void supportShareData() throws Exception {
+        assertFalse(mSsidSetStoreData.supportShareData());
+    }
+
+    /**
+     * Verify that the store data is serialized correctly, matches the predefined test XML data.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void serializeSsidSet() throws Exception {
+        Set<String> ssidSet = new HashSet<>();
+        ssidSet.add(TEST_SSID1);
+        ssidSet.add(TEST_SSID2);
+        when(mDataSource.getSsids()).thenReturn(ssidSet);
+        byte[] actualData = serializeData(false /* shared */);
+        assertTrue(Arrays.equals(TEST_SSID_SET_XML_BYTES, actualData));
+    }
+
+    /**
+     * Verify that the store data is deserialized correctly using the predefined test XML data.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void deserializeSsidSet() throws Exception {
+        Set<String> ssidSet = new HashSet<>();
+        ssidSet.add(TEST_SSID1);
+        ssidSet.add(TEST_SSID2);
+        deserializeData(TEST_SSID_SET_XML_BYTES, false /* shared */);
+        verify(mDataSource).setSsids(eq(ssidSet));
+    }
+
+    /**
+     * Verify that a XmlPullParserException will be thrown when parsing a SSIDSet set with an
+     * unknown tag.
+     *
+     * @throws Exception
+     */
+    @Test(expected = XmlPullParserException.class)
+    public void parseSetWithUnknownTag() throws Exception {
+        String ssidSet =
+                "<set name=\"SSIDSet\">\n"
+                        + "<string>" + TEST_SSID1 + "</string>\n"
+                        + "<string>" + TEST_SSID2 + "</string>\n"
+                        + "<Unknown>" + "badInput" + "</Unknown>" // Unknown tag.
+                        + "</set>\n";
+        deserializeData(ssidSet.getBytes(StandardCharsets.UTF_8), false /* shared */);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiApConfigStoreTest.java b/tests/wifitests/src/com/android/server/wifi/WifiApConfigStoreTest.java
index 02064d8..a923475 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiApConfigStoreTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiApConfigStoreTest.java
@@ -268,7 +268,9 @@
         assertFalse(WifiApConfigStore.validateApWifiConfiguration(config));
 
         // now check a valid SSID with a random length
-        config.SSID = generateRandomString(mRandom.nextInt(WifiApConfigStore.SSID_MAX_LEN + 1));
+        int validLength = WifiApConfigStore.SSID_MAX_LEN - WifiApConfigStore.SSID_MIN_LEN;
+        config.SSID = generateRandomString(
+                mRandom.nextInt(validLength) + WifiApConfigStore.SSID_MIN_LEN);
         assertTrue(WifiApConfigStore.validateApWifiConfiguration(config));
     }
 
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java
index 7509c18..0fa6600 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java
@@ -43,7 +43,6 @@
 import android.util.Pair;
 
 import com.android.internal.R;
-import com.android.server.wifi.WifiConfigStoreLegacy.WifiConfigStoreDataLegacy;
 import com.android.server.wifi.util.WifiPermissionsUtil;
 import com.android.server.wifi.util.WifiPermissionsWrapper;
 
@@ -101,7 +100,6 @@
     @Mock private TelephonyManager mTelephonyManager;
     @Mock private WifiKeyStore mWifiKeyStore;
     @Mock private WifiConfigStore mWifiConfigStore;
-    @Mock private WifiConfigStoreLegacy mWifiConfigStoreLegacy;
     @Mock private PackageManager mPackageManager;
     @Mock private DevicePolicyManagerInternal mDevicePolicyManagerInternal;
     @Mock private WifiPermissionsUtil mWifiPermissionsUtil;
@@ -1144,7 +1142,7 @@
         ScanDetailCache retrievedScanDetailCache =
                 mWifiConfigManager.getScanDetailCacheForNetwork(result.getNetworkId());
         assertEquals(1, retrievedScanDetailCache.size());
-        ScanResult retrievedScanResult = retrievedScanDetailCache.get(scanResult.BSSID);
+        ScanResult retrievedScanResult = retrievedScanDetailCache.getScanResult(scanResult.BSSID);
 
         ScanTestUtil.assertScanResultEquals(scanResult, retrievedScanResult);
     }
@@ -1521,6 +1519,45 @@
     }
 
     /**
+     * Verifies that the list of PNO networks does not contain any permanently or temporarily
+     * disabled networks.
+     * {@link WifiConfigManager#retrievePnoNetworkList()}.
+     */
+    @Test
+    public void testRetrievePnoListDoesNotContainDisabledNetworks() throws Exception {
+        // Create and add 2 networks.
+        WifiConfiguration network1 = WifiConfigurationTestUtil.createEapNetwork();
+        WifiConfiguration network2 = WifiConfigurationTestUtil.createPskNetwork();
+
+        NetworkUpdateResult result1 = verifyAddNetworkToWifiConfigManager(network1);
+        NetworkUpdateResult result2 = verifyAddNetworkToWifiConfigManager(network2);
+
+        // Enable all of them.
+        verifyUpdateNetworkSelectionStatus(
+                result1.getNetworkId(), NetworkSelectionStatus.NETWORK_SELECTION_ENABLE, 0);
+        verifyUpdateNetworkSelectionStatus(
+                result2.getNetworkId(), NetworkSelectionStatus.NETWORK_SELECTION_ENABLE, 0);
+
+        // Set network1 to temporarily disabled. The threshold for association rejection is 5, so
+        // disable it 5 times to actually mark it temporarily disabled.
+        int assocRejectReason = NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION;
+        int assocRejectThreshold =
+                WifiConfigManager.NETWORK_SELECTION_DISABLE_THRESHOLD[assocRejectReason];
+        for (int i = 1; i <= assocRejectThreshold; i++) {
+            verifyUpdateNetworkSelectionStatus(result1.getNetworkId(), assocRejectReason, i);
+        }
+
+        // Set network 2 to permanently disabled.
+        verifyUpdateNetworkSelectionStatus(
+                result2.getNetworkId(), NetworkSelectionStatus.DISABLED_BY_WIFI_MANAGER, 0);
+
+        // Retrieve the Pno network list & verify both networks are not included.
+        List<WifiScanner.PnoSettings.PnoNetwork> pnoNetworks =
+                mWifiConfigManager.retrievePnoNetworkList();
+        assertEquals(0, pnoNetworks.size());
+    }
+
+    /**
      * Verifies the linking of networks when they have the same default GW Mac address in
      * {@link WifiConfigManager#getOrCreateScanDetailCacheForNetwork(WifiConfiguration)}.
      */
@@ -2369,7 +2406,7 @@
     }
 
     /**
-     * Verifies that the foreground user stop using {@link WifiConfigManager#handleUserStop(int)}
+     * Verifies that the user stop handling using {@link WifiConfigManager#handleUserStop(int)}
      * and ensures that the store is written only when the foreground user is stopped.
      */
     @Test
@@ -2392,6 +2429,49 @@
     }
 
     /**
+     * Verifies that the user stop handling using {@link WifiConfigManager#handleUserStop(int)}
+     * and ensures that the shared data is not lost when the foreground user is stopped.
+     */
+    @Test
+    public void testHandleUserStopDoesNotClearSharedData() throws Exception {
+        int user1 = TEST_DEFAULT_USER;
+
+        //
+        // Setup the database for the user before initiating stop.
+        //
+        int appId = 674;
+        // Create 2 networks. 1 for user1, and 1 shared.
+        final WifiConfiguration user1Network = WifiConfigurationTestUtil.createPskNetwork();
+        user1Network.shared = false;
+        user1Network.creatorUid = UserHandle.getUid(user1, appId);
+        final WifiConfiguration sharedNetwork = WifiConfigurationTestUtil.createPskNetwork();
+
+        // Set up the store data that is loaded initially.
+        List<WifiConfiguration> sharedNetworks = new ArrayList<WifiConfiguration>() {
+            {
+                add(sharedNetwork);
+            }
+        };
+        List<WifiConfiguration> user1Networks = new ArrayList<WifiConfiguration>() {
+            {
+                add(user1Network);
+            }
+        };
+        setupStoreDataForRead(sharedNetworks, user1Networks, new HashSet<String>());
+        assertTrue(mWifiConfigManager.loadFromStore());
+        verify(mWifiConfigStore).read();
+
+        // Ensure that we have 2 networks in the database before the stop.
+        assertEquals(2, mWifiConfigManager.getConfiguredNetworks().size());
+
+        mWifiConfigManager.handleUserStop(user1);
+
+        // Ensure that we only have 1 shared network in the database after the stop.
+        assertEquals(1, mWifiConfigManager.getConfiguredNetworks().size());
+        assertEquals(sharedNetwork.SSID, mWifiConfigManager.getConfiguredNetworks().get(0).SSID);
+    }
+
+    /**
      * Verifies the foreground user unlock via {@link WifiConfigManager#handleUserUnlock(int)}
      * results in a store read after bootup.
      */
@@ -2539,55 +2619,6 @@
     }
 
     /**
-     * Verifies the loading of networks using {@link WifiConfigManager#migrateFromLegacyStore()} ()}
-     * attempts to migrate data from legacy stores when the legacy store files are present.
-     */
-    @Test
-    public void testMigrationFromLegacyStore() throws Exception {
-        // Create the store data to be returned from legacy stores.
-        List<WifiConfiguration> networks = new ArrayList<>();
-        networks.add(WifiConfigurationTestUtil.createPskNetwork());
-        networks.add(WifiConfigurationTestUtil.createEapNetwork());
-        networks.add(WifiConfigurationTestUtil.createWepNetwork());
-        String deletedEphemeralSSID = "EphemeralSSID";
-        Set<String> deletedEphermalSSIDs = new HashSet<>(Arrays.asList(deletedEphemeralSSID));
-        WifiConfigStoreDataLegacy storeData =
-                new WifiConfigStoreDataLegacy(networks, deletedEphermalSSIDs);
-
-        when(mWifiConfigStoreLegacy.areStoresPresent()).thenReturn(true);
-        when(mWifiConfigStoreLegacy.read()).thenReturn(storeData);
-
-        // Now trigger the migration from legacy store. This should populate the in memory list with
-        // all the networks above from the legacy store.
-        assertTrue(mWifiConfigManager.migrateFromLegacyStore());
-
-        verify(mWifiConfigStoreLegacy).read();
-        verify(mWifiConfigStoreLegacy).removeStores();
-
-        List<WifiConfiguration> retrievedNetworks =
-                mWifiConfigManager.getConfiguredNetworksWithPasswords();
-        WifiConfigurationTestUtil.assertConfigurationsEqualForConfigManagerAddOrUpdate(
-                networks, retrievedNetworks);
-        assertTrue(mWifiConfigManager.wasEphemeralNetworkDeleted(deletedEphemeralSSID));
-    }
-
-    /**
-     * Verifies the loading of networks using {@link WifiConfigManager#migrateFromLegacyStore()} ()}
-     * does not attempt to migrate data from legacy stores when the legacy store files are absent
-     * (i.e migration was already done once).
-     */
-    @Test
-    public void testNoDuplicateMigrationFromLegacyStore() throws Exception {
-        when(mWifiConfigStoreLegacy.areStoresPresent()).thenReturn(false);
-
-        // Now trigger a migration from legacy store.
-        assertTrue(mWifiConfigManager.migrateFromLegacyStore());
-
-        verify(mWifiConfigStoreLegacy, never()).read();
-        verify(mWifiConfigStoreLegacy, never()).removeStores();
-    }
-
-    /**
      * Verifies the loading of networks using {@link WifiConfigManager#loadFromStore()} does
      * not attempt to read from any of the stores (new or legacy) when the store files are
      * not present.
@@ -2595,12 +2626,10 @@
     @Test
     public void testFreshInstallDoesNotLoadFromStore() throws Exception {
         when(mWifiConfigStore.areStoresPresent()).thenReturn(false);
-        when(mWifiConfigStoreLegacy.areStoresPresent()).thenReturn(false);
 
         assertTrue(mWifiConfigManager.loadFromStore());
 
         verify(mWifiConfigStore, never()).read();
-        verify(mWifiConfigStoreLegacy, never()).read();
 
         assertTrue(mWifiConfigManager.getConfiguredNetworksWithPasswords().isEmpty());
     }
@@ -2613,11 +2642,9 @@
     public void testHandleUserSwitchAfterFreshInstall() throws Exception {
         int user2 = TEST_DEFAULT_USER + 1;
         when(mWifiConfigStore.areStoresPresent()).thenReturn(false);
-        when(mWifiConfigStoreLegacy.areStoresPresent()).thenReturn(false);
 
         assertTrue(mWifiConfigManager.loadFromStore());
         verify(mWifiConfigStore, never()).read();
-        verify(mWifiConfigStoreLegacy, never()).read();
 
         setupStoreDataForUserRead(new ArrayList<WifiConfiguration>(), new HashSet<String>());
         // Now switch the user to user 2.
@@ -3376,7 +3403,7 @@
         mWifiConfigManager =
                 new WifiConfigManager(
                         mContext, mClock, mUserManager, mTelephonyManager,
-                        mWifiKeyStore, mWifiConfigStore, mWifiConfigStoreLegacy,
+                        mWifiKeyStore, mWifiConfigStore,
                         mWifiPermissionsUtil, mWifiPermissionsWrapper, mNetworkListStoreData,
                         mDeletedEphemeralSsidsStoreData);
         mWifiConfigManager.enableVerboseLogging(1);
@@ -3971,7 +3998,7 @@
         ScanDetailCache retrievedScanDetailCache =
                 mWifiConfigManager.getScanDetailCacheForNetwork(network.networkId);
         assertEquals(1, retrievedScanDetailCache.size());
-        ScanResult retrievedScanResult = retrievedScanDetailCache.get(scanResult.BSSID);
+        ScanResult retrievedScanResult = retrievedScanDetailCache.getScanResult(scanResult.BSSID);
 
         ScanTestUtil.assertScanResultEquals(scanResult, retrievedScanResult);
     }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreLegacyTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreLegacyTest.java
deleted file mode 100644
index 4b4e875..0000000
--- a/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreLegacyTest.java
+++ /dev/null
@@ -1,273 +0,0 @@
-/*
- * Copyright (C) 2016 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 static org.junit.Assert.*;
-import static org.mockito.Mockito.*;
-
-import android.app.test.MockAnswerUtil.AnswerWithArguments;
-import android.net.IpConfiguration;
-import android.net.wifi.WifiConfiguration;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.text.TextUtils;
-import android.util.SparseArray;
-
-import com.android.server.net.IpConfigStore;
-import com.android.server.wifi.hotspot2.LegacyPasspointConfig;
-import com.android.server.wifi.hotspot2.LegacyPasspointConfigParser;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Unit tests for {@link com.android.server.wifi.WifiConfigStoreLegacy}.
- */
-@SmallTest
-public class WifiConfigStoreLegacyTest {
-    private static final String MASKED_FIELD_VALUE = "*";
-
-    // Test mocks
-    @Mock private WifiNative mWifiNative;
-    @Mock private WifiNetworkHistory mWifiNetworkHistory;
-    @Mock private IpConfigStore mIpconfigStore;
-    @Mock private LegacyPasspointConfigParser mPasspointConfigParser;
-
-    /**
-     * Test instance of WifiConfigStore.
-     */
-    private WifiConfigStoreLegacy mWifiConfigStore;
-
-
-    /**
-     * Setup the test environment.
-     */
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
-        mWifiConfigStore = new WifiConfigStoreLegacy(mWifiNetworkHistory, mWifiNative,
-                mIpconfigStore, mPasspointConfigParser);
-    }
-
-    /**
-     * Called after each test
-     */
-    @After
-    public void cleanup() {
-        validateMockitoUsage();
-    }
-
-    /**
-     * Verify loading of network configurations from legacy stores. This is verifying the population
-     * of the masked wpa_supplicant fields using wpa_supplicant.conf file.
-     */
-    @Test
-    public void testLoadFromStores() throws Exception {
-        WifiConfiguration pskNetwork = WifiConfigurationTestUtil.createPskNetwork();
-        WifiConfiguration wepNetwork = WifiConfigurationTestUtil.createWepNetwork();
-        WifiConfiguration eapNetwork = WifiConfigurationTestUtil.createEapNetwork();
-        WifiConfiguration passpointNetwork = WifiConfigurationTestUtil.createPasspointNetwork();
-        eapNetwork.enterpriseConfig.setPassword("EapPassword");
-
-        // Initialize Passpoint configuration data.
-        int passpointNetworkId = 1234;
-        String fqdn = passpointNetwork.FQDN;
-        String providerFriendlyName = passpointNetwork.providerFriendlyName;
-        long[] roamingConsortiumIds = new long[] {0x1234, 0x5678};
-        String realm = "test.com";
-        String imsi = "214321";
-
-        // Update Passpoint network.
-        // Network ID is used for lookup network extras, so use an unique ID for passpoint network.
-        passpointNetwork.networkId = passpointNetworkId;
-        passpointNetwork.enterpriseConfig.setPassword("PaspointPassword");
-        // Reset FQDN and provider friendly name so that the derived network from #read will
-        // obtained these information from networkExtras and {@link LegacyPasspointConfigParser}.
-        passpointNetwork.FQDN = null;
-        passpointNetwork.providerFriendlyName = null;
-
-        final List<WifiConfiguration> networks = new ArrayList<>();
-        networks.add(pskNetwork);
-        networks.add(wepNetwork);
-        networks.add(eapNetwork);
-        networks.add(passpointNetwork);
-
-        // Setup legacy Passpoint configuration data.
-        Map<String, LegacyPasspointConfig> passpointConfigs = new HashMap<>();
-        LegacyPasspointConfig passpointConfig = new LegacyPasspointConfig();
-        passpointConfig.mFqdn = fqdn;
-        passpointConfig.mFriendlyName = providerFriendlyName;
-        passpointConfig.mRoamingConsortiumOis = roamingConsortiumIds;
-        passpointConfig.mRealm = realm;
-        passpointConfig.mImsi = imsi;
-        passpointConfigs.put(fqdn, passpointConfig);
-
-        // Return the config data with passwords masked from wpa_supplicant control interface.
-        doAnswer(new AnswerWithArguments() {
-            public boolean answer(Map<String, WifiConfiguration> configs,
-                    SparseArray<Map<String, String>> networkExtras) {
-                for (Map.Entry<String, WifiConfiguration> entry:
-                        createWpaSupplicantLoadData(networks).entrySet()) {
-                    configs.put(entry.getKey(), entry.getValue());
-                }
-                // Setup networkExtras for Passpoint configuration.
-                networkExtras.put(passpointNetworkId, createNetworkExtrasForPasspointConfig(fqdn));
-                return true;
-            }
-        }).when(mWifiNative).migrateNetworksFromSupplicant(any(Map.class), any(SparseArray.class));
-
-        when(mPasspointConfigParser.parseConfig(anyString())).thenReturn(passpointConfigs);
-        WifiConfigStoreLegacy.WifiConfigStoreDataLegacy storeData = mWifiConfigStore.read();
-
-        // Update the expected configuration for Passpoint network.
-        passpointNetwork.isLegacyPasspointConfig = true;
-        passpointNetwork.FQDN = fqdn;
-        passpointNetwork.providerFriendlyName = providerFriendlyName;
-        passpointNetwork.roamingConsortiumIds = roamingConsortiumIds;
-        passpointNetwork.enterpriseConfig.setRealm(realm);
-        passpointNetwork.enterpriseConfig.setPlmn(imsi);
-
-        WifiConfigurationTestUtil.assertConfigurationsEqualForConfigStore(
-                networks, storeData.getConfigurations());
-    }
-
-    private SparseArray<IpConfiguration> createIpConfigStoreLoadData(
-            List<WifiConfiguration> configurations) {
-        SparseArray<IpConfiguration> newIpConfigurations = new SparseArray<>();
-        for (WifiConfiguration config : configurations) {
-            newIpConfigurations.put(
-                    config.configKey().hashCode(),
-                    new IpConfiguration(config.getIpConfiguration()));
-        }
-        return newIpConfigurations;
-    }
-
-    private Map<String, String> createPskMap(List<WifiConfiguration> configurations) {
-        Map<String, String> pskMap = new HashMap<>();
-        for (WifiConfiguration config : configurations) {
-            if (!TextUtils.isEmpty(config.preSharedKey)) {
-                pskMap.put(config.configKey(), config.preSharedKey);
-            }
-        }
-        return pskMap;
-    }
-
-    private Map<String, String> createWepKey0Map(List<WifiConfiguration> configurations) {
-        Map<String, String> wepKeyMap = new HashMap<>();
-        for (WifiConfiguration config : configurations) {
-            if (!TextUtils.isEmpty(config.wepKeys[0])) {
-                wepKeyMap.put(config.configKey(), config.wepKeys[0]);
-            }
-        }
-        return wepKeyMap;
-    }
-
-    private Map<String, String> createWepKey1Map(List<WifiConfiguration> configurations) {
-        Map<String, String> wepKeyMap = new HashMap<>();
-        for (WifiConfiguration config : configurations) {
-            if (!TextUtils.isEmpty(config.wepKeys[1])) {
-                wepKeyMap.put(config.configKey(), config.wepKeys[1]);
-            }
-        }
-        return wepKeyMap;
-    }
-
-    private Map<String, String> createWepKey2Map(List<WifiConfiguration> configurations) {
-        Map<String, String> wepKeyMap = new HashMap<>();
-        for (WifiConfiguration config : configurations) {
-            if (!TextUtils.isEmpty(config.wepKeys[2])) {
-                wepKeyMap.put(config.configKey(), config.wepKeys[2]);
-            }
-        }
-        return wepKeyMap;
-    }
-
-    private Map<String, String> createWepKey3Map(List<WifiConfiguration> configurations) {
-        Map<String, String> wepKeyMap = new HashMap<>();
-        for (WifiConfiguration config : configurations) {
-            if (!TextUtils.isEmpty(config.wepKeys[3])) {
-                wepKeyMap.put(config.configKey(), config.wepKeys[3]);
-            }
-        }
-        return wepKeyMap;
-    }
-
-    private Map<String, String> createEapPasswordMap(List<WifiConfiguration> configurations) {
-        Map<String, String> eapPasswordMap = new HashMap<>();
-        for (WifiConfiguration config : configurations) {
-            if (!TextUtils.isEmpty(config.enterpriseConfig.getPassword())) {
-                eapPasswordMap.put(config.configKey(), config.enterpriseConfig.getPassword());
-            }
-        }
-        return eapPasswordMap;
-    }
-
-    private Map<String, WifiConfiguration> createWpaSupplicantLoadData(
-            List<WifiConfiguration> configurations) {
-        Map<String, WifiConfiguration> configurationMap = new HashMap<>();
-        for (WifiConfiguration config : configurations) {
-            configurationMap.put(config.configKey(true), config);
-        }
-        return configurationMap;
-    }
-
-    private List<WifiConfiguration> createMaskedWifiConfigurations(
-            List<WifiConfiguration> configurations) {
-        List<WifiConfiguration> newConfigurations = new ArrayList<>();
-        for (WifiConfiguration config : configurations) {
-            newConfigurations.add(createMaskedWifiConfiguration(config));
-        }
-        return newConfigurations;
-    }
-
-    private WifiConfiguration createMaskedWifiConfiguration(WifiConfiguration configuration) {
-        WifiConfiguration newConfig = new WifiConfiguration(configuration);
-        if (!TextUtils.isEmpty(configuration.preSharedKey)) {
-            newConfig.preSharedKey = MASKED_FIELD_VALUE;
-        }
-        if (!TextUtils.isEmpty(configuration.wepKeys[0])) {
-            newConfig.wepKeys[0] = MASKED_FIELD_VALUE;
-        }
-        if (!TextUtils.isEmpty(configuration.wepKeys[1])) {
-            newConfig.wepKeys[1] = MASKED_FIELD_VALUE;
-        }
-        if (!TextUtils.isEmpty(configuration.wepKeys[2])) {
-            newConfig.wepKeys[2] = MASKED_FIELD_VALUE;
-        }
-        if (!TextUtils.isEmpty(configuration.wepKeys[3])) {
-            newConfig.wepKeys[3] = MASKED_FIELD_VALUE;
-        }
-        if (!TextUtils.isEmpty(configuration.enterpriseConfig.getPassword())) {
-            newConfig.enterpriseConfig.setPassword(MASKED_FIELD_VALUE);
-        }
-        return newConfig;
-    }
-
-    private Map<String, String> createNetworkExtrasForPasspointConfig(String fqdn) {
-        Map<String, String> extras = new HashMap<>();
-        extras.put(SupplicantStaNetworkHal.ID_STRING_KEY_FQDN, fqdn);
-        return extras;
-    }
-}
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java
index 47efed3..0958fea 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java
@@ -21,6 +21,7 @@
 
 import android.app.test.TestAlarmManager;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.net.wifi.WifiConfiguration;
 import android.os.test.TestLooper;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -60,6 +61,7 @@
 
     private static final String TEST_USER_DATA = "UserData";
     private static final String TEST_SHARE_DATA = "ShareData";
+    private static final String TEST_CREATOR_NAME = "CreatorName";
 
     private static final String TEST_DATA_XML_STRING_FORMAT =
             "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
@@ -91,10 +93,11 @@
                     + "<boolean name=\"NoInternetAccessExpected\" value=\"false\" />\n"
                     + "<int name=\"UserApproved\" value=\"0\" />\n"
                     + "<boolean name=\"MeteredHint\" value=\"false\" />\n"
+                    + "<int name=\"MeteredOverride\" value=\"0\" />\n"
                     + "<boolean name=\"UseExternalScores\" value=\"false\" />\n"
                     + "<int name=\"NumAssociation\" value=\"0\" />\n"
                     + "<int name=\"CreatorUid\" value=\"%d\" />\n"
-                    + "<null name=\"CreatorName\" />\n"
+                    + "<string name=\"CreatorName\">%s</string>\n"
                     + "<null name=\"CreationTime\" />\n"
                     + "<int name=\"LastUpdateUid\" value=\"-1\" />\n"
                     + "<null name=\"LastUpdateName\" />\n"
@@ -124,6 +127,7 @@
 
     // Test mocks
     @Mock private Context mContext;
+    @Mock private PackageManager mPackageManager;
     private TestAlarmManager mAlarmManager;
     private TestLooper mLooper;
     @Mock private Clock mClock;
@@ -145,6 +149,8 @@
         mLooper = new TestLooper();
         when(mContext.getSystemService(Context.ALARM_SERVICE))
                 .thenReturn(mAlarmManager.getAlarmManager());
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mPackageManager.getNameForUid(anyInt())).thenReturn(TEST_CREATOR_NAME);
         mUserStore = new MockStoreFile();
         mSharedStore = new MockStoreFile();
         mStoreData = new MockStoreData();
@@ -363,9 +369,10 @@
     @Test
     public void testReadWifiConfigStoreData() throws Exception {
         // Setup network list.
-        NetworkListStoreData networkList = new NetworkListStoreData();
+        NetworkListStoreData networkList = new NetworkListStoreData(mContext);
         mWifiConfigStore.registerStoreData(networkList);
         WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        openNetwork.creatorName = TEST_CREATOR_NAME;
         openNetwork.setIpConfiguration(
                 WifiConfigurationTestUtil.createDHCPIpConfigurationWithNoProxy());
         List<WifiConfiguration> userConfigs = new ArrayList<>();
@@ -383,7 +390,7 @@
         String xmlString = String.format(TEST_DATA_XML_STRING_FORMAT,
                 openNetwork.configKey().replaceAll("\"", "&quot;"),
                 openNetwork.SSID.replaceAll("\"", "&quot;"),
-                openNetwork.shared, openNetwork.creatorUid, testSsid);
+                openNetwork.shared, openNetwork.creatorUid, openNetwork.creatorName, testSsid);
         byte[] xmlBytes = xmlString.getBytes(StandardCharsets.UTF_8);
         mUserStore.storeRawDataToWrite(xmlBytes);
 
@@ -405,9 +412,10 @@
         mWifiConfigStore.switchUserStoreAndRead(mUserStore);
 
         // Setup network list store data.
-        NetworkListStoreData networkList = new NetworkListStoreData();
+        NetworkListStoreData networkList = new NetworkListStoreData(mContext);
         mWifiConfigStore.registerStoreData(networkList);
         WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        openNetwork.creatorName = TEST_CREATOR_NAME;
         openNetwork.setIpConfiguration(
                 WifiConfigurationTestUtil.createDHCPIpConfigurationWithNoProxy());
         List<WifiConfiguration> userConfigs = new ArrayList<>();
@@ -427,7 +435,7 @@
         String xmlString = String.format(TEST_DATA_XML_STRING_FORMAT,
                 openNetwork.configKey().replaceAll("\"", "&quot;"),
                 openNetwork.SSID.replaceAll("\"", "&quot;"),
-                openNetwork.shared, openNetwork.creatorUid, testSsid);
+                openNetwork.shared, openNetwork.creatorUid, openNetwork.creatorName, testSsid);
         byte[] xmlBytes = xmlString.getBytes(StandardCharsets.UTF_8);
 
         mWifiConfigStore.write(true);
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java
index bdb6b14..4fabd9d 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java
@@ -41,6 +41,7 @@
 import android.net.wifi.WifiScanner.ScanListener;
 import android.net.wifi.WifiScanner.ScanSettings;
 import android.net.wifi.WifiSsid;
+import android.os.Process;
 import android.os.SystemClock;
 import android.os.WorkSource;
 import android.os.test.TestLooper;
@@ -123,7 +124,7 @@
     @Mock private NetworkScoreManager mNetworkScoreManager;
     @Mock private Clock mClock;
     @Mock private WifiLastResortWatchdog mWifiLastResortWatchdog;
-    @Mock private WifiNotificationController mWifiNotificationController;
+    @Mock private OpenNetworkNotifier mOpenNetworkNotifier;
     @Mock private WifiMetrics mWifiMetrics;
     @Mock private WifiNetworkScoreCache mScoreCache;
     @Captor ArgumentCaptor<ScanResult> mCandidateScanResultCaptor;
@@ -294,7 +295,7 @@
     WifiConnectivityManager createConnectivityManager() {
         return new WifiConnectivityManager(mContext, mWifiStateMachine, mWifiScanner,
                 mWifiConfigManager, mWifiInfo, mWifiNS, mWifiConnectivityHelper,
-                mWifiLastResortWatchdog, mWifiNotificationController, mWifiMetrics,
+                mWifiLastResortWatchdog, mOpenNetworkNotifier, mWifiMetrics,
                 mLooper.getLooper(), mClock, mLocalLog, true, mFrameworkFacade, null, null, null);
     }
 
@@ -314,7 +315,8 @@
         mWifiConnectivityManager.handleConnectionStateChanged(
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
 
-        verify(mWifiStateMachine).startConnectToNetwork(CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
+        verify(mWifiStateMachine).startConnectToNetwork(
+                CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
     }
 
     /**
@@ -333,7 +335,8 @@
         mWifiConnectivityManager.handleConnectionStateChanged(
                 WifiConnectivityManager.WIFI_STATE_CONNECTED);
 
-        verify(mWifiStateMachine).startConnectToNetwork(CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
+        verify(mWifiStateMachine).startConnectToNetwork(
+                CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
     }
 
     /**
@@ -353,7 +356,7 @@
         mWifiConnectivityManager.handleScreenStateChanged(true);
 
         verify(mWifiStateMachine, atLeastOnce()).startConnectToNetwork(
-                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
+                CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
     }
 
     /**
@@ -373,7 +376,7 @@
         mWifiConnectivityManager.handleScreenStateChanged(true);
 
         verify(mWifiStateMachine, atLeastOnce()).startConnectToNetwork(
-                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
+                CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
     }
 
     /**
@@ -400,7 +403,7 @@
         mWifiConnectivityManager.handleScreenStateChanged(true);
 
         verify(mWifiStateMachine, times(0)).startConnectToNetwork(
-                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
+                CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
     }
 
     /**
@@ -438,7 +441,7 @@
 
         // Verify that we attempt to connect upto the rate.
         verify(mWifiStateMachine, times(numAttempts)).startConnectToNetwork(
-                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
+                CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
     }
 
     /**
@@ -479,7 +482,7 @@
 
         // Verify that all the connection attempts went through
         verify(mWifiStateMachine, times(numAttempts)).startConnectToNetwork(
-                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
+                CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
     }
 
     /**
@@ -523,7 +526,7 @@
 
         // Verify that all the connection attempts went through
         verify(mWifiStateMachine, times(numAttempts)).startConnectToNetwork(
-                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
+                CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
     }
 
     /**
@@ -609,12 +612,12 @@
     }
 
     /**
-     * {@link WifiNotificationController} handles scan results on network selection.
+     * {@link OpenNetworkNotifier} handles scan results on network selection.
      *
      * Expected behavior: ONA handles scan results
      */
     @Test
-    public void wifiDisconnected_noConnectionCandidate_openNetworkNotificationScanResultsHandled() {
+    public void wifiDisconnected_noConnectionCandidate_openNetworkNotifierScanResultsHandled() {
         // no connection candidate selected
         when(mWifiNS.selectNetwork(anyObject(), anyObject(), anyObject(), anyBoolean(),
                 anyBoolean(), anyBoolean())).thenReturn(null);
@@ -633,37 +636,66 @@
         mWifiConnectivityManager.handleConnectionStateChanged(
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
 
-        verify(mWifiNotificationController).handleScanResults(expectedOpenNetworks);
+        verify(mOpenNetworkNotifier).handleScanResults(expectedOpenNetworks);
     }
 
     /**
-     * When wifi is connected, {@link WifiNotificationController} tries to clear the pending
-     * notification and does not reset notification repeat delay.
+     * When wifi is connected, {@link OpenNetworkNotifier} handles the Wi-Fi connected behavior.
      *
-     * Expected behavior: ONA clears pending notification and does not reset repeat delay.
+     * Expected behavior: ONA handles connected behavior
      */
     @Test
-    public void wifiConnected_openNetworkNotificationClearsPendingNotification() {
+    public void wifiConnected_openNetworkNotifierHandlesConnection() {
         // Set WiFi to connected state
         mWifiConnectivityManager.handleConnectionStateChanged(
                 WifiConnectivityManager.WIFI_STATE_CONNECTED);
 
-        verify(mWifiNotificationController).clearPendingNotification(false /* isRepeatDelayReset*/);
+        verify(mOpenNetworkNotifier).handleWifiConnected();
     }
 
     /**
-     * When wifi is connected, {@link WifiNotificationController} handles connection state
+     * When wifi is connected, {@link OpenNetworkNotifier} handles connection state
      * change.
      *
      * Expected behavior: ONA does not clear pending notification.
      */
     @Test
-    public void wifiDisconnected_openNetworkNotificationDoesNotClearPendingNotification() {
+    public void wifiDisconnected_openNetworkNotifierDoesNotClearPendingNotification() {
         // Set WiFi to disconnected state
         mWifiConnectivityManager.handleConnectionStateChanged(
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
 
-        verify(mWifiNotificationController, never()).clearPendingNotification(anyBoolean());
+        verify(mOpenNetworkNotifier, never()).clearPendingNotification(anyBoolean());
+    }
+
+    /**
+     * When a Wi-Fi connection attempt ends, {@link OpenNetworkNotifier} handles the connection
+     * failure. A failure code that is not {@link WifiMetrics.ConnectionEvent#FAILURE_NONE}
+     * represents a connection failure.
+     *
+     * Expected behavior: ONA handles connection failure.
+     */
+    @Test
+    public void wifiConnectionEndsWithFailure_openNetworkNotifierHandlesConnectionFailure() {
+        mWifiConnectivityManager.handleConnectionAttemptEnded(
+                WifiMetrics.ConnectionEvent.FAILURE_CONNECT_NETWORK_FAILED);
+
+        verify(mOpenNetworkNotifier).handleConnectionFailure();
+    }
+
+    /**
+     * When a Wi-Fi connection attempt ends, {@link OpenNetworkNotifier} does not handle connection
+     * failure after a successful connection. {@link WifiMetrics.ConnectionEvent#FAILURE_NONE}
+     * represents a successful connection.
+     *
+     * Expected behavior: ONA does nothing.
+     */
+    @Test
+    public void wifiConnectionEndsWithSuccess_openNetworkNotifierDoesNotHandleConnectionFailure() {
+        mWifiConnectivityManager.handleConnectionAttemptEnded(
+                WifiMetrics.ConnectionEvent.FAILURE_NONE);
+
+        verify(mOpenNetworkNotifier, never()).handleConnectionFailure();
     }
 
     /**
@@ -672,24 +704,24 @@
      * Expected behavior: clear pending notification and reset notification repeat delay
      * */
     @Test
-    public void openNetworkNotificationControllerToggledOnWifiStateChanges() {
+    public void openNetworkNotifierClearsPendingNotificationOnWifiDisabled() {
         mWifiConnectivityManager.setWifiEnabled(false);
 
-        verify(mWifiNotificationController).clearPendingNotification(true /* isRepeatDelayReset */);
+        verify(mOpenNetworkNotifier).clearPendingNotification(true /* resetRepeatDelay */);
     }
 
     /**
      * Verify that the ONA controller tracks screen state changes.
      */
     @Test
-    public void openNetworkNotificationControllerTracksScreenStateChanges() {
+    public void openNetworkNotifierTracksScreenStateChanges() {
         mWifiConnectivityManager.handleScreenStateChanged(false);
 
-        verify(mWifiNotificationController).handleScreenStateChanged(false);
+        verify(mOpenNetworkNotifier).handleScreenStateChanged(false);
 
         mWifiConnectivityManager.handleScreenStateChanged(true);
 
-        verify(mWifiNotificationController).handleScreenStateChanged(true);
+        verify(mOpenNetworkNotifier).handleScreenStateChanged(true);
     }
 
     /**
@@ -1123,7 +1155,8 @@
 
         // Verify that WCM receives the scan results and initiates a connection
         // to the network.
-        verify(mWifiStateMachine).startConnectToNetwork(CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
+        verify(mWifiStateMachine).startConnectToNetwork(
+                CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
     }
 
     /**
@@ -1145,21 +1178,22 @@
 
         // Force a connectivity scan which enables WifiConnectivityManager
         // to wait for full band scan results.
-        mWifiConnectivityManager.forceConnectivityScan();
+        mWifiConnectivityManager.forceConnectivityScan(WIFI_WORK_SOURCE);
 
         // No roaming because no full band scan results.
         verify(mWifiStateMachine, times(0)).startConnectToNetwork(
-                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
+                CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
 
         // Set up as full band scan results.
         when(mScanData.isAllChannelsScanned()).thenReturn(true);
 
         // Force a connectivity scan which enables WifiConnectivityManager
         // to wait for full band scan results.
-        mWifiConnectivityManager.forceConnectivityScan();
+        mWifiConnectivityManager.forceConnectivityScan(WIFI_WORK_SOURCE);
 
         // Roaming attempt because full band scan results are available.
-        verify(mWifiStateMachine).startConnectToNetwork(CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
+        verify(mWifiStateMachine).startConnectToNetwork(
+                CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
     }
 
     /**
@@ -1252,14 +1286,14 @@
         // its blacklist expiration time hasn't reached yet.
         when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime()
                 + WifiConnectivityManager.BSSID_BLACKLIST_EXPIRE_TIME_MS / 2);
-        mWifiConnectivityManager.forceConnectivityScan();
+        mWifiConnectivityManager.forceConnectivityScan(WIFI_WORK_SOURCE);
         assertTrue(mWifiConnectivityManager.isBssidDisabled(bssid));
 
         // Force another connectivity scan at BSSID_BLACKLIST_EXPIRE_TIME_MS from when the
         // BSSID was blacklisted. Verify that the blacklisted BSSId is freed.
         when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime()
                 + WifiConnectivityManager.BSSID_BLACKLIST_EXPIRE_TIME_MS);
-        mWifiConnectivityManager.forceConnectivityScan();
+        mWifiConnectivityManager.forceConnectivityScan(WIFI_WORK_SOURCE);
 
         // Verify the BSSID is no longer blacklisted.
         assertFalse(mWifiConnectivityManager.isBssidDisabled(bssid));
@@ -1412,7 +1446,7 @@
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
 
         verify(mWifiStateMachine).startConnectToNetwork(
-                CANDIDATE_NETWORK_ID, WifiStateMachine.SUPPLICANT_BSSID_ANY);
+                CANDIDATE_NETWORK_ID, Process.WIFI_UID, WifiStateMachine.SUPPLICANT_BSSID_ANY);
     }
 
     /*
@@ -1448,7 +1482,8 @@
         mWifiConnectivityManager.handleConnectionStateChanged(
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
 
-        verify(mWifiStateMachine).startConnectToNetwork(CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
+        verify(mWifiStateMachine).startConnectToNetwork(
+                CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
     }
 
     /*
@@ -1468,7 +1503,8 @@
         mWifiConnectivityManager.handleConnectionStateChanged(
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
 
-        verify(mWifiStateMachine).startConnectToNetwork(CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
+        verify(mWifiStateMachine).startConnectToNetwork(
+                CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
     }
 
     /*
@@ -1500,7 +1536,8 @@
         mWifiConnectivityManager.handleConnectionStateChanged(
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
 
-        verify(mWifiStateMachine).startConnectToNetwork(CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
+        verify(mWifiStateMachine).startConnectToNetwork(
+                CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
     }
 
     /**
@@ -1590,7 +1627,7 @@
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
 
         verify(mWifiStateMachine, times(0)).startConnectToNetwork(
-                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
+                CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
     }
 
     /*
@@ -1652,7 +1689,7 @@
     /**
      *  Dump ONA controller.
      *
-     * Expected behavior: {@link WifiNotificationController#dump(FileDescriptor, PrintWriter,
+     * Expected behavior: {@link OpenNetworkNotifier#dump(FileDescriptor, PrintWriter,
      * String[])} is invoked.
      */
     @Test
@@ -1661,6 +1698,6 @@
         PrintWriter pw = new PrintWriter(sw);
         mWifiConnectivityManager.dump(new FileDescriptor(), pw, new String[]{});
 
-        verify(mWifiNotificationController).dump(any(), any(), any());
+        verify(mOpenNetworkNotifier).dump(any(), any(), any());
     }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiCountryCodeTest.java b/tests/wifitests/src/com/android/server/wifi/WifiCountryCodeTest.java
index 33aab60..fb4e71e 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiCountryCodeTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiCountryCodeTest.java
@@ -169,7 +169,8 @@
     }
 
     /**
-     * Test if we can reset to the default country code when phone is out of service.
+     * Test if we can reset to the default country code when phone is out of service, when
+     * |config_wifi_revert_country_code_on_cellular_loss| is set to true;
      * Telephony service calls |setCountryCode| with an empty string when phone is out of service.
      * In this case we should fall back to the default country code.
      * @throws Exception
@@ -184,4 +185,28 @@
         assertEquals(mDefaultCountryCode, mWifiCountryCode.getCountryCode());
     }
 
+    /**
+     * Test if we can keep using the last known country code when phone is out of service, when
+     * |config_wifi_revert_country_code_on_cellular_loss| is set to false;
+     * Telephony service calls |setCountryCode| with an empty string when phone is out of service.
+     * In this case we should keep using the last known country code.
+     * @throws Exception
+     */
+    @Test
+    public void doNotResetCountryCodeWhenOutOfService() throws Exception {
+        // Refresh mWifiCountryCode with |config_wifi_revert_country_code_on_cellular_loss|
+        // setting to false.
+        mWifiCountryCode = new WifiCountryCode(
+                mWifiNative,
+                mDefaultCountryCode,
+                false /* config_wifi_revert_country_code_on_cellular_loss */);
+
+        assertEquals(mDefaultCountryCode, mWifiCountryCode.getCountryCode());
+        mWifiCountryCode.setCountryCode(mTelephonyCountryCode);
+        assertEquals(mTelephonyCountryCode, mWifiCountryCode.getCountryCode());
+        // Out of service.
+        mWifiCountryCode.setCountryCode("");
+        assertEquals(mTelephonyCountryCode, mWifiCountryCode.getCountryCode());
+    }
+
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java b/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
index 5a13928..c6a06e8 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
@@ -16,8 +16,11 @@
 package com.android.server.wifi;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import android.net.NetworkAgent;
 import android.net.wifi.ScanResult;
@@ -37,6 +40,8 @@
 import com.android.server.wifi.hotspot2.PasspointMatch;
 import com.android.server.wifi.hotspot2.PasspointProvider;
 import com.android.server.wifi.nano.WifiMetricsProto;
+import com.android.server.wifi.nano.WifiMetricsProto.ConnectToNetworkNotificationAndActionCount;
+import com.android.server.wifi.nano.WifiMetricsProto.PnoScanMetrics;
 import com.android.server.wifi.nano.WifiMetricsProto.StaEvent;
 
 import org.junit.Before;
@@ -252,6 +257,24 @@
     private static final int NUM_PASSPOINT_PROVIDER_UNINSTALL_SUCCESS = 2;
     private static final int NUM_PASSPOINT_PROVIDERS_SUCCESSFULLY_CONNECTED = 1;
     private static final int NUM_PARTIAL_SCAN_RESULTS = 73;
+    private static final int NUM_PNO_SCAN_ATTEMPTS = 20;
+    private static final int NUM_PNO_SCAN_FAILED = 5;
+    private static final int NUM_PNO_SCAN_STARTED_OVER_OFFLOAD = 17;
+    private static final int NUM_PNO_SCAN_FAILED_OVER_OFFLOAD = 8;
+    private static final int NUM_PNO_FOUND_NETWORK_EVENTS = 10;
+    /** Number of notifications per "Connect to Network" notification type. */
+    private static final int[] NUM_CONNECT_TO_NETWORK_NOTIFICATIONS = {0, 10, 20, 30, 40};
+    /** Number of notifications per "Connect to Network notification type and action type. */
+    private static final int[][] NUM_CONNECT_TO_NETWORK_NOTIFICATION_ACTIONS = {
+            {0, 1, 2, 3, 4},
+            {10, 11, 12, 13, 14},
+            {20, 21, 22, 23, 24},
+            {30, 31, 32, 33, 34},
+            {40, 41, 42, 43, 44}};
+    private static final int SIZE_OPEN_NETWORK_RECOMMENDER_BLACKLIST = 10;
+    private static final boolean IS_WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON = true;
+    private static final int NUM_OPEN_NETWORK_CONNECT_MESSAGE_FAILED_TO_SEND = 5;
+    private static final int NUM_OPEN_NETWORK_RECOMMENDATION_UPDATES = 8;
 
     private ScanDetail buildMockScanDetail(boolean hidden, NetworkDetail.HSRelease hSRelease,
             String capabilities) {
@@ -290,6 +313,23 @@
         return mockScanDetail;
     }
 
+    private ScanDetail buildMockScanDetailPasspoint(String ssid, String bssid, long hessid,
+            int anqpDomainId, NetworkDetail.HSRelease hsRelease) {
+        ScanDetail mockScanDetail = mock(ScanDetail.class);
+        NetworkDetail mockNetworkDetail = mock(NetworkDetail.class);
+        ScanResult scanResult = new ScanResult();
+        scanResult.SSID = ssid;
+        scanResult.BSSID = bssid;
+        scanResult.hessid = hessid;
+        scanResult.capabilities = "PSK";
+        when(mockScanDetail.getNetworkDetail()).thenReturn(mockNetworkDetail);
+        when(mockScanDetail.getScanResult()).thenReturn(scanResult);
+        when(mockNetworkDetail.getHSRelease()).thenReturn(hsRelease);
+        when(mockNetworkDetail.getAnqpDomainID()).thenReturn(anqpDomainId);
+        when(mockNetworkDetail.isInterworking()).thenReturn(true);
+        return mockScanDetail;
+    }
+
     private List<ScanDetail> buildMockScanDetailList() {
         List<ScanDetail> mockScanDetails = new ArrayList<ScanDetail>();
         mockScanDetails.add(buildMockScanDetail(true, null, "[ESS]"));
@@ -474,6 +514,50 @@
         for (int i = 0; i < NUM_PASSPOINT_PROVIDER_UNINSTALL_SUCCESS; i++) {
             mWifiMetrics.incrementNumPasspointProviderUninstallSuccess();
         }
+
+        // increment pno scan metrics
+        for (int i = 0; i < NUM_PNO_SCAN_ATTEMPTS; i++) {
+            mWifiMetrics.incrementPnoScanStartAttempCount();
+        }
+        for (int i = 0; i < NUM_PNO_SCAN_FAILED; i++) {
+            mWifiMetrics.incrementPnoScanFailedCount();
+        }
+        for (int i = 0; i < NUM_PNO_SCAN_STARTED_OVER_OFFLOAD; i++) {
+            mWifiMetrics.incrementPnoScanStartedOverOffloadCount();
+        }
+        for (int i = 0; i < NUM_PNO_SCAN_FAILED_OVER_OFFLOAD; i++) {
+            mWifiMetrics.incrementPnoScanFailedOverOffloadCount();
+        }
+        for (int i = 0; i < NUM_PNO_FOUND_NETWORK_EVENTS; i++) {
+            mWifiMetrics.incrementPnoFoundNetworkEventCount();
+        }
+
+        // set and increment "connect to network" notification metrics
+        for (int i = 0; i < NUM_CONNECT_TO_NETWORK_NOTIFICATIONS.length; i++) {
+            int count = NUM_CONNECT_TO_NETWORK_NOTIFICATIONS[i];
+            for (int j = 0; j < count; j++) {
+                mWifiMetrics.incrementConnectToNetworkNotification(i);
+            }
+        }
+        for (int i = 0; i < NUM_CONNECT_TO_NETWORK_NOTIFICATION_ACTIONS.length; i++) {
+            int[] actions = NUM_CONNECT_TO_NETWORK_NOTIFICATION_ACTIONS[i];
+            for (int j = 0; j < actions.length; j++) {
+                int count = actions[j];
+                for (int k = 0; k < count; k++) {
+                    mWifiMetrics.incrementConnectToNetworkNotificationAction(i, j);
+                }
+            }
+        }
+        mWifiMetrics.setOpenNetworkRecommenderBlacklistSize(
+                SIZE_OPEN_NETWORK_RECOMMENDER_BLACKLIST);
+        mWifiMetrics.setIsWifiNetworksAvailableNotificationEnabled(
+                IS_WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON);
+        for (int i = 0; i < NUM_OPEN_NETWORK_RECOMMENDATION_UPDATES; i++) {
+            mWifiMetrics.incrementNumOpenNetworkRecommendationUpdates();
+        }
+        for (int i = 0; i < NUM_OPEN_NETWORK_CONNECT_MESSAGE_FAILED_TO_SEND; i++) {
+            mWifiMetrics.incrementNumOpenNetworkConnectMessageFailedToSend();
+        }
     }
 
     /**
@@ -618,6 +702,40 @@
                 mDecodedProto.numPasspointProviderUninstallSuccess);
         assertEquals(NUM_PASSPOINT_PROVIDERS_SUCCESSFULLY_CONNECTED,
                 mDecodedProto.numPasspointProvidersSuccessfullyConnected);
+
+        PnoScanMetrics pno_metrics = mDecodedProto.pnoScanMetrics;
+        assertNotNull(pno_metrics);
+        assertEquals(NUM_PNO_SCAN_ATTEMPTS, pno_metrics.numPnoScanAttempts);
+        assertEquals(NUM_PNO_SCAN_FAILED, pno_metrics.numPnoScanFailed);
+        assertEquals(NUM_PNO_SCAN_STARTED_OVER_OFFLOAD, pno_metrics.numPnoScanStartedOverOffload);
+        assertEquals(NUM_PNO_SCAN_FAILED_OVER_OFFLOAD, pno_metrics.numPnoScanFailedOverOffload);
+        assertEquals(NUM_PNO_FOUND_NETWORK_EVENTS, pno_metrics.numPnoFoundNetworkEvents);
+
+        for (ConnectToNetworkNotificationAndActionCount notificationCount
+                : mDecodedProto.connectToNetworkNotificationCount) {
+            assertEquals(NUM_CONNECT_TO_NETWORK_NOTIFICATIONS[notificationCount.notification],
+                    notificationCount.count);
+            assertEquals(ConnectToNetworkNotificationAndActionCount.RECOMMENDER_OPEN,
+                    notificationCount.recommender);
+        }
+        for (ConnectToNetworkNotificationAndActionCount notificationActionCount
+                : mDecodedProto.connectToNetworkNotificationActionCount) {
+            assertEquals(NUM_CONNECT_TO_NETWORK_NOTIFICATION_ACTIONS
+                            [notificationActionCount.notification]
+                            [notificationActionCount.action],
+                    notificationActionCount.count);
+            assertEquals(ConnectToNetworkNotificationAndActionCount.RECOMMENDER_OPEN,
+                    notificationActionCount.recommender);
+        }
+
+        assertEquals(SIZE_OPEN_NETWORK_RECOMMENDER_BLACKLIST,
+                mDecodedProto.openNetworkRecommenderBlacklistSize);
+        assertEquals(IS_WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
+                mDecodedProto.isWifiNetworksAvailableNotificationOn);
+        assertEquals(NUM_OPEN_NETWORK_RECOMMENDATION_UPDATES,
+                mDecodedProto.numOpenNetworkRecommendationUpdates);
+        assertEquals(NUM_OPEN_NETWORK_CONNECT_MESSAGE_FAILED_TO_SEND,
+                mDecodedProto.numOpenNetworkConnectMessageFailedToSend);
     }
 
     /**
@@ -664,6 +782,31 @@
         // pending their implementation</TODO>
     }
 
+    /**
+     * Test that score breach events are properly generated
+     */
+    @Test
+    public void testScoreBeachEvents() throws Exception {
+        int upper = WifiMetrics.LOW_WIFI_SCORE + 7;
+        int mid = WifiMetrics.LOW_WIFI_SCORE;
+        int lower = WifiMetrics.LOW_WIFI_SCORE - 8;
+        mWifiMetrics.setWifiState(WifiMetricsProto.WifiLog.WIFI_ASSOCIATED);
+        for (int score = upper; score >= mid; score--) mWifiMetrics.incrementWifiScoreCount(score);
+        mWifiMetrics.incrementWifiScoreCount(mid + 1);
+        mWifiMetrics.incrementWifiScoreCount(lower); // First breach
+        for (int score = lower; score <= mid; score++) mWifiMetrics.incrementWifiScoreCount(score);
+        mWifiMetrics.incrementWifiScoreCount(mid - 1);
+        mWifiMetrics.incrementWifiScoreCount(upper); // Second breach
+
+        dumpProtoAndDeserialize();
+
+        assertEquals(2, mDecodedProto.staEventList.length);
+        assertEquals(StaEvent.TYPE_SCORE_BREACH, mDecodedProto.staEventList[0].type);
+        assertEquals(lower, mDecodedProto.staEventList[0].lastScore);
+        assertEquals(StaEvent.TYPE_SCORE_BREACH, mDecodedProto.staEventList[1].type);
+        assertEquals(upper, mDecodedProto.staEventList[1].lastScore);
+    }
+
     private static final String SSID = "red";
     private static final int CONFIG_DTIM = 3;
     private static final int NETWORK_DETAIL_WIFIMODE = 5;
@@ -942,7 +1085,7 @@
     private static final int ASSOC_TIMEOUT = 1;
     private static final int LOCAL_GEN = 1;
     private static final int AUTH_FAILURE_REASON = WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD;
-    private static final int NUM_TEST_STA_EVENTS = 14;
+    private static final int NUM_TEST_STA_EVENTS = 15;
     private static final String   sSSID = "\"SomeTestSsid\"";
     private static final WifiSsid sWifiSsid = WifiSsid.createFromAsciiEncoded(sSSID);
     private static final String   sBSSID = "01:02:03:04:05:06";
@@ -990,7 +1133,8 @@
         {StaEvent.TYPE_CMD_START_ROAM,                  0,                          1},
         {StaEvent.TYPE_CONNECT_NETWORK,                 0,                          1},
         {StaEvent.TYPE_NETWORK_AGENT_VALID_NETWORK,     0,                          0},
-        {StaEvent.TYPE_FRAMEWORK_DISCONNECT,            StaEvent.DISCONNECT_API,    0}
+        {StaEvent.TYPE_FRAMEWORK_DISCONNECT,            StaEvent.DISCONNECT_API,    0},
+        {StaEvent.TYPE_SCORE_BREACH,                    0,                          0}
     };
     // Values used to generate the StaEvent log calls from WifiMonitor
     // <type>, <reason>, <status>, <local_gen>,
@@ -1023,6 +1167,8 @@
         {StaEvent.TYPE_NETWORK_AGENT_VALID_NETWORK,     -1,            -1,         0,
             /**/                               0,             0,        0, 0},    /**/
         {StaEvent.TYPE_FRAMEWORK_DISCONNECT,            -1,            -1,         0,
+            /**/                               0,             0,        0, 0},    /**/
+        {StaEvent.TYPE_SCORE_BREACH,                    -1,            -1,         0,
             /**/                               0,             0,        0, 0}     /**/
     };
 
@@ -1043,6 +1189,7 @@
         }
     }
     private void verifyDeserializedStaEvents(WifiMetricsProto.WifiLog wifiLog) {
+        assertNotNull(mTestWifiConfig);
         assertEquals(NUM_TEST_STA_EVENTS, wifiLog.staEventList.length);
         int j = 0; // De-serialized event index
         for (int i = 0; i < mTestStaMessageInts.length; i++) {
@@ -1062,6 +1209,21 @@
                 j++;
             }
         }
+        for (int i = 0; i < mTestStaLogInts.length; i++) {
+            StaEvent event = wifiLog.staEventList[j];
+            int[] evs = mExpectedValues[j];
+            assertEquals(evs[0], event.type);
+            assertEquals(evs[1], event.reason);
+            assertEquals(evs[2], event.status);
+            assertEquals(evs[3] == 1 ? true : false, event.localGen);
+            assertEquals(evs[4], event.authFailureReason);
+            assertEquals(evs[5] == 1 ? true : false, event.associationTimedOut);
+            assertEquals(evs[6], event.supplicantStateChangesBitmask);
+            assertConfigInfoEqualsWifiConfig(
+                    evs[7] == 1 ? mTestWifiConfig : null, event.configInfo);
+            j++;
+        }
+        assertEquals(mExpectedValues.length, j);
     }
 
     /**
@@ -1171,6 +1333,107 @@
                 a(WifiMetrics.MAX_CONNECTABLE_BSSID_NETWORK_BUCKET), a(1));
     }
 
+    /**
+     * Test that Hotspot 2.0 (Passpoint) scan results are collected correctly and that relevant
+     * bounds are observed.
+     */
+    @Test
+    public void testObservedHotspotAps() throws Exception {
+        List<ScanDetail> scan = new ArrayList<ScanDetail>();
+        // 2 R1 (Unknown AP isn't counted) passpoint APs belonging to a single provider: hessid1
+        long hessid1 = 10;
+        int anqpDomainId1 = 5;
+        scan.add(buildMockScanDetailPasspoint("PASSPOINT_XX", "00:02:03:04:05:06", hessid1,
+                anqpDomainId1, NetworkDetail.HSRelease.R1));
+        scan.add(buildMockScanDetailPasspoint("PASSPOINT_XY", "01:02:03:04:05:06", hessid1,
+                anqpDomainId1, NetworkDetail.HSRelease.R1));
+        scan.add(buildMockScanDetailPasspoint("PASSPOINT_XYZ", "02:02:03:04:05:06", hessid1,
+                anqpDomainId1, NetworkDetail.HSRelease.Unknown));
+        // 2 R2 passpoint APs belonging to a single provider: hessid2
+        long hessid2 = 12;
+        int anqpDomainId2 = 6;
+        scan.add(buildMockScanDetailPasspoint("PASSPOINT_Y", "AA:02:03:04:05:06", hessid2,
+                anqpDomainId2, NetworkDetail.HSRelease.R2));
+        scan.add(buildMockScanDetailPasspoint("PASSPOINT_Z", "AB:02:03:04:05:06", hessid2,
+                anqpDomainId2, NetworkDetail.HSRelease.R2));
+        mWifiMetrics.incrementAvailableNetworksHistograms(scan, true);
+        scan = new ArrayList<ScanDetail>();
+        // 3 R2 passpoint APs belonging to a single provider: hessid3 (in next scan)
+        long hessid3 = 15;
+        int anqpDomainId3 = 8;
+        scan.add(buildMockScanDetailPasspoint("PASSPOINT_Y", "AA:02:03:04:05:06", hessid3,
+                anqpDomainId3, NetworkDetail.HSRelease.R2));
+        scan.add(buildMockScanDetailPasspoint("PASSPOINT_Y", "AA:02:03:04:05:06", hessid3,
+                anqpDomainId3, NetworkDetail.HSRelease.R2));
+        scan.add(buildMockScanDetailPasspoint("PASSPOINT_Z", "AB:02:03:04:05:06", hessid3,
+                anqpDomainId3, NetworkDetail.HSRelease.R2));
+        mWifiMetrics.incrementAvailableNetworksHistograms(scan, true);
+        dumpProtoAndDeserialize();
+
+        verifyHist(mDecodedProto.observedHotspotR1ApsInScanHistogram, 2, a(0, 2), a(1, 1));
+        verifyHist(mDecodedProto.observedHotspotR2ApsInScanHistogram, 2, a(2, 3), a(1, 1));
+        verifyHist(mDecodedProto.observedHotspotR1EssInScanHistogram, 2, a(0, 1), a(1, 1));
+        verifyHist(mDecodedProto.observedHotspotR2EssInScanHistogram, 1, a(1), a(2));
+        verifyHist(mDecodedProto.observedHotspotR1ApsPerEssInScanHistogram, 1, a(2), a(1));
+        verifyHist(mDecodedProto.observedHotspotR2ApsPerEssInScanHistogram, 2, a(2, 3), a(1, 1));
+
+        // check bounds
+        scan.clear();
+        int lotsOfSSids = Math.max(WifiMetrics.MAX_TOTAL_PASSPOINT_APS_BUCKET,
+                WifiMetrics.MAX_TOTAL_PASSPOINT_UNIQUE_ESS_BUCKET) + 5;
+        for (int i = 0; i < lotsOfSSids; i++) {
+            scan.add(buildMockScanDetailPasspoint("PASSPOINT_XX" + i, "00:02:03:04:05:06", i,
+                    i + 10, NetworkDetail.HSRelease.R1));
+            scan.add(buildMockScanDetailPasspoint("PASSPOINT_XY" + i, "AA:02:03:04:05:06", 1000 * i,
+                    i + 10, NetworkDetail.HSRelease.R2));
+        }
+        mWifiMetrics.incrementAvailableNetworksHistograms(scan, true);
+        dumpProtoAndDeserialize();
+        verifyHist(mDecodedProto.observedHotspotR1ApsInScanHistogram, 1,
+                a(WifiMetrics.MAX_TOTAL_PASSPOINT_APS_BUCKET), a(1));
+        verifyHist(mDecodedProto.observedHotspotR2ApsInScanHistogram, 1,
+                a(WifiMetrics.MAX_TOTAL_PASSPOINT_APS_BUCKET), a(1));
+        verifyHist(mDecodedProto.observedHotspotR1EssInScanHistogram, 1,
+                a(WifiMetrics.MAX_TOTAL_PASSPOINT_UNIQUE_ESS_BUCKET), a(1));
+        verifyHist(mDecodedProto.observedHotspotR2EssInScanHistogram, 1,
+                a(WifiMetrics.MAX_TOTAL_PASSPOINT_UNIQUE_ESS_BUCKET), a(1));
+
+    }
+
+    /**
+     * Test Open Network Notification blacklist size and feature state are not cleared when proto
+     * is dumped.
+     */
+    @Test
+    public void testOpenNetworkNotificationBlacklistSizeAndFeatureStateNotCleared()
+            throws Exception {
+        mWifiMetrics.setOpenNetworkRecommenderBlacklistSize(
+                SIZE_OPEN_NETWORK_RECOMMENDER_BLACKLIST);
+        mWifiMetrics.setIsWifiNetworksAvailableNotificationEnabled(
+                IS_WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON);
+        for (int i = 0; i < NUM_OPEN_NETWORK_RECOMMENDATION_UPDATES; i++) {
+            mWifiMetrics.incrementNumOpenNetworkRecommendationUpdates();
+        }
+
+        // This should clear most metrics in mWifiMetrics
+        dumpProtoAndDeserialize();
+        assertEquals(SIZE_OPEN_NETWORK_RECOMMENDER_BLACKLIST,
+                mDecodedProto.openNetworkRecommenderBlacklistSize);
+        assertEquals(IS_WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
+                mDecodedProto.isWifiNetworksAvailableNotificationOn);
+        assertEquals(NUM_OPEN_NETWORK_RECOMMENDATION_UPDATES,
+                mDecodedProto.numOpenNetworkRecommendationUpdates);
+
+        // Check that blacklist size and feature state persist on next dump but
+        // others do not.
+        dumpProtoAndDeserialize();
+        assertEquals(SIZE_OPEN_NETWORK_RECOMMENDER_BLACKLIST,
+                mDecodedProto.openNetworkRecommenderBlacklistSize);
+        assertEquals(IS_WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
+                mDecodedProto.isWifiNetworksAvailableNotificationOn);
+        assertEquals(0, mDecodedProto.numOpenNetworkRecommendationUpdates);
+    }
+
     /** short hand for instantiating an anonymous int array, instead of 'new int[]{a1, a2, ...}' */
     private int[] a(int... element) {
         return element;
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTest.java b/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTest.java
index 4965a35..3d3af36 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTest.java
@@ -23,12 +23,12 @@
 import static org.mockito.Mockito.*;
 
 import android.content.Context;
-import android.content.res.Resources;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
 import android.net.wifi.WifiInfo;
 import android.os.SystemClock;
+import android.test.mock.MockResources;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.LocalLog;
 import android.util.Pair;
@@ -41,6 +41,7 @@
 import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -52,6 +53,8 @@
 @SmallTest
 public class WifiNetworkSelectorTest {
 
+    private static final int RSSI_BUMP = 1;
+
     /** Sets up test. */
     @Before
     public void setUp() throws Exception {
@@ -68,22 +71,6 @@
         mDummyEvaluator.setEvaluatorToSelectCandidate(true);
         when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime());
 
-        mThresholdMinimumRssi2G = mResource.getInteger(
-                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz);
-        mThresholdMinimumRssi5G = mResource.getInteger(
-                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz);
-        mThresholdQualifiedRssi2G = mResource.getInteger(
-                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_24GHz);
-        mThresholdQualifiedRssi5G = mResource.getInteger(
-                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_5GHz);
-        mStayOnNetworkMinimumTxRate = mResource.getInteger(
-                R.integer.config_wifi_framework_min_tx_rate_for_staying_on_network);
-        mStayOnNetworkMinimumRxRate = mResource.getInteger(
-                R.integer.config_wifi_framework_min_rx_rate_for_staying_on_network);
-        mThresholdSaturatedRssi2G = mResource.getInteger(
-                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz);
-        mThresholdSaturatedRssi5G = mResource.getInteger(
-                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_5GHz);
     }
 
     /** Cleans up test. */
@@ -145,7 +132,11 @@
     private DummyNetworkEvaluator mDummyEvaluator = new DummyNetworkEvaluator();
     @Mock private WifiConfigManager mWifiConfigManager;
     @Mock private Context mContext;
-    @Mock private Resources mResource;
+
+    // For simulating the resources, we use a Spy on a MockResource
+    // (which is really more of a stub than a mock, in spite if its name).
+    // This is so that we get errors on any calls that we have not explicitly set up.
+    @Spy private MockResources mResource = new MockResources();
     @Mock private WifiInfo mWifiInfo;
     @Mock private Clock mClock;
     private LocalLog mLocalLog;
@@ -155,40 +146,32 @@
     private int mThresholdQualifiedRssi5G;
     private int mStayOnNetworkMinimumTxRate;
     private int mStayOnNetworkMinimumRxRate;
-    private int mThresholdSaturatedRssi2G;
-    private int mThresholdSaturatedRssi5G;
 
     private void setupContext() {
         when(mContext.getResources()).thenReturn(mResource);
     }
 
+    private int setupIntegerResource(int resourceName, int value) {
+        doReturn(value).when(mResource).getInteger(resourceName);
+        return value;
+    }
+
     private void setupResources() {
-        when(mResource.getBoolean(
-                R.bool.config_wifi_framework_enable_associated_network_selection)).thenReturn(true);
-        when(mResource.getInteger(
-                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_5GHz))
-                .thenReturn(-70);
-        when(mResource.getInteger(
-                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_24GHz))
-                .thenReturn(-73);
-        when(mResource.getInteger(
-                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz))
-                .thenReturn(-82);
-        when(mResource.getInteger(
-                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz))
-                .thenReturn(-85);
-        when(mResource.getInteger(
-                R.integer.config_wifi_framework_max_tx_rate_for_full_scan))
-                .thenReturn(8);
-        when(mResource.getInteger(
-                R.integer.config_wifi_framework_max_rx_rate_for_full_scan))
-                .thenReturn(8);
-        when(mResource.getInteger(
-                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_5GHz))
-                .thenReturn(-57);
-        when(mResource.getInteger(
-                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz))
-                .thenReturn(-60);
+        doReturn(true).when(mResource).getBoolean(
+                R.bool.config_wifi_framework_enable_associated_network_selection);
+
+        mThresholdMinimumRssi2G = setupIntegerResource(
+                R.integer.config_wifi_framework_wifi_score_entry_rssi_threshold_24GHz, -79);
+        mThresholdMinimumRssi5G = setupIntegerResource(
+                R.integer.config_wifi_framework_wifi_score_entry_rssi_threshold_5GHz, -76);
+        mThresholdQualifiedRssi2G = setupIntegerResource(
+                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_24GHz, -73);
+        mThresholdQualifiedRssi5G = setupIntegerResource(
+                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_5GHz, -70);
+        mStayOnNetworkMinimumTxRate = setupIntegerResource(
+                R.integer.config_wifi_framework_min_tx_rate_for_staying_on_network, 16);
+        mStayOnNetworkMinimumRxRate = setupIntegerResource(
+                R.integer.config_wifi_framework_min_rx_rate_for_staying_on_network, 16);
     }
 
     private void setupWifiInfo() {
@@ -279,7 +262,7 @@
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
         int[] freqs = {2437, 5180};
         String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
-        int[] levels = {mThresholdMinimumRssi2G + 1, mThresholdMinimumRssi5G + 1};
+        int[] levels = {mThresholdMinimumRssi2G + RSSI_BUMP, mThresholdMinimumRssi5G + RSSI_BUMP};
         int[] securities = {SECURITY_PSK, SECURITY_PSK};
 
         // Make a network selection.
@@ -318,7 +301,7 @@
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
         int[] freqs = {2437, 5180};
         String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
-        int[] levels = {mThresholdMinimumRssi2G + 1, mThresholdMinimumRssi5G + 1};
+        int[] levels = {mThresholdMinimumRssi2G + RSSI_BUMP, mThresholdMinimumRssi5G + RSSI_BUMP};
         int[] securities = {SECURITY_PSK, SECURITY_PSK};
 
         // Make a network selection.
@@ -483,7 +466,7 @@
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
         int[] freqs = {2437, 2457};
         String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
-        int[] levels = {mThresholdMinimumRssi2G + 20, mThresholdMinimumRssi2G + 1};
+        int[] levels = {mThresholdMinimumRssi2G + 20, mThresholdMinimumRssi2G + RSSI_BUMP};
         int[] securities = {SECURITY_PSK, SECURITY_PSK};
 
         // Make a network selection to connect to test1.
@@ -534,8 +517,8 @@
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4", "6c:f3:7f:ae:8c:f5"};
         int[] freqs = {2437, 5180, 5181};
         String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
-        int[] levels = {mThresholdMinimumRssi2G + 1, mThresholdMinimumRssi5G + 1,
-                mThresholdMinimumRssi5G + 1};
+        int[] levels = {mThresholdMinimumRssi2G + RSSI_BUMP, mThresholdMinimumRssi5G + RSSI_BUMP,
+                mThresholdMinimumRssi5G + RSSI_BUMP};
         int[] securities = {SECURITY_PSK, SECURITY_PSK, SECURITY_PSK};
 
         ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
@@ -581,7 +564,7 @@
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
         int[] freqs = {2437, 5180};
         String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
-        int[] levels = {mThresholdMinimumRssi2G + 1, mThresholdMinimumRssi5G + 1};
+        int[] levels = {mThresholdMinimumRssi2G + RSSI_BUMP, mThresholdMinimumRssi5G + RSSI_BUMP};
         int[] securities = {SECURITY_PSK, SECURITY_PSK};
 
         ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
@@ -961,7 +944,7 @@
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
         int[] freqs = {2437, 5180};
         String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[ESS]"};
-        int[] levels = {mThresholdMinimumRssi2G + 1, mThresholdMinimumRssi5G + 1};
+        int[] levels = {mThresholdMinimumRssi2G + RSSI_BUMP, mThresholdMinimumRssi5G + RSSI_BUMP};
         mDummyEvaluator.setEvaluatorToSelectCandidate(false);
 
         List<ScanDetail> scanDetails = WifiNetworkSelectorTestUtil.buildScanDetails(
@@ -990,7 +973,7 @@
         String[] bssids = {"6c:f3:7f:ae:8c:f3"};
         int[] freqs = {2437, 5180};
         String[] caps = {"[ESS]"};
-        int[] levels = {mThresholdMinimumRssi2G + 1};
+        int[] levels = {mThresholdMinimumRssi2G + RSSI_BUMP};
         int[] securities = {SECURITY_NONE};
         mDummyEvaluator.setEvaluatorToSelectCandidate(false);
 
@@ -1027,7 +1010,7 @@
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
         int[] freqs = {2437, 5180};
         String[] caps = {"[ESS]", "[ESS]"};
-        int[] levels = {mThresholdMinimumRssi2G + 1, mThresholdMinimumRssi5G + 1};
+        int[] levels = {mThresholdMinimumRssi2G + RSSI_BUMP, mThresholdMinimumRssi5G + RSSI_BUMP};
         mDummyEvaluator.setEvaluatorToSelectCandidate(false);
 
         List<ScanDetail> scanDetails = WifiNetworkSelectorTestUtil.buildScanDetails(
@@ -1055,7 +1038,7 @@
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
         int[] freqs = {2437, 5180};
         String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
-        int[] levels = {mThresholdMinimumRssi2G + 1, mThresholdMinimumRssi5G + 1};
+        int[] levels = {mThresholdMinimumRssi2G + RSSI_BUMP, mThresholdMinimumRssi5G + RSSI_BUMP};
         mDummyEvaluator.setEvaluatorToSelectCandidate(false);
 
         List<ScanDetail> scanDetails = WifiNetworkSelectorTestUtil.buildScanDetails(
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiNotificationControllerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiNotificationControllerTest.java
deleted file mode 100644
index 27055a8..0000000
--- a/tests/wifitests/src/com/android/server/wifi/WifiNotificationControllerTest.java
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * Copyright (C) 2016 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 static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.content.Context;
-import android.content.res.Resources;
-import android.net.wifi.ScanResult;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.os.test.TestLooper;
-import android.provider.Settings;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Unit tests for {@link WifiNotificationController}.
- */
-public class WifiNotificationControllerTest {
-
-    @Mock private Context mContext;
-    @Mock private Resources mResources;
-    @Mock private FrameworkFacade mFrameworkFacade;
-    @Mock private NotificationManager mNotificationManager;
-    @Mock private UserManager mUserManager;
-    private WifiNotificationController mNotificationController;
-
-
-    /** Initialize objects before each test run. */
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        when(mContext.getSystemService(Context.NOTIFICATION_SERVICE))
-                .thenReturn(mNotificationManager);
-        when(mFrameworkFacade.getIntegerSetting(mContext,
-                Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1)).thenReturn(1);
-        when(mContext.getSystemService(Context.USER_SERVICE))
-                .thenReturn(mUserManager);
-        when(mContext.getResources()).thenReturn(mResources);
-
-        TestLooper mock_looper = new TestLooper();
-        mNotificationController = new WifiNotificationController(
-                mContext, mock_looper.getLooper(), mFrameworkFacade,
-                mock(Notification.Builder.class));
-        mNotificationController.handleScreenStateChanged(true);
-    }
-
-    private List<ScanDetail> createOpenScanResults() {
-        List<ScanDetail> scanResults = new ArrayList<>();
-        ScanResult scanResult = new ScanResult();
-        scanResult.capabilities = "[ESS]";
-        scanResults.add(new ScanDetail(scanResult, null /* networkDetail */));
-        return scanResults;
-    }
-
-    /**
-     * When scan results with open networks are handled, a notification is posted.
-     */
-    @Test
-    public void handleScanResults_hasOpenNetworks_notificationDisplayed() {
-        mNotificationController.handleScanResults(createOpenScanResults());
-
-        verify(mNotificationManager).notifyAsUser(any(), anyInt(), any(), any());
-    }
-
-    /**
-     * When scan results with no open networks are handled, a notification is not posted.
-     */
-    @Test
-    public void handleScanResults_emptyList_notificationNotDisplayed() {
-        mNotificationController.handleScanResults(new ArrayList<>());
-
-        verify(mNotificationManager, never()).notifyAsUser(any(), anyInt(), any(), any());
-    }
-
-    /**
-     * When a notification is showing and scan results with no open networks are handled, the
-     * notification is cleared.
-     */
-    @Test
-    public void handleScanResults_notificationShown_emptyList_notificationCleared() {
-        mNotificationController.handleScanResults(createOpenScanResults());
-
-        verify(mNotificationManager).notifyAsUser(any(), anyInt(), any(), any());
-
-        mNotificationController.handleScanResults(new ArrayList<>());
-
-        verify(mNotificationManager).cancelAsUser(any(), anyInt(), any());
-    }
-    /**
-     * When a notification is showing, screen is off, and scan results with no open networks are
-     * handled, the notification is cleared.
-     */
-    @Test
-    public void handleScanResults_notificationShown_screenOff_emptyList_notificationCleared() {
-        mNotificationController.handleScanResults(createOpenScanResults());
-
-        verify(mNotificationManager).notifyAsUser(any(), anyInt(), any(), any());
-
-        mNotificationController.handleScreenStateChanged(false);
-        mNotificationController.handleScanResults(new ArrayList<>());
-
-        verify(mNotificationManager).cancelAsUser(any(), anyInt(), any());
-    }
-
-    /**
-     * If notification is showing, do not post another notification.
-     */
-    @Test
-    public void handleScanResults_notificationShowing_doesNotRepostNotification() {
-        mNotificationController.handleScanResults(createOpenScanResults());
-        mNotificationController.handleScanResults(createOpenScanResults());
-
-        verify(mNotificationManager).notifyAsUser(any(), anyInt(), any(), any());
-    }
-
-    /**
-     * When {@link WifiNotificationController#clearPendingNotification(boolean)} is called and a
-     * notification is shown, clear the notification.
-     */
-    @Test
-    public void clearPendingNotification_clearsNotificationIfOneIsShowing() {
-        mNotificationController.handleScanResults(createOpenScanResults());
-
-        verify(mNotificationManager).notifyAsUser(any(), anyInt(), any(), any());
-
-        mNotificationController.clearPendingNotification(true);
-
-        verify(mNotificationManager).cancelAsUser(any(), anyInt(), any());
-    }
-
-    /**
-     * When {@link WifiNotificationController#clearPendingNotification(boolean)} is called and a
-     * notification was not previously shown, do not clear the notification.
-     */
-    @Test
-    public void clearPendingNotification_doesNotClearNotificationIfNoneShowing() {
-        mNotificationController.clearPendingNotification(true);
-
-        verify(mNotificationManager, never()).cancelAsUser(any(), anyInt(), any());
-    }
-
-    /**
-     * When screen is off and notification is not displayed, notification is not posted on handling
-     * new scan results with open networks.
-     */
-    @Test
-    public void screenOff_handleScanResults_notificationNotDisplayed() {
-        mNotificationController.handleScreenStateChanged(false);
-        mNotificationController.handleScanResults(createOpenScanResults());
-
-        verify(mNotificationManager, never()).notifyAsUser(any(), anyInt(), any(), any());
-    }
-
-    /** Verifies that {@link UserManager#DISALLOW_CONFIG_WIFI} disables the feature. */
-    @Test
-    public void userHasDisallowConfigWifiRestriction_notificationNotDisplayed() {
-        when(mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, UserHandle.CURRENT))
-                .thenReturn(true);
-
-        mNotificationController.handleScanResults(createOpenScanResults());
-
-        verify(mNotificationManager, never()).notifyAsUser(any(), anyInt(), any(), any());
-    }
-
-    /** Verifies that {@link UserManager#DISALLOW_CONFIG_WIFI} clears the showing notification. */
-    @Test
-    public void userHasDisallowConfigWifiRestriction_showingNotificationIsCleared() {
-        mNotificationController.handleScanResults(createOpenScanResults());
-
-        verify(mNotificationManager).notifyAsUser(any(), anyInt(), any(), any());
-
-        when(mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, UserHandle.CURRENT))
-                .thenReturn(true);
-
-        mNotificationController.handleScanResults(createOpenScanResults());
-
-        verify(mNotificationManager).cancelAsUser(any(), anyInt(), any());
-    }
-}
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiScoreReportTest.java b/tests/wifitests/src/com/android/server/wifi/WifiScoreReportTest.java
index 24d3afa..6f01c8e 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiScoreReportTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiScoreReportTest.java
@@ -48,6 +48,18 @@
 
     private static final int CELLULAR_THRESHOLD_SCORE = 50;
 
+    class FakeClock extends Clock {
+        long mWallClockMillis = 1500000000000L;
+        int mStepMillis = 1001;
+
+        @Override
+        public long getWallClockMillis() {
+            mWallClockMillis += mStepMillis;
+            return mWallClockMillis;
+        }
+    }
+
+    FakeClock mClock;
     WifiConfiguration mWifiConfiguration;
     WifiScoreReport mWifiScoreReport;
     ScanDetailCache mScanDetailCache;
@@ -122,7 +134,8 @@
         when(mWifiConfigManager.getScanDetailCacheForNetwork(anyInt()))
                 .thenReturn(mScanDetailCache);
         when(mContext.getResources()).thenReturn(mResources);
-        mWifiScoreReport = new WifiScoreReport(mContext, mWifiConfigManager, new Clock());
+        mClock = new FakeClock();
+        mWifiScoreReport = new WifiScoreReport(mContext, mWifiConfigManager, mClock);
     }
 
     /**
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java b/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java
index 055050d..e43bdc2 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java
@@ -400,7 +400,9 @@
     public void testSetWifiEnabledFromNetworkSettingsHolderWhenInAirplaneMode() throws Exception {
         when(mSettingsStore.handleWifiToggled(eq(true))).thenReturn(true);
         when(mSettingsStore.isAirplaneModeOn()).thenReturn(true);
-        when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(true);
+        when(mContext.checkPermission(
+                eq(android.Manifest.permission.NETWORK_SETTINGS), anyInt(), anyInt()))
+                        .thenReturn(PackageManager.PERMISSION_GRANTED);
         assertTrue(mWifiServiceImpl.setWifiEnabled(SYSUI_PACKAGE_NAME, true));
         verify(mWifiController).sendMessage(eq(CMD_WIFI_TOGGLED));
     }
@@ -413,7 +415,9 @@
     public void testSetWifiEnabledFromAppFailsWhenInAirplaneMode() throws Exception {
         when(mSettingsStore.handleWifiToggled(eq(true))).thenReturn(true);
         when(mSettingsStore.isAirplaneModeOn()).thenReturn(true);
-        when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(false);
+        when(mContext.checkPermission(
+                eq(android.Manifest.permission.NETWORK_SETTINGS), anyInt(), anyInt()))
+                        .thenReturn(PackageManager.PERMISSION_DENIED);
         assertFalse(mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, true));
         verify(mWifiController, never()).sendMessage(eq(CMD_WIFI_TOGGLED));
     }
@@ -426,7 +430,9 @@
     public void testSetWifiEnabledFromNetworkSettingsHolderWhenApEnabled() throws Exception {
         when(mWifiStateMachine.syncGetWifiApState()).thenReturn(WifiManager.WIFI_AP_STATE_ENABLED);
         when(mSettingsStore.handleWifiToggled(eq(true))).thenReturn(true);
-        when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(true);
+        when(mContext.checkPermission(
+                eq(android.Manifest.permission.NETWORK_SETTINGS), anyInt(), anyInt()))
+                        .thenReturn(PackageManager.PERMISSION_GRANTED);
         when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
         assertTrue(mWifiServiceImpl.setWifiEnabled(SYSUI_PACKAGE_NAME, true));
         verify(mWifiController).sendMessage(eq(CMD_WIFI_TOGGLED));
@@ -438,7 +444,9 @@
     @Test
     public void testSetWifiEnabledFromAppFailsWhenApEnabled() throws Exception {
         when(mWifiStateMachine.syncGetWifiApState()).thenReturn(WifiManager.WIFI_AP_STATE_ENABLED);
-        when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(false);
+        when(mContext.checkPermission(
+                eq(android.Manifest.permission.NETWORK_SETTINGS), anyInt(), anyInt()))
+                        .thenReturn(PackageManager.PERMISSION_DENIED);
         when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
         assertFalse(mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, true));
         verify(mSettingsStore, never()).handleWifiToggled(anyBoolean());
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java b/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java
index 929a5fe..69e6070 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java
@@ -30,6 +30,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -42,9 +43,13 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
+import android.hardware.wifi.supplicant.V1_0.ISupplicantStaIfaceCallback;
 import android.net.ConnectivityManager;
 import android.net.DhcpResults;
 import android.net.LinkProperties;
+import android.net.NetworkCapabilities;
+import android.net.NetworkFactory;
+import android.net.NetworkRequest;
 import android.net.dhcp.DhcpClient;
 import android.net.ip.IpManager;
 import android.net.wifi.IApInterface;
@@ -74,6 +79,7 @@
 import android.os.Message;
 import android.os.Messenger;
 import android.os.PowerManager;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -97,6 +103,7 @@
 import com.android.server.wifi.hotspot2.NetworkDetail;
 import com.android.server.wifi.hotspot2.PasspointManager;
 import com.android.server.wifi.p2p.WifiP2pServiceImpl;
+import com.android.server.wifi.util.WifiPermissionsUtil;
 
 import org.junit.After;
 import org.junit.Before;
@@ -119,6 +126,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
+import java.util.function.Consumer;
 
 /**
  * Unit tests for {@link com.android.server.wifi.WifiStateMachine}.
@@ -133,11 +141,15 @@
             (ActivityManager.isLowRamDeviceStatic()
                     ? WifiStateMachine.NUM_LOG_RECS_VERBOSE_LOW_MEMORY
                     : WifiStateMachine.NUM_LOG_RECS_VERBOSE);
-    private static final int FRAMEWORK_NETWORK_ID = 7;
+    private static final int FRAMEWORK_NETWORK_ID = 0;
     private static final int TEST_RSSI = -54;
+    private static final int TEST_NETWORK_ID = 54;
+    private static final int TEST_VALID_NETWORK_SCORE = 54;
+    private static final int TEST_OUTSCORED_NETWORK_SCORE = 100;
     private static final int WPS_SUPPLICANT_NETWORK_ID = 5;
     private static final int WPS_FRAMEWORK_NETWORK_ID = 10;
     private static final String DEFAULT_TEST_SSID = "\"GoogleGuest\"";
+    private static final String OP_PACKAGE_NAME = "com.xxx";
 
     private long mBinderToken;
 
@@ -224,7 +236,9 @@
                 mAlarmManager.getAlarmManager());
 
         when(context.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(
-                mock(ConnectivityManager.class));
+                mConnectivityManager);
+
+        when(context.getOpPackageName()).thenReturn(OP_PACKAGE_NAME);
 
         return context;
     }
@@ -310,6 +324,7 @@
     HandlerThread mP2pThread;
     HandlerThread mSyncThread;
     AsyncChannel  mWsmAsyncChannel;
+    AsyncChannel  mNetworkFactoryChannel;
     TestAlarmManager mAlarmManager;
     MockWifiMonitor mWifiMonitor;
     TestLooper mLooper;
@@ -318,6 +333,7 @@
     FrameworkFacade mFrameworkFacade;
     IpManager.Callback mIpManagerCallback;
     PhoneStateListener mPhoneStateListener;
+    NetworkRequest mDefaultNetworkRequest;
 
     final ArgumentCaptor<SoftApManager.Listener> mSoftApManagerListenerCaptor =
                     ArgumentCaptor.forClass(SoftApManager.Listener.class);
@@ -338,6 +354,7 @@
     @Mock IClientInterface mClientInterface;
     @Mock IBinder mApInterfaceBinder;
     @Mock IBinder mClientInterfaceBinder;
+    @Mock IBinder mPackageManagerBinder;
     @Mock WifiConfigManager mWifiConfigManager;
     @Mock WifiNative mWifiNative;
     @Mock WifiConnectivityManager mWifiConnectivityManager;
@@ -345,11 +362,14 @@
     @Mock WifiStateTracker mWifiStateTracker;
     @Mock PasspointManager mPasspointManager;
     @Mock SelfRecovery mSelfRecovery;
+    @Mock WifiPermissionsUtil mWifiPermissionsUtil;
     @Mock IpManager mIpManager;
     @Mock TelephonyManager mTelephonyManager;
     @Mock WrongPasswordNotifier mWrongPasswordNotifier;
     @Mock Clock mClock;
     @Mock ScanDetailCache mScanDetailCache;
+    @Mock BaseWifiDiagnostics mWifiDiagnostics;
+    @Mock ConnectivityManager mConnectivityManager;
 
     public WifiStateMachineTest() throws Exception {
     }
@@ -374,8 +394,7 @@
         when(mWifiInjector.getBuildProperties()).thenReturn(mBuildProperties);
         when(mWifiInjector.getKeyStore()).thenReturn(mock(KeyStore.class));
         when(mWifiInjector.getWifiBackupRestore()).thenReturn(mock(WifiBackupRestore.class));
-        when(mWifiInjector.makeWifiDiagnostics(anyObject())).thenReturn(
-                mock(BaseWifiDiagnostics.class));
+        when(mWifiInjector.makeWifiDiagnostics(anyObject())).thenReturn(mWifiDiagnostics);
         when(mWifiInjector.makeWificond()).thenReturn(mWificond);
         when(mWifiInjector.getWifiConfigManager()).thenReturn(mWifiConfigManager);
         when(mWifiInjector.getWifiScanner()).thenReturn(mWifiScanner);
@@ -390,6 +409,7 @@
         when(mWifiInjector.getWifiMonitor()).thenReturn(mWifiMonitor);
         when(mWifiInjector.getWifiNative()).thenReturn(mWifiNative);
         when(mWifiInjector.getSelfRecovery()).thenReturn(mSelfRecovery);
+        when(mWifiInjector.getWifiPermissionsUtil()).thenReturn(mWifiPermissionsUtil);
         when(mWifiInjector.makeTelephonyManager()).thenReturn(mTelephonyManager);
         when(mWifiInjector.getClock()).thenReturn(mClock);
 
@@ -438,15 +458,11 @@
             }
         }).when(mTelephonyManager).listen(any(PhoneStateListener.class), anyInt());
 
+        when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(true);
         initializeWsm();
     }
 
-    private void initializeWsm() throws Exception {
-        mWsm = new WifiStateMachine(mContext, mFrameworkFacade, mLooper.getLooper(),
-                mUserManager, mWifiInjector, mBackupManagerProxy, mCountryCode, mWifiNative,
-                mWrongPasswordNotifier);
-        mWsmThread = getWsmHandlerThread(mWsm);
-
+    private void registerAsyncChannel(Consumer<AsyncChannel> consumer, Messenger messenger) {
         final AsyncChannel channel = new AsyncChannel();
         Handler handler = new Handler(mLooper.getLooper()) {
             @Override
@@ -454,7 +470,7 @@
                 switch (msg.what) {
                     case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
                         if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
-                            mWsmAsyncChannel = channel;
+                            consumer.accept(channel);
                         } else {
                             Log.d(TAG, "Failed to connect Command channel " + this);
                         }
@@ -466,15 +482,50 @@
             }
         };
 
-        channel.connect(mContext, handler, mWsm.getMessenger());
+        channel.connect(mContext, handler, messenger);
         mLooper.dispatchAll();
-        /* Now channel is supposed to be connected */
+    }
+
+    private void initializeWsm() throws Exception {
+        mWsm = new WifiStateMachine(mContext, mFrameworkFacade, mLooper.getLooper(),
+                mUserManager, mWifiInjector, mBackupManagerProxy, mCountryCode, mWifiNative,
+                mWrongPasswordNotifier);
+        mWsmThread = getWsmHandlerThread(mWsm);
+
+        registerAsyncChannel((x) -> {
+            mWsmAsyncChannel = x;
+        }, mWsm.getMessenger());
 
         mBinderToken = Binder.clearCallingIdentity();
 
         /* Send the BOOT_COMPLETED message to setup some WSM state. */
         mWsm.sendMessage(WifiStateMachine.CMD_BOOT_COMPLETED);
         mLooper.dispatchAll();
+
+        /* Simulate the initial NetworkRequest sent in by ConnectivityService. */
+        ArgumentCaptor<Messenger> captor = ArgumentCaptor.forClass(Messenger.class);
+        verify(mConnectivityManager, atLeast(2)).registerNetworkFactory(
+                captor.capture(), anyString());
+        Messenger networkFactoryMessenger = captor.getAllValues().get(0);
+        registerAsyncChannel((x) -> {
+            mNetworkFactoryChannel = x;
+        }, networkFactoryMessenger);
+
+        mDefaultNetworkRequest = new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .build();
+        sendDefaultNetworkRequest(TEST_VALID_NETWORK_SCORE);
+    }
+
+    /**
+     * Helper function to resend the cached network request (id == 0) with the specified score.
+     * Note: If you need to add a separate network request, don't use the builder to create one
+     * since the new request object will again default to id == 0.
+     */
+    private void sendDefaultNetworkRequest(int score) {
+        mNetworkFactoryChannel.sendMessage(
+                NetworkFactory.CMD_REQUEST_NETWORK, score, 0, mDefaultNetworkRequest);
+        mLooper.dispatchAll();
     }
 
     @After
@@ -490,6 +541,7 @@
         mSyncThread = null;
         mWsmAsyncChannel = null;
         mWsm = null;
+        mNetworkFactoryChannel = null;
     }
 
     @Test
@@ -740,25 +792,7 @@
                 (Intent) argThat(new WifiEnablingStateIntentMatcher()), any());
     }
 
-    /**
-     * Verifies that configs can be removed when in client mode.
-     */
-    @Test
-    public void canRemoveNetworkConfigInClientMode() throws Exception {
-        boolean result;
-        when(mWifiConfigManager.removeNetwork(eq(0), anyInt())).thenReturn(true);
-        initializeAndAddNetworkAndVerifySuccess();
-        mLooper.startAutoDispatch();
-        result = mWsm.syncRemoveNetwork(mWsmAsyncChannel, 0);
-        mLooper.stopAutoDispatch();
-        assertTrue(result);
-    }
-
-    /**
-     * Verifies that configs can be removed when not in client mode.
-     */
-    @Test
-    public void canRemoveNetworkConfigWhenWifiDisabled() {
+    private void canRemoveNetwork() {
         boolean result;
         when(mWifiConfigManager.removeNetwork(eq(0), anyInt())).thenReturn(true);
         mLooper.startAutoDispatch();
@@ -770,12 +804,25 @@
     }
 
     /**
-     * Verifies that configs can be forgotten when in client mode.
+     * Verifies that configs can be removed when not in client mode.
      */
     @Test
-    public void canForgetNetworkConfigInClientMode() throws Exception {
-        when(mWifiConfigManager.removeNetwork(eq(0), anyInt())).thenReturn(true);
+    public void canRemoveNetworkConfigWhenWifiDisabled() {
+        canRemoveNetwork();
+    }
+
+
+    /**
+     * Verifies that configs can be removed when in client mode.
+     */
+    @Test
+    public void canRemoveNetworkConfigInClientMode() throws Exception {
         initializeAndAddNetworkAndVerifySuccess();
+        canRemoveNetwork();
+    }
+
+    private void canForgetNetwork() {
+        when(mWifiConfigManager.removeNetwork(eq(0), anyInt())).thenReturn(true);
         mWsm.sendMessage(WifiManager.FORGET_NETWORK, 0, MANAGED_PROFILE_UID);
         mLooper.dispatchAll();
         verify(mWifiConfigManager).removeNetwork(anyInt(), anyInt());
@@ -786,10 +833,109 @@
      */
     @Test
     public void canForgetNetworkConfigWhenWifiDisabled() throws Exception {
-        when(mWifiConfigManager.removeNetwork(eq(0), anyInt())).thenReturn(true);
-        mWsm.sendMessage(WifiManager.FORGET_NETWORK, 0, MANAGED_PROFILE_UID);
-        mLooper.dispatchAll();
-        verify(mWifiConfigManager).removeNetwork(anyInt(), anyInt());
+        canForgetNetwork();
+    }
+
+    /**
+     * Verifies that configs can be forgotten when in client mode.
+     */
+    @Test
+    public void canForgetNetworkConfigInClientMode() throws Exception {
+        initializeAndAddNetworkAndVerifySuccess();
+        canForgetNetwork();
+    }
+
+    private void canSaveNetworkConfig() {
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+
+        int networkId = TEST_NETWORK_ID;
+        when(mWifiConfigManager.addOrUpdateNetwork(any(WifiConfiguration.class), anyInt()))
+                .thenReturn(new NetworkUpdateResult(networkId));
+        when(mWifiConfigManager.enableNetwork(eq(networkId), eq(false), anyInt()))
+                .thenReturn(true);
+
+        mLooper.startAutoDispatch();
+        Message reply = mWsmAsyncChannel.sendMessageSynchronously(WifiManager.SAVE_NETWORK, config);
+        mLooper.stopAutoDispatch();
+        assertEquals(WifiManager.SAVE_NETWORK_SUCCEEDED, reply.what);
+
+        verify(mWifiConfigManager).addOrUpdateNetwork(any(WifiConfiguration.class), anyInt());
+        verify(mWifiConfigManager).enableNetwork(eq(networkId), eq(false), anyInt());
+    }
+
+    /**
+     * Verifies that configs can be saved when not in client mode.
+     */
+    @Test
+    public void canSaveNetworkConfigWhenWifiDisabled() throws Exception {
+        canSaveNetworkConfig();
+    }
+
+    /**
+     * Verifies that configs can be saved when in client mode.
+     */
+    @Test
+    public void canSaveNetworkConfigInClientMode() throws Exception {
+        loadComponentsInStaMode();
+        canSaveNetworkConfig();
+    }
+
+    /**
+     * Verifies that null configs are rejected in SAVE_NETWORK message.
+     */
+    @Test
+    public void saveNetworkConfigFailsWithNullConfig() throws Exception {
+        mLooper.startAutoDispatch();
+        Message reply = mWsmAsyncChannel.sendMessageSynchronously(WifiManager.SAVE_NETWORK, null);
+        mLooper.stopAutoDispatch();
+        assertEquals(WifiManager.SAVE_NETWORK_FAILED, reply.what);
+
+        verify(mWifiConfigManager, never())
+                .addOrUpdateNetwork(any(WifiConfiguration.class), anyInt());
+        verify(mWifiConfigManager, never())
+                .enableNetwork(anyInt(), anyBoolean(), anyInt());
+    }
+
+    /**
+     * Verifies that configs save fails when the addition of network fails.
+     */
+    @Test
+    public void saveNetworkConfigFailsWithConfigAddFailure() throws Exception {
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+
+        when(mWifiConfigManager.addOrUpdateNetwork(any(WifiConfiguration.class), anyInt()))
+                .thenReturn(new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID));
+
+        mLooper.startAutoDispatch();
+        Message reply = mWsmAsyncChannel.sendMessageSynchronously(WifiManager.SAVE_NETWORK, config);
+        mLooper.stopAutoDispatch();
+        assertEquals(WifiManager.SAVE_NETWORK_FAILED, reply.what);
+
+        verify(mWifiConfigManager).addOrUpdateNetwork(any(WifiConfiguration.class), anyInt());
+        verify(mWifiConfigManager, never())
+                .enableNetwork(anyInt(), anyBoolean(), anyInt());
+    }
+
+    /**
+     * Verifies that configs save fails when the enable of network fails.
+     */
+    @Test
+    public void saveNetworkConfigFailsWithConfigEnableFailure() throws Exception {
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+
+        int networkId = 5;
+        when(mWifiConfigManager.addOrUpdateNetwork(any(WifiConfiguration.class), anyInt()))
+                .thenReturn(new NetworkUpdateResult(networkId));
+        when(mWifiConfigManager.enableNetwork(eq(networkId), eq(false), anyInt()))
+                .thenReturn(false);
+
+        mLooper.startAutoDispatch();
+        Message reply = mWsmAsyncChannel.sendMessageSynchronously(WifiManager.SAVE_NETWORK, config);
+        mLooper.stopAutoDispatch();
+        assertEquals(WifiManager.SAVE_NETWORK_FAILED, reply.what);
+
+        verify(mWifiConfigManager).addOrUpdateNetwork(any(WifiConfiguration.class), anyInt());
+        verify(mWifiConfigManager).enableNetwork(eq(networkId), eq(false), anyInt());
     }
 
     /**
@@ -829,6 +975,7 @@
                 .thenReturn(new NetworkUpdateResult(0));
         when(mWifiConfigManager.getSavedNetworks()).thenReturn(Arrays.asList(config));
         when(mWifiConfigManager.getConfiguredNetwork(0)).thenReturn(config);
+        when(mWifiConfigManager.getConfiguredNetworkWithPassword(0)).thenReturn(config);
 
         mLooper.startAutoDispatch();
         mWsm.syncAddOrUpdateNetwork(mWsmAsyncChannel, config);
@@ -937,28 +1084,87 @@
                 hiddenNetworkSet);
     }
 
-    @Test
-    public void connect() throws Exception {
-        initializeAndAddNetworkAndVerifySuccess();
-        when(mWifiConfigManager.enableNetwork(eq(0), eq(true), anyInt())).thenReturn(true);
-        when(mWifiConfigManager.checkAndUpdateLastConnectUid(eq(0), anyInt())).thenReturn(true);
+    private void setupAndStartConnectSequence(WifiConfiguration config) throws Exception {
+        when(mWifiConfigManager.enableNetwork(eq(config.networkId), eq(true), anyInt()))
+                .thenReturn(true);
+        when(mWifiConfigManager.checkAndUpdateLastConnectUid(eq(config.networkId), anyInt()))
+                .thenReturn(true);
+        when(mWifiConfigManager.getConfiguredNetwork(eq(config.networkId)))
+                .thenReturn(config);
+        when(mWifiConfigManager.getConfiguredNetworkWithPassword(eq(config.networkId)))
+                .thenReturn(config);
 
         mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
         mLooper.dispatchAll();
         verify(mWifiNative).removeAllNetworks();
 
         mLooper.startAutoDispatch();
-        assertTrue(mWsm.syncEnableNetwork(mWsmAsyncChannel, 0, true));
+        assertTrue(mWsm.syncEnableNetwork(mWsmAsyncChannel, config.networkId, true));
         mLooper.stopAutoDispatch();
+    }
 
-        verify(mWifiConfigManager).enableNetwork(eq(0), eq(true), anyInt());
-        verify(mWifiConnectivityManager).setUserConnectChoice(eq(0));
+    private void validateSuccessfulConnectSequence(WifiConfiguration config) {
+        verify(mWifiConfigManager).enableNetwork(eq(config.networkId), eq(true), anyInt());
+        verify(mWifiConnectivityManager).setUserConnectChoice(eq(config.networkId));
+        verify(mWifiConnectivityManager).prepareForForcedConnection(eq(config.networkId));
+        verify(mWifiConfigManager).getConfiguredNetworkWithPassword(eq(config.networkId));
+        verify(mWifiNative).connectToNetwork(eq(config));
+    }
+
+    private void validateFailureConnectSequence(WifiConfiguration config) {
+        verify(mWifiConfigManager).enableNetwork(eq(config.networkId), eq(true), anyInt());
+        verify(mWifiConnectivityManager).setUserConnectChoice(eq(config.networkId));
+        verify(mWifiConnectivityManager).prepareForForcedConnection(eq(config.networkId));
+        verify(mWifiConfigManager, never()).getConfiguredNetworkWithPassword(eq(config.networkId));
+        verify(mWifiNative, never()).connectToNetwork(eq(config));
+    }
+
+    /**
+     * Tests the network connection initiation sequence with the default network request pending
+     * from WifiNetworkFactory.
+     * This simulates the connect sequence using the public
+     * {@link WifiManager#enableNetwork(int, boolean)} and ensures that we invoke
+     * {@link WifiNative#connectToNetwork(WifiConfiguration)}.
+     */
+    @Test
+    public void triggerConnect() throws Exception {
+        loadComponentsInStaMode();
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+        config.networkId = FRAMEWORK_NETWORK_ID;
+        setupAndStartConnectSequence(config);
+        validateSuccessfulConnectSequence(config);
+    }
+
+    /**
+     * Tests the network connection initiation sequence with no network request pending from
+     * from WifiNetworkFactory.
+     * This simulates the connect sequence using the public
+     * {@link WifiManager#enableNetwork(int, boolean)} and ensures that we don't invoke
+     * {@link WifiNative#connectToNetwork(WifiConfiguration)}.
+     */
+    @Test
+    public void triggerConnectWithNoNetworkRequest() throws Exception {
+        loadComponentsInStaMode();
+        // Change the network score to remove the network request.
+        sendDefaultNetworkRequest(TEST_OUTSCORED_NETWORK_SCORE);
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+        config.networkId = FRAMEWORK_NETWORK_ID;
+        setupAndStartConnectSequence(config);
+        validateFailureConnectSequence(config);
+    }
+
+    /**
+     * Tests the entire successful network connection flow.
+     */
+    @Test
+    public void connect() throws Exception {
+        triggerConnect();
         when(mWifiConfigManager.getScanDetailCacheForNetwork(FRAMEWORK_NETWORK_ID))
                 .thenReturn(mScanDetailCache);
 
         when(mScanDetailCache.getScanDetail(sBSSID)).thenReturn(
                 getGoogleGuestScanDetail(TEST_RSSI, sBSSID, sFreq));
-        when(mScanDetailCache.get(sBSSID)).thenReturn(
+        when(mScanDetailCache.getScanResult(sBSSID)).thenReturn(
                 getGoogleGuestScanDetail(TEST_RSSI, sBSSID, sFreq).getScanResult());
 
         mWsm.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, sBSSID);
@@ -988,6 +1194,54 @@
         assertEquals("ConnectedState", getCurrentState().getName());
     }
 
+    /**
+     * Tests the network connection initiation sequence with no network request pending from
+     * from WifiNetworkFactory when we're already connected to a different network.
+     * This simulates the connect sequence using the public
+     * {@link WifiManager#enableNetwork(int, boolean)} and ensures that we invoke
+     * {@link WifiNative#connectToNetwork(WifiConfiguration)}.
+     */
+    @Test
+    public void triggerConnectWithNoNetworkRequestAndAlreadyConnected() throws Exception {
+        // Simulate the first connection.
+        connect();
+
+        // Change the network score to remove the network request.
+        sendDefaultNetworkRequest(TEST_OUTSCORED_NETWORK_SCORE);
+
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+        config.networkId = FRAMEWORK_NETWORK_ID + 1;
+        setupAndStartConnectSequence(config);
+        validateSuccessfulConnectSequence(config);
+        verify(mWifiPermissionsUtil, times(2)).checkNetworkSettingsPermission(anyInt());
+    }
+
+    /**
+     * Tests the network connection initiation sequence from a non-privileged app with no network
+     * request pending from from WifiNetworkFactory when we're already connected to a different
+     * network.
+     * This simulates the connect sequence using the public
+     * {@link WifiManager#enableNetwork(int, boolean)} and ensures that we don't invoke
+     * {@link WifiNative#connectToNetwork(WifiConfiguration)}.
+     */
+    @Test
+    public void triggerConnectWithNoNetworkRequestAndAlreadyConnectedButNonPrivilegedApp()
+            throws Exception {
+        // Simulate the first connection.
+        connect();
+
+        // Change the network score to remove the network request.
+        sendDefaultNetworkRequest(TEST_OUTSCORED_NETWORK_SCORE);
+
+        when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(false);
+
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+        config.networkId = FRAMEWORK_NETWORK_ID + 1;
+        setupAndStartConnectSequence(config);
+        validateFailureConnectSequence(config);
+        verify(mWifiPermissionsUtil, times(2)).checkNetworkSettingsPermission(anyInt());
+    }
+
     @Test
     public void connectWithNoEnablePermission() throws Exception {
         initializeAndAddNetworkAndVerifySuccess();
@@ -1560,31 +1814,90 @@
     }
 
     /**
-     * Verify successful Wps PBC network connection.
+     *  Test that we disconnect from a network if it was removed while we are in the
+     *  ObtainingIpState.
+     */
+    @Test
+    public void disconnectFromNetworkWhenRemovedWhileObtainingIpAddr() throws Exception {
+        initializeAndAddNetworkAndVerifySuccess();
+
+        when(mWifiConfigManager.enableNetwork(eq(0), eq(true), anyInt())).thenReturn(true);
+        when(mWifiConfigManager.checkAndUpdateLastConnectUid(eq(0), anyInt())).thenReturn(true);
+
+        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+        mLooper.dispatchAll();
+        verify(mWifiNative).removeAllNetworks();
+
+        mLooper.startAutoDispatch();
+        assertTrue(mWsm.syncEnableNetwork(mWsmAsyncChannel, 0, true));
+        mLooper.stopAutoDispatch();
+
+        verify(mWifiConfigManager).enableNetwork(eq(0), eq(true), anyInt());
+        verify(mWifiConnectivityManager).setUserConnectChoice(eq(0));
+        when(mWifiConfigManager.getScanDetailCacheForNetwork(FRAMEWORK_NETWORK_ID))
+                .thenReturn(mScanDetailCache);
+
+        when(mScanDetailCache.getScanDetail(sBSSID)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, sBSSID, sFreq));
+        when(mScanDetailCache.getScanResult(sBSSID)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, sBSSID, sFreq).getScanResult());
+
+        mWsm.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, sBSSID);
+        mLooper.dispatchAll();
+
+        mWsm.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
+                new StateChangeResult(0, sWifiSsid, sBSSID, SupplicantState.COMPLETED));
+        mLooper.dispatchAll();
+
+        assertEquals("ObtainingIpState", getCurrentState().getName());
+
+        // now remove the config
+        when(mWifiConfigManager.removeNetwork(eq(FRAMEWORK_NETWORK_ID), anyInt()))
+                .thenReturn(true);
+        mWsm.sendMessage(WifiManager.FORGET_NETWORK, FRAMEWORK_NETWORK_ID, MANAGED_PROFILE_UID);
+        mLooper.dispatchAll();
+        verify(mWifiConfigManager).removeNetwork(eq(FRAMEWORK_NETWORK_ID), anyInt());
+
+        reset(mWifiConfigManager);
+
+        when(mWifiConfigManager.getConfiguredNetwork(FRAMEWORK_NETWORK_ID)).thenReturn(null);
+
+        DhcpResults dhcpResults = new DhcpResults();
+        dhcpResults.setGateway("1.2.3.4");
+        dhcpResults.setIpAddress("192.168.1.100", 0);
+        dhcpResults.addDns("8.8.8.8");
+        dhcpResults.setLeaseDuration(3600);
+
+        injectDhcpSuccess(dhcpResults);
+        mLooper.dispatchAll();
+
+        assertEquals("DisconnectingState", getCurrentState().getName());
+    }
+
+    /**
+     * Sunny-day scenario for WPS connections. Verifies that after a START_WPS and
+     * NETWORK_CONNECTION_EVENT command, WifiStateMachine will have transitioned to ObtainingIpState
      */
     @Test
     public void wpsPbcConnectSuccess() throws Exception {
         loadComponentsInStaMode();
         mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
         mLooper.dispatchAll();
+        assertEquals("DisconnectedState", getCurrentState().getName());
 
-        when(mWifiNative.startWpsPbc(eq(sBSSID))).thenReturn(true);
         WpsInfo wpsInfo = new WpsInfo();
         wpsInfo.setup = WpsInfo.PBC;
         wpsInfo.BSSID = sBSSID;
-
+        when(mWifiNative.removeAllNetworks()).thenReturn(true);
+        when(mWifiNative.startWpsPbc(anyString())).thenReturn(true);
         mWsm.sendMessage(WifiManager.START_WPS, 0, 0, wpsInfo);
         mLooper.dispatchAll();
-        verify(mWifiNative).startWpsPbc(eq(sBSSID));
-
         assertEquals("WpsRunningState", getCurrentState().getName());
 
         setupMocksForWpsNetworkMigration();
-
         mWsm.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, null);
         mLooper.dispatchAll();
-
-        assertEquals("DisconnectedState", getCurrentState().getName());
+        assertEquals("ObtainingIpState", getCurrentState().getName());
         verifyMocksForWpsNetworkMigration();
     }
 
@@ -1610,6 +1923,68 @@
     }
 
     /**
+     * Verify that if Supplicant generates multiple networks for a WPS configuration,
+     * WifiStateMachine cycles into DisconnectedState
+     */
+    @Test
+    public void wpsPbcConnectFailure_tooManyConfigs() throws Exception {
+        loadComponentsInStaMode();
+        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+        mLooper.dispatchAll();
+        assertEquals("DisconnectedState", getCurrentState().getName());
+
+        WpsInfo wpsInfo = new WpsInfo();
+        wpsInfo.setup = WpsInfo.PBC;
+        wpsInfo.BSSID = sBSSID;
+        when(mWifiNative.removeAllNetworks()).thenReturn(true);
+        when(mWifiNative.startWpsPbc(anyString())).thenReturn(true);
+        mWsm.sendMessage(WifiManager.START_WPS, 0, 0, wpsInfo);
+        mLooper.dispatchAll();
+        assertEquals("WpsRunningState", getCurrentState().getName());
+
+        setupMocksForWpsNetworkMigration();
+        doAnswer(new AnswerWithArguments() {
+            public boolean answer(Map<String, WifiConfiguration> configs,
+                                  SparseArray<Map<String, String>> networkExtras) throws Exception {
+                configs.put("dummy1", new WifiConfiguration());
+                configs.put("dummy2", new WifiConfiguration());
+                return true;
+            }
+        }).when(mWifiNative).migrateNetworksFromSupplicant(any(Map.class), any(SparseArray.class));
+        mWsm.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, null);
+        mLooper.dispatchAll();
+        assertTrue("DisconnectedState".equals(getCurrentState().getName()));
+    }
+
+    /**
+     * Verify that when supplicant fails to load networks during WPS, WifiStateMachine cycles into
+     * DisconnectedState
+     */
+    @Test
+    public void wpsPbcConnectFailure_migrateNetworksFailure() throws Exception {
+        loadComponentsInStaMode();
+        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+        mLooper.dispatchAll();
+        assertEquals("DisconnectedState", getCurrentState().getName());
+
+        WpsInfo wpsInfo = new WpsInfo();
+        wpsInfo.setup = WpsInfo.PBC;
+        wpsInfo.BSSID = sBSSID;
+        when(mWifiNative.removeAllNetworks()).thenReturn(true);
+        when(mWifiNative.startWpsPbc(anyString())).thenReturn(true);
+        mWsm.sendMessage(WifiManager.START_WPS, 0, 0, wpsInfo);
+        mLooper.dispatchAll();
+        assertEquals("WpsRunningState", getCurrentState().getName());
+
+        setupMocksForWpsNetworkMigration();
+        when(mWifiNative.migrateNetworksFromSupplicant(any(Map.class), any(SparseArray.class)))
+                .thenReturn(false);
+        mWsm.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, null);
+        mLooper.dispatchAll();
+        assertEquals("DisconnectedState", getCurrentState().getName());
+    }
+
+    /**
      * Verify successful Wps Pin Display network connection.
      */
     @Test
@@ -1634,7 +2009,7 @@
         mWsm.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, null);
         mLooper.dispatchAll();
 
-        assertEquals("DisconnectedState", getCurrentState().getName());
+        assertEquals("ObtainingIpState", getCurrentState().getName());
         verifyMocksForWpsNetworkMigration();
     }
 
@@ -1682,6 +2057,7 @@
 
         verify(mWifiMetrics).incrementNumHalCrashes();
         verify(mSelfRecovery).trigger(eq(SelfRecovery.REASON_HAL_CRASH));
+        verify(mWifiDiagnostics).captureBugReportData(WifiDiagnostics.REPORT_REASON_HAL_CRASH);
     }
 
     @Test
@@ -1704,16 +2080,16 @@
 
         verify(mWifiMetrics).incrementNumWificondCrashes();
         verify(mSelfRecovery).trigger(eq(SelfRecovery.REASON_WIFICOND_CRASH));
+        verify(mWifiDiagnostics).captureBugReportData(WifiDiagnostics.REPORT_REASON_WIFICOND_CRASH);
     }
 
     private void setupMocksForWpsNetworkMigration() {
-        // Now trigger the network connection event for adding the WPS network.
+        WifiConfiguration config = new WifiConfiguration();
+        config.networkId = WPS_SUPPLICANT_NETWORK_ID;
+        config.SSID = DEFAULT_TEST_SSID;
         doAnswer(new AnswerWithArguments() {
             public boolean answer(Map<String, WifiConfiguration> configs,
                                   SparseArray<Map<String, String>> networkExtras) throws Exception {
-                WifiConfiguration config = new WifiConfiguration();
-                config.networkId = WPS_SUPPLICANT_NETWORK_ID;
-                config.SSID = DEFAULT_TEST_SSID;
                 configs.put("dummy", config);
                 return true;
             }
@@ -1722,6 +2098,10 @@
                 .thenReturn(new NetworkUpdateResult(WPS_FRAMEWORK_NETWORK_ID));
         when(mWifiConfigManager.enableNetwork(eq(WPS_FRAMEWORK_NETWORK_ID), anyBoolean(), anyInt()))
                 .thenReturn(true);
+        when(mWifiNative.getFrameworkNetworkId(eq(WPS_FRAMEWORK_NETWORK_ID))).thenReturn(
+                WPS_FRAMEWORK_NETWORK_ID);
+        when(mWifiConfigManager.getConfiguredNetwork(eq(WPS_FRAMEWORK_NETWORK_ID))).thenReturn(
+                config);
     }
 
     private void verifyMocksForWpsNetworkMigration() {
@@ -1748,7 +2128,7 @@
                 .thenReturn(mScanDetailCache);
         when(mScanDetailCache.getScanDetail(sBSSID1)).thenReturn(
                 getGoogleGuestScanDetail(TEST_RSSI, sBSSID1, sFreq1));
-        when(mScanDetailCache.get(sBSSID1)).thenReturn(
+        when(mScanDetailCache.getScanResult(sBSSID1)).thenReturn(
                 getGoogleGuestScanDetail(TEST_RSSI, sBSSID1, sFreq1).getScanResult());
 
         // This simulates the behavior of roaming to network with |sBSSID1|, |sFreq1|.
@@ -1776,7 +2156,7 @@
                 .thenReturn(mScanDetailCache);
         when(mScanDetailCache.getScanDetail(sBSSID1)).thenReturn(
                 getGoogleGuestScanDetail(TEST_RSSI, sBSSID1, sFreq1));
-        when(mScanDetailCache.get(sBSSID1)).thenReturn(
+        when(mScanDetailCache.getScanResult(sBSSID1)).thenReturn(
                 getGoogleGuestScanDetail(TEST_RSSI, sBSSID1, sFreq1).getScanResult());
 
         // This simulates the behavior of roaming to network with |sBSSID1|, |sFreq1|.
@@ -1856,6 +2236,153 @@
     }
 
     /**
+     * Test that the process uid has full wifiInfo access.
+     * Also tests that {@link WifiStateMachine#syncRequestConnectionInfo(String)} always
+     * returns a copy of WifiInfo.
+     */
+    @Test
+    public void testConnectedIdsAreVisibleFromOwnUid() throws Exception {
+        assertEquals(Process.myUid(), Binder.getCallingUid());
+        WifiInfo wifiInfo = mWsm.getWifiInfo();
+        wifiInfo.setBSSID(sBSSID);
+        wifiInfo.setSSID(WifiSsid.createFromAsciiEncoded(sSSID));
+
+        connect();
+        WifiInfo connectionInfo = mWsm.syncRequestConnectionInfo(mContext.getOpPackageName());
+
+        assertNotEquals(wifiInfo, connectionInfo);
+        assertEquals(wifiInfo.getSSID(), connectionInfo.getSSID());
+        assertEquals(wifiInfo.getBSSID(), connectionInfo.getBSSID());
+    }
+
+    /**
+     * Test that connected SSID and BSSID are not exposed to an app that does not have the
+     * appropriate permissions.
+     * Also tests that {@link WifiStateMachine#syncRequestConnectionInfo(String)} always
+     * returns a copy of WifiInfo.
+     */
+    @Test
+    public void testConnectedIdsAreHiddenFromRandomApp() throws Exception {
+        int actualUid = Binder.getCallingUid();
+        int fakeUid = Process.myUid() + 100000;
+        assertNotEquals(actualUid, fakeUid);
+        BinderUtil.setUid(fakeUid);
+        try {
+            WifiInfo wifiInfo = mWsm.getWifiInfo();
+
+            // Get into a connected state, with known BSSID and SSID
+            connect();
+            assertEquals(sBSSID, wifiInfo.getBSSID());
+            assertEquals(sWifiSsid, wifiInfo.getWifiSsid());
+
+            when(mWifiPermissionsUtil.canAccessFullConnectionInfo(any(), anyString(), eq(fakeUid),
+                    anyInt())).thenReturn(false);
+
+            WifiInfo connectionInfo = mWsm.syncRequestConnectionInfo(mContext.getOpPackageName());
+
+            assertNotEquals(wifiInfo, connectionInfo);
+            assertEquals(WifiSsid.NONE, connectionInfo.getSSID());
+            assertEquals(WifiInfo.DEFAULT_MAC_ADDRESS, connectionInfo.getBSSID());
+        } finally {
+            BinderUtil.setUid(actualUid);
+        }
+    }
+
+    /**
+     * Test that connected SSID and BSSID are not exposed to an app that does not have the
+     * appropriate permissions, when canAccessScanResults raises a SecurityException.
+     * Also tests that {@link WifiStateMachine#syncRequestConnectionInfo(String)} always
+     * returns a copy of WifiInfo.
+     */
+    @Test
+    public void testConnectedIdsAreHiddenOnSecurityException() throws Exception {
+        int actualUid = Binder.getCallingUid();
+        int fakeUid = Process.myUid() + 100000;
+        assertNotEquals(actualUid, fakeUid);
+        BinderUtil.setUid(fakeUid);
+        try {
+            WifiInfo wifiInfo = mWsm.getWifiInfo();
+
+            // Get into a connected state, with known BSSID and SSID
+            connect();
+            assertEquals(sBSSID, wifiInfo.getBSSID());
+            assertEquals(sWifiSsid, wifiInfo.getWifiSsid());
+
+            when(mWifiPermissionsUtil.canAccessFullConnectionInfo(any(), anyString(), eq(fakeUid),
+                    anyInt())).thenThrow(new SecurityException());
+
+            WifiInfo connectionInfo = mWsm.syncRequestConnectionInfo(mContext.getOpPackageName());
+
+            assertNotEquals(wifiInfo, connectionInfo);
+            assertEquals(WifiSsid.NONE, connectionInfo.getSSID());
+            assertEquals(WifiInfo.DEFAULT_MAC_ADDRESS, connectionInfo.getBSSID());
+        } finally {
+            BinderUtil.setUid(actualUid);
+        }
+    }
+
+    /**
+     * Test that connected SSID and BSSID are exposed to an app that does have the
+     * appropriate permissions.
+     */
+    @Test
+    public void testConnectedIdsAreVisibleFromPermittedApp() throws Exception {
+        int actualUid = Binder.getCallingUid();
+        int fakeUid = Process.myUid() + 100000;
+        BinderUtil.setUid(fakeUid);
+        try {
+            WifiInfo wifiInfo = mWsm.getWifiInfo();
+
+            // Get into a connected state, with known BSSID and SSID
+            connect();
+            assertEquals(sBSSID, wifiInfo.getBSSID());
+            assertEquals(sWifiSsid, wifiInfo.getWifiSsid());
+
+            when(mWifiPermissionsUtil.canAccessFullConnectionInfo(any(), anyString(), eq(fakeUid),
+                    anyInt())).thenReturn(true);
+
+            WifiInfo connectionInfo = mWsm.syncRequestConnectionInfo(mContext.getOpPackageName());
+
+            assertNotEquals(wifiInfo, connectionInfo);
+            assertEquals(wifiInfo.getSSID(), connectionInfo.getSSID());
+            assertEquals(wifiInfo.getBSSID(), connectionInfo.getBSSID());
+            // Access to our MAC address uses a different permission, make sure it is not granted
+            assertEquals(WifiInfo.DEFAULT_MAC_ADDRESS, connectionInfo.getMacAddress());
+        } finally {
+            BinderUtil.setUid(actualUid);
+        }
+    }
+
+    /**
+     * Test that reconnectCommand() triggers connectivity scan when WifiStateMachine
+     * is in DisconnectedMode.
+     */
+    @Test
+    public void testReconnectCommandWhenDisconnected() throws Exception {
+        // Connect to network with |sBSSID|, |sFreq|, and then disconnect.
+        disconnect();
+
+        mWsm.reconnectCommand(WifiStateMachine.WIFI_WORK_SOURCE);
+        mLooper.dispatchAll();
+        verify(mWifiConnectivityManager).forceConnectivityScan(WifiStateMachine.WIFI_WORK_SOURCE);
+    }
+
+    /**
+     * Test that reconnectCommand() doesn't trigger connectivity scan when WifiStateMachine
+     * is in ConnectedMode.
+     */
+    @Test
+    public void testReconnectCommandWhenConnected() throws Exception {
+        // Connect to network with |sBSSID|, |sFreq|.
+        connect();
+
+        mWsm.reconnectCommand(WifiStateMachine.WIFI_WORK_SOURCE);
+        mLooper.dispatchAll();
+        verify(mWifiConnectivityManager, never())
+                .forceConnectivityScan(WifiStateMachine.WIFI_WORK_SOURCE);
+    }
+
+    /**
      * Adds the network without putting WifiStateMachine into ConnectMode.
      */
     @Test
@@ -1872,9 +2399,7 @@
     @Test
     public void testStartWps_nullWpsInfo() throws Exception {
         loadComponentsInStaMode();
-        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
-        assertEquals(WifiStateMachine.CONNECT_MODE, mWsm.getOperationalModeForTest());
-        assertEquals("DisconnectedState", getCurrentState().getName());
+
         mLooper.startAutoDispatch();
         Message reply = mWsmAsyncChannel.sendMessageSynchronously(WifiManager.START_WPS, 0, 0,
                 null);
@@ -1889,9 +2414,6 @@
     @Test
     public void testSyncDisableNetwork_failure() throws Exception {
         loadComponentsInStaMode();
-        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
-        assertEquals(WifiStateMachine.CONNECT_MODE, mWsm.getOperationalModeForTest());
-        assertEquals("DisconnectedState", getCurrentState().getName());
         when(mWifiConfigManager.disableNetwork(anyInt(), anyInt())).thenReturn(false);
 
         mLooper.startAutoDispatch();
@@ -2151,6 +2673,43 @@
     }
 
     /**
+     * Verifies that WifiStateMachine sets and unsets appropriate 'RecentFailureReason' values
+     * on a WifiConfiguration when it fails association, authentication, or successfully connects
+     */
+    @Test
+    public void testExtraFailureReason_ApIsBusy() throws Exception {
+        // Setup CONNECT_MODE & a WifiConfiguration
+        initializeAndAddNetworkAndVerifySuccess();
+        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+        mLooper.dispatchAll();
+        // Trigger a connection to this (CMD_START_CONNECT will actually fail, but it sets up
+        // targetNetworkId state)
+        mWsm.sendMessage(WifiStateMachine.CMD_START_CONNECT, 0, 0, sBSSID);
+        mLooper.dispatchAll();
+        // Simulate an ASSOCIATION_REJECTION_EVENT, due to the AP being busy
+        mWsm.sendMessage(WifiMonitor.ASSOCIATION_REJECTION_EVENT, 0,
+                ISupplicantStaIfaceCallback.StatusCode.AP_UNABLE_TO_HANDLE_NEW_STA, sBSSID);
+        mLooper.dispatchAll();
+        verify(mWifiConfigManager).setRecentFailureAssociationStatus(eq(0),
+                eq(WifiConfiguration.RecentFailure.STATUS_AP_UNABLE_TO_HANDLE_NEW_STA));
+        assertEquals("DisconnectedState", getCurrentState().getName());
+
+        // Simulate an AUTHENTICATION_FAILURE_EVENT, which should clear the ExtraFailureReason
+        reset(mWifiConfigManager);
+        mWsm.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT, 0, 0, null);
+        mLooper.dispatchAll();
+        verify(mWifiConfigManager).clearRecentFailureReason(eq(0));
+        verify(mWifiConfigManager, never()).setRecentFailureAssociationStatus(anyInt(), anyInt());
+
+        // Simulate a NETWORK_CONNECTION_EVENT which should clear the ExtraFailureReason
+        reset(mWifiConfigManager);
+        mWsm.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, null);
+        mLooper.dispatchAll();
+        verify(mWifiConfigManager).clearRecentFailureReason(eq(0));
+        verify(mWifiConfigManager, never()).setRecentFailureAssociationStatus(anyInt(), anyInt());
+    }
+
+    /**
      * Test that the helper method
      * {@link WifiStateMachine#shouldEvaluateWhetherToSendExplicitlySelected(WifiConfiguration)}
      * returns true when we connect to the last selected network before expiration of
@@ -2214,20 +2773,4 @@
         currentConfig.networkId = lastSelectedNetworkId - 1;
         assertFalse(mWsm.shouldEvaluateWhetherToSendExplicitlySelected(currentConfig));
     }
-
-    /**
-     * Test that {@link WifiStateMachine#syncRequestConnectionInfo()} always returns a copy of
-     * WifiInfo.
-     */
-    @Test
-    public void testSyncRequestConnectionInfoDoesNotReturnLocalReference() {
-        WifiInfo wifiInfo = mWsm.getWifiInfo();
-        wifiInfo.setBSSID(sBSSID);
-        wifiInfo.setSSID(WifiSsid.createFromAsciiEncoded(sSSID));
-
-        WifiInfo syncWifiInfo = mWsm.syncRequestConnectionInfo();
-        assertEquals(wifiInfo.getSSID(), syncWifiInfo.getSSID());
-        assertEquals(wifiInfo.getBSSID(), syncWifiInfo.getBSSID());
-        assertFalse(wifiInfo == syncWifiInfo);
-    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WificondControlTest.java b/tests/wifitests/src/com/android/server/wifi/WificondControlTest.java
index 2617331..cca2045 100644
--- a/tests/wifitests/src/com/android/server/wifi/WificondControlTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WificondControlTest.java
@@ -23,7 +23,10 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.argThat;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -33,6 +36,8 @@
 import android.net.wifi.IScanEvent;
 import android.net.wifi.IWifiScannerImpl;
 import android.net.wifi.IWificond;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiEnterpriseConfig;
 import android.net.wifi.WifiScanner;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -48,6 +53,7 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatcher;
 
+import java.io.ByteArrayOutputStream;
 import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.HashSet;
@@ -60,6 +66,8 @@
 public class WificondControlTest {
     private WifiInjector mWifiInjector;
     private WifiMonitor mWifiMonitor;
+    private WifiMetrics mWifiMetrics;
+    private CarrierNetworkConfig mCarrierNetworkConfig;
     private WificondControl mWificondControl;
     private static final String TEST_INTERFACE_NAME = "test_wlan_if";
     private static final byte[] TEST_SSID =
@@ -68,7 +76,7 @@
             new byte[] {(byte) 0x12, (byte) 0xef, (byte) 0xa1,
                         (byte) 0x2c, (byte) 0x97, (byte) 0x8b};
     // This the IE buffer which is consistent with TEST_SSID.
-    private static final byte[] TEST_INFO_ELEMENT =
+    private static final byte[] TEST_INFO_ELEMENT_SSID =
             new byte[] {
                     // Element ID for SSID.
                     (byte) 0x00,
@@ -76,6 +84,17 @@
                     (byte) 0x0b,
                     // This is string "GoogleGuest"
                     'G', 'o', 'o', 'g', 'l', 'e', 'G', 'u', 'e', 's', 't'};
+    // RSN IE data indicating EAP key management.
+    private static final byte[] TEST_INFO_ELEMENT_RSN =
+            new byte[] {
+                    // Element ID for RSN.
+                    (byte) 0x30,
+                    // Length of the element data.
+                    (byte) 0x18,
+                    (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x02,
+                    (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x04,
+                    (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x02, (byte) 0x01, (byte) 0x00,
+                    (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x01, (byte) 0x00, (byte) 0x00 };
 
     private static final int TEST_FREQUENCY = 2456;
     private static final int TEST_SIGNAL_MBM = -4500;
@@ -86,7 +105,7 @@
             new NativeScanResult() {{
                 ssid = TEST_SSID;
                 bssid = TEST_BSSID;
-                infoElement = TEST_INFO_ELEMENT;
+                infoElement = TEST_INFO_ELEMENT_SSID;
                 frequency = TEST_FREQUENCY;
                 signalMbm = TEST_SIGNAL_MBM;
                 capability = TEST_CAPABILITY;
@@ -127,7 +146,10 @@
     public void setUp() throws Exception {
         mWifiInjector = mock(WifiInjector.class);
         mWifiMonitor = mock(WifiMonitor.class);
-        mWificondControl = new WificondControl(mWifiInjector, mWifiMonitor);
+        mWifiMetrics = mock(WifiMetrics.class);
+        when(mWifiInjector.getWifiMetrics()).thenReturn(mWifiMetrics);
+        mCarrierNetworkConfig = mock(CarrierNetworkConfig.class);
+        mWificondControl = new WificondControl(mWifiInjector, mWifiMonitor, mCarrierNetworkConfig);
     }
 
     /**
@@ -441,7 +463,8 @@
         assertTrue(mWificondControl.tearDownInterfaces());
 
         // getScanResults should fail.
-        assertEquals(0, mWificondControl.getScanResults().size());
+        assertEquals(0,
+                mWificondControl.getScanResults(WificondControl.SCAN_TYPE_SINGLE_SCAN).size());
     }
 
     /**
@@ -456,7 +479,12 @@
         NativeScanResult[] mockScanResults = {MOCK_NATIVE_SCAN_RESULT};
         when(scanner.getScanResults()).thenReturn(mockScanResults);
 
-        ArrayList<ScanDetail> returnedScanResults = mWificondControl.getScanResults();
+        ArrayList<ScanDetail> returnedScanResults = mWificondControl.getScanResults(
+                WificondControl.SCAN_TYPE_SINGLE_SCAN);
+        // The test IEs {@link #TEST_INFO_ELEMENT} doesn't contained RSN IE, which means non-EAP
+        // AP. So verify carrier network is not checked, since EAP is currently required for a
+        // carrier network.
+        verify(mCarrierNetworkConfig, never()).isCarrierNetwork(anyString());
         assertEquals(mockScanResults.length, returnedScanResults.size());
         // Since NativeScanResult is organized differently from ScanResult, this only checks
         // a few fields.
@@ -471,6 +499,60 @@
     }
 
     /**
+     * Verifies that scan result's carrier network info {@link ScanResult#isCarrierAp} and
+     * {@link ScanResult#getCarrierApEapType} is set appropriated based on the carrier network
+     * config.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testGetScanResultsForCarrierAp() throws Exception {
+        IWifiScannerImpl scanner = setupClientInterfaceAndCreateMockWificondScanner();
+        assertNotNull(scanner);
+
+        // Include RSN IE to indicate EAP key management.
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        out.write(TEST_INFO_ELEMENT_SSID);
+        out.write(TEST_INFO_ELEMENT_RSN);
+        NativeScanResult nativeScanResult = new NativeScanResult(MOCK_NATIVE_SCAN_RESULT);
+        nativeScanResult.infoElement = out.toByteArray();
+        when(scanner.getScanResults()).thenReturn(new NativeScanResult[] {nativeScanResult});
+
+        // AP associated with a carrier network.
+        int eapType = WifiEnterpriseConfig.Eap.SIM;
+        String carrierName = "Test Carrier";
+        when(mCarrierNetworkConfig.isCarrierNetwork(new String(nativeScanResult.ssid)))
+                .thenReturn(true);
+        when(mCarrierNetworkConfig.getNetworkEapType(new String(nativeScanResult.ssid)))
+                .thenReturn(eapType);
+        when(mCarrierNetworkConfig.getCarrierName(new String(nativeScanResult.ssid)))
+                .thenReturn(carrierName);
+        ArrayList<ScanDetail> returnedScanResults = mWificondControl.getScanResults(
+                WificondControl.SCAN_TYPE_SINGLE_SCAN);
+        assertEquals(1, returnedScanResults.size());
+        // Verify returned scan result.
+        ScanResult scanResult = returnedScanResults.get(0).getScanResult();
+        assertArrayEquals(nativeScanResult.ssid, scanResult.SSID.getBytes());
+        assertTrue(scanResult.isCarrierAp);
+        assertEquals(eapType, scanResult.carrierApEapType);
+        assertEquals(carrierName, scanResult.carrierName);
+        reset(mCarrierNetworkConfig);
+
+        // AP not associated with a carrier network.
+        when(mCarrierNetworkConfig.isCarrierNetwork(new String(nativeScanResult.ssid)))
+                .thenReturn(false);
+        returnedScanResults = mWificondControl.getScanResults(
+                WificondControl.SCAN_TYPE_SINGLE_SCAN);
+        assertEquals(1, returnedScanResults.size());
+        // Verify returned scan result.
+        scanResult = returnedScanResults.get(0).getScanResult();
+        assertArrayEquals(nativeScanResult.ssid, scanResult.SSID.getBytes());
+        assertFalse(scanResult.isCarrierAp);
+        assertEquals(ScanResult.UNSPECIFIED, scanResult.carrierApEapType);
+        assertEquals(null, scanResult.carrierName);
+    }
+
+    /**
      * Verifies that Scan() can convert input parameters to SingleScanSettings correctly.
      */
     @Test
@@ -581,7 +663,7 @@
 
     /**
      * Verifies that WificondControl can invoke WifiMonitor broadcast methods upon pno scan
-     * reuslt event.
+     * result event.
      */
     @Test
     public void testPnoScanResultEvent() throws Exception {
@@ -592,11 +674,48 @@
         IPnoScanEvent pnoScanEvent = messageCaptor.getValue();
         assertNotNull(pnoScanEvent);
         pnoScanEvent.OnPnoNetworkFound();
-
         verify(mWifiMonitor).broadcastPnoScanResultEvent(any(String.class));
     }
 
     /**
+     * Verifies that WificondControl can invoke WifiMetrics pno scan count methods upon pno event.
+     */
+    @Test
+    public void testPnoScanEventsForMetrics() throws Exception {
+        IWifiScannerImpl scanner = setupClientInterfaceAndCreateMockWificondScanner();
+
+        ArgumentCaptor<IPnoScanEvent> messageCaptor = ArgumentCaptor.forClass(IPnoScanEvent.class);
+        verify(scanner).subscribePnoScanEvents(messageCaptor.capture());
+        IPnoScanEvent pnoScanEvent = messageCaptor.getValue();
+        assertNotNull(pnoScanEvent);
+
+        pnoScanEvent.OnPnoNetworkFound();
+        verify(mWifiMetrics).incrementPnoFoundNetworkEventCount();
+
+        pnoScanEvent.OnPnoScanFailed();
+        verify(mWifiMetrics).incrementPnoScanFailedCount();
+
+        pnoScanEvent.OnPnoScanOverOffloadStarted();
+        verify(mWifiMetrics).incrementPnoScanStartedOverOffloadCount();
+
+        pnoScanEvent.OnPnoScanOverOffloadFailed(0);
+        verify(mWifiMetrics).incrementPnoScanFailedOverOffloadCount();
+    }
+
+    /**
+     * Verifies that startPnoScan() can invoke WifiMetrics pno scan count methods correctly.
+     */
+    @Test
+    public void testStartPnoScanForMetrics() throws Exception {
+        IWifiScannerImpl scanner = setupClientInterfaceAndCreateMockWificondScanner();
+
+        when(scanner.startPnoScan(any(PnoSettings.class))).thenReturn(false);
+        assertFalse(mWificondControl.startPnoScan(TEST_PNO_SETTINGS));
+        verify(mWifiMetrics).incrementPnoScanStartAttempCount();
+        verify(mWifiMetrics).incrementPnoScanFailedCount();
+    }
+
+    /**
      * Verifies that abortScan() calls underlying wificond.
      */
     @Test
diff --git a/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareDataPathStateManagerTest.java b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareDataPathStateManagerTest.java
index 8063004..61143d9 100644
--- a/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareDataPathStateManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareDataPathStateManagerTest.java
@@ -19,6 +19,7 @@
 import static android.hardware.wifi.V1_0.NanDataPathChannelCfg.CHANNEL_NOT_REQUESTED;
 
 import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -83,8 +84,9 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.lang.reflect.Field;
 import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
 
 /**
  * Unit test harness for WifiAwareDataPathStateManager class.
@@ -99,7 +101,6 @@
     @Mock private WifiAwareNativeManager mMockNativeManager;
     @Mock private WifiAwareNativeApi mMockNative;
     @Mock private Context mMockContext;
-    @Mock private IWifiAwareManager mMockAwareService;
     @Mock private ConnectivityManager mMockCm;
     @Mock private INetworkManagementService mMockNwMgt;
     @Mock private WifiAwareDataPathStateManager.NetworkInterfaceWrapper mMockNetworkInterface;
@@ -172,7 +173,8 @@
         when(mPermissionsWrapperMock.getUidPermission(eq(Manifest.permission.CONNECTIVITY_INTERNAL),
                 anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
 
-        installDataPathStateManagerMocks();
+        mDut.mDataPathMgr.mNwService = mMockNwMgt;
+        mDut.mDataPathMgr.mNiWrapper = mMockNetworkInterface;
     }
 
     /**
@@ -252,7 +254,7 @@
             mMockLooper.dispatchAll();
         }
 
-        verifyNoMoreInteractions(mMockNative);
+        verifyNoMoreInteractions(mMockNative, mMockNwMgt);
     }
 
     /**
@@ -273,12 +275,12 @@
                 anyInt())).thenReturn(PackageManager.PERMISSION_DENIED);
 
         // (0) initialize
-        DataPathEndPointInfo res = initDataPathEndPoint(clientId, pubSubId, requestorId,
+        DataPathEndPointInfo res = initDataPathEndPoint(true, clientId, pubSubId, requestorId,
                 peerDiscoveryMac, inOrder, inOrderM, false);
 
         // (1) request network
         NetworkRequest nr = getSessionNetworkRequest(clientId, res.mSessionId, res.mPeerHandle, pmk,
-                null, false);
+                null, false, 0);
 
         Message reqNetworkMsg = Message.obtain();
         reqNetworkMsg.what = NetworkFactory.CMD_REQUEST_NETWORK;
@@ -288,7 +290,7 @@
         mMockLooper.dispatchAll();
 
         // failure: no interactions with connectivity manager or native manager
-        verifyNoMoreInteractions(mMockNative, mMockCm, mAwareMetricsMock);
+        verifyNoMoreInteractions(mMockNative, mMockCm, mAwareMetricsMock, mMockNwMgt);
     }
 
     /**
@@ -309,12 +311,12 @@
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
 
         // (0) initialize
-        DataPathEndPointInfo res = initDataPathEndPoint(clientId, pubSubId, requestorId,
+        DataPathEndPointInfo res = initDataPathEndPoint(true, clientId, pubSubId, requestorId,
                 peerDiscoveryMac, inOrder, inOrderM, true);
 
         // (1) request network
         NetworkRequest nr = getSessionNetworkRequest(clientId, res.mSessionId, res.mPeerHandle,
-                null, null, true);
+                null, null, true, 0);
 
         Message reqNetworkMsg = Message.obtain();
         reqNetworkMsg.what = NetworkFactory.CMD_REQUEST_NETWORK;
@@ -342,7 +344,302 @@
         mMockLooper.dispatchAll();
 
         // failure if there's further activity
-        verifyNoMoreInteractions(mMockNative, mMockCm, mAwareMetricsMock);
+        verifyNoMoreInteractions(mMockNative, mMockCm, mAwareMetricsMock, mMockNwMgt);
+    }
+
+    /**
+     * Validate multiple NDPs created on a single NDI. Most importantly that the interface is
+     * set up on first NDP and torn down on last NDP - and not when one or the other is created or
+     * deleted.
+     *
+     * Procedure:
+     * - create NDP 1, 2, and 3 (interface up only on first)
+     * - delete NDP 2, 1, and 3 (interface down only on last)
+     */
+    @Test
+    public void testMultipleNdpsOnSingleNdi() throws Exception {
+        final int clientId = 123;
+        final byte pubSubId = 58;
+        final int requestorId = 1341234;
+        final int ndpId = 2;
+
+        final int[] startOrder = {0, 1, 2};
+        final int[] endOrder = {1, 0, 2};
+        int networkRequestId = 0;
+
+        ArgumentCaptor<Messenger> messengerCaptor = ArgumentCaptor.forClass(Messenger.class);
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        InOrder inOrder = inOrder(mMockNative, mMockCm, mMockCallback, mMockSessionCallback,
+                mMockNwMgt);
+        InOrder inOrderM = inOrder(mAwareMetricsMock);
+
+        NetworkRequest[] nrs = new NetworkRequest[3];
+        DataPathEndPointInfo[] ress = new DataPathEndPointInfo[3];
+        Messenger[] agentMessengers = new Messenger[3];
+        Messenger messenger = null;
+        boolean first = true;
+        for (int i : startOrder) {
+            networkRequestId += 1;
+            byte[] peerDiscoveryMac = HexEncoding.decode("000102030405".toCharArray(), false);
+            byte[] peerDataPathMac = HexEncoding.decode("0A0B0C0D0E0F".toCharArray(), false);
+            peerDiscoveryMac[5] = (byte) (peerDiscoveryMac[5] + i);
+            peerDataPathMac[5] = (byte) (peerDataPathMac[5] + i);
+
+            // (0) initialize
+            ress[i] = initDataPathEndPoint(first, clientId, (byte) (pubSubId + i),
+                    requestorId + i, peerDiscoveryMac, inOrder, inOrderM, false);
+            if (first) {
+                messenger = ress[i].mMessenger;
+            }
+
+            // (1) request network
+            nrs[i] = getSessionNetworkRequest(clientId, ress[i].mSessionId, ress[i].mPeerHandle,
+                    null, null, false, networkRequestId);
+
+            Message reqNetworkMsg = Message.obtain();
+            reqNetworkMsg.what = NetworkFactory.CMD_REQUEST_NETWORK;
+            reqNetworkMsg.obj = nrs[i];
+            reqNetworkMsg.arg1 = 0;
+            messenger.send(reqNetworkMsg);
+            mMockLooper.dispatchAll();
+            inOrder.verify(mMockNative).initiateDataPath(transactionId.capture(),
+                    eq(requestorId + i),
+                    eq(CHANNEL_NOT_REQUESTED), anyInt(), eq(peerDiscoveryMac),
+                    eq(sAwareInterfacePrefix + "0"), eq(null),
+                    eq(null), eq(false), any());
+
+            mDut.onInitiateDataPathResponseSuccess(transactionId.getValue(), ndpId + i);
+            mMockLooper.dispatchAll();
+
+            // (2) get confirmation
+            mDut.onDataPathConfirmNotification(ndpId + i, peerDataPathMac, true, 0, null);
+            mMockLooper.dispatchAll();
+            if (first) {
+                inOrder.verify(mMockNwMgt).setInterfaceUp(anyString());
+                inOrder.verify(mMockNwMgt).enableIpv6(anyString());
+
+                first = false;
+            }
+            inOrder.verify(mMockCm).registerNetworkAgent(messengerCaptor.capture(), any(), any(),
+                    any(), anyInt(), any());
+            agentMessengers[i] = messengerCaptor.getValue();
+            inOrderM.verify(mAwareMetricsMock).recordNdpStatus(eq(NanStatusType.SUCCESS),
+                    eq(false), anyLong());
+            inOrderM.verify(mAwareMetricsMock).recordNdpCreation(anyInt(), any());
+        }
+
+        // (3) end data-path (unless didn't get confirmation)
+        int index = 0;
+        for (int i: endOrder) {
+            Message endNetworkReqMsg = Message.obtain();
+            endNetworkReqMsg.what = NetworkFactory.CMD_CANCEL_REQUEST;
+            endNetworkReqMsg.obj = nrs[i];
+            messenger.send(endNetworkReqMsg);
+
+            Message endNetworkUsageMsg = Message.obtain();
+            endNetworkUsageMsg.what = AsyncChannel.CMD_CHANNEL_DISCONNECTED;
+            agentMessengers[i].send(endNetworkUsageMsg);
+            mMockLooper.dispatchAll();
+
+            inOrder.verify(mMockNative).endDataPath(transactionId.capture(), eq(ndpId + i));
+
+            mDut.onEndDataPathResponse(transactionId.getValue(), true, 0);
+            mDut.onDataPathEndNotification(ndpId + i);
+            mMockLooper.dispatchAll();
+
+            if (index++ == endOrder.length - 1) {
+                inOrder.verify(mMockNwMgt).setInterfaceDown(anyString());
+            }
+            inOrderM.verify(mAwareMetricsMock).recordNdpSessionDuration(anyLong());
+        }
+
+        verifyNoMoreInteractions(mMockNative, mMockCm, mAwareMetricsMock, mMockNwMgt);
+    }
+
+    /**
+     * Validate that multiple NDP requests which resolve to the same canonical request are treated
+     * as one.
+     */
+    @Test
+    public void testMultipleIdenticalRequests() throws Exception {
+        final int numRequestsPre = 6;
+        final int numRequestsPost = 5;
+        final int clientId = 123;
+        final int ndpId = 5;
+        final byte[] peerDiscoveryMac = HexEncoding.decode("000102030405".toCharArray(), false);
+        final byte[] peerDataPathMac = HexEncoding.decode("0A0B0C0D0E0F".toCharArray(), false);
+        NetworkRequest[] nrs = new NetworkRequest[numRequestsPre + numRequestsPost];
+
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        ArgumentCaptor<Messenger> agentMessengerCaptor = ArgumentCaptor.forClass(Messenger.class);
+
+        InOrder inOrder = inOrder(mMockNative, mMockCm, mMockCallback, mMockSessionCallback,
+                mMockNwMgt);
+        InOrder inOrderM = inOrder(mAwareMetricsMock);
+
+        // (1) initialize all clients
+        Messenger messenger = initOobDataPathEndPoint(true, 1, clientId, inOrder, inOrderM);
+        for (int i = 1; i < numRequestsPre + numRequestsPost; ++i) {
+            initOobDataPathEndPoint(false, 1, clientId + i, inOrder, inOrderM);
+        }
+
+        // (2) make 3 network requests (all identical under the hood)
+        for (int i = 0; i < numRequestsPre; ++i) {
+            nrs[i] = getDirectNetworkRequest(clientId + i,
+                    WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR, peerDiscoveryMac, null,
+                    null, i);
+
+            Message reqNetworkMsg = Message.obtain();
+            reqNetworkMsg.what = NetworkFactory.CMD_REQUEST_NETWORK;
+            reqNetworkMsg.obj = nrs[i];
+            reqNetworkMsg.arg1 = 0;
+            messenger.send(reqNetworkMsg);
+        }
+        mMockLooper.dispatchAll();
+
+        // (3) verify the start NDP HAL request
+        inOrder.verify(mMockNative).initiateDataPath(transactionId.capture(), eq(0),
+                eq(CHANNEL_NOT_REQUESTED), anyInt(), eq(peerDiscoveryMac),
+                eq(sAwareInterfacePrefix + "0"), eq(null), eq(null), eq(true), any());
+
+        // (4) unregister request #0 (the primary)
+        Message endNetworkReqMsg = Message.obtain();
+        endNetworkReqMsg.what = NetworkFactory.CMD_CANCEL_REQUEST;
+        endNetworkReqMsg.obj = nrs[0];
+        messenger.send(endNetworkReqMsg);
+        mMockLooper.dispatchAll();
+
+        // (5) respond to the registration request
+        mDut.onInitiateDataPathResponseSuccess(transactionId.getValue(), ndpId);
+        mMockLooper.dispatchAll();
+
+        // (6) unregister request #1
+        endNetworkReqMsg = Message.obtain();
+        endNetworkReqMsg.what = NetworkFactory.CMD_CANCEL_REQUEST;
+        endNetworkReqMsg.obj = nrs[1];
+        messenger.send(endNetworkReqMsg);
+        mMockLooper.dispatchAll();
+
+        // (7) confirm the NDP creation
+        mDut.onDataPathConfirmNotification(ndpId, peerDataPathMac, true, 0, null);
+        mMockLooper.dispatchAll();
+
+        inOrder.verify(mMockNwMgt).setInterfaceUp(anyString());
+        inOrder.verify(mMockNwMgt).enableIpv6(anyString());
+        inOrder.verify(mMockCm).registerNetworkAgent(agentMessengerCaptor.capture(), any(), any(),
+                any(), anyInt(), any());
+        inOrderM.verify(mAwareMetricsMock).recordNdpStatus(eq(NanStatusType.SUCCESS),
+                eq(true), anyLong());
+        inOrderM.verify(mAwareMetricsMock).recordNdpCreation(anyInt(), any());
+
+        // (8) execute 'post' requests
+        for (int i = numRequestsPre; i < numRequestsPre + numRequestsPost; ++i) {
+            nrs[i] = getDirectNetworkRequest(clientId + i,
+                    WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR, peerDiscoveryMac, null,
+                    null, i);
+
+            Message reqNetworkMsg = Message.obtain();
+            reqNetworkMsg.what = NetworkFactory.CMD_REQUEST_NETWORK;
+            reqNetworkMsg.obj = nrs[i];
+            reqNetworkMsg.arg1 = 0;
+            messenger.send(reqNetworkMsg);
+        }
+        mMockLooper.dispatchAll();
+
+        // (9) unregister all requests
+        for (int i = 2; i < numRequestsPre + numRequestsPost; ++i) {
+            endNetworkReqMsg = Message.obtain();
+            endNetworkReqMsg.what = NetworkFactory.CMD_CANCEL_REQUEST;
+            endNetworkReqMsg.obj = nrs[i];
+            messenger.send(endNetworkReqMsg);
+            mMockLooper.dispatchAll();
+        }
+
+        Message endNetworkUsageMsg = Message.obtain();
+        endNetworkUsageMsg.what = AsyncChannel.CMD_CHANNEL_DISCONNECTED;
+        agentMessengerCaptor.getValue().send(endNetworkUsageMsg);
+        mMockLooper.dispatchAll();
+
+        // (10) verify that NDP torn down
+        inOrder.verify(mMockNative).endDataPath(transactionId.capture(), eq(ndpId));
+
+        mDut.onEndDataPathResponse(transactionId.getValue(), true, 0);
+        mDut.onDataPathEndNotification(ndpId);
+        mMockLooper.dispatchAll();
+
+        inOrder.verify(mMockNwMgt).setInterfaceDown(anyString());
+        inOrderM.verify(mAwareMetricsMock).recordNdpSessionDuration(anyLong());
+
+        verifyNoMoreInteractions(mMockNative, mMockCm, mMockCallback, mMockSessionCallback,
+                mAwareMetricsMock, mMockNwMgt);
+    }
+
+    /**
+     * Validate that multiple NDP requests to the same peer target different NDIs.
+     */
+    @Test
+    public void testMultipleNdi() throws Exception {
+        final int numNdis = 5;
+        final int clientId = 123;
+        final int ndpId = 5;
+        final byte[] peerDiscoveryMac = HexEncoding.decode("000102030405".toCharArray(), false);
+        final byte[] peerDataPathMac = HexEncoding.decode("0A0B0C0D0E0F".toCharArray(), false);
+
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        ArgumentCaptor<String> ifNameCaptor = ArgumentCaptor.forClass(String.class);
+
+        InOrder inOrder = inOrder(mMockNative, mMockCm, mMockCallback, mMockSessionCallback,
+                mMockNwMgt);
+        InOrder inOrderM = inOrder(mAwareMetricsMock);
+
+        // (1) initialize all clients
+        Messenger messenger = initOobDataPathEndPoint(true, numNdis, clientId, inOrder, inOrderM);
+        for (int i = 1; i < numNdis + 3; ++i) {
+            initOobDataPathEndPoint(false, numNdis, clientId + i, inOrder, inOrderM);
+        }
+
+        // (2) make N network requests: each unique
+        Set<String> interfaces = new HashSet<>();
+        for (int i = 0; i < numNdis + 1; ++i) {
+            byte[] pmk = new byte[32];
+            pmk[0] = (byte) i;
+
+            NetworkRequest nr = getDirectNetworkRequest(clientId + i,
+                    WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR, peerDiscoveryMac, pmk,
+                    null, i);
+
+            Message reqNetworkMsg = Message.obtain();
+            reqNetworkMsg.what = NetworkFactory.CMD_REQUEST_NETWORK;
+            reqNetworkMsg.obj = nr;
+            reqNetworkMsg.arg1 = 0;
+            messenger.send(reqNetworkMsg);
+            mMockLooper.dispatchAll();
+
+            if (i < numNdis) {
+                inOrder.verify(mMockNative).initiateDataPath(transactionId.capture(), eq(0),
+                        eq(CHANNEL_NOT_REQUESTED), anyInt(), eq(peerDiscoveryMac),
+                        ifNameCaptor.capture(), eq(pmk), eq(null), eq(true), any());
+                interfaces.add(ifNameCaptor.getValue());
+
+                mDut.onInitiateDataPathResponseSuccess(transactionId.getValue(), ndpId + i);
+                mDut.onDataPathConfirmNotification(ndpId + i, peerDataPathMac, true, 0, null);
+                mMockLooper.dispatchAll();
+
+                inOrder.verify(mMockNwMgt).setInterfaceUp(anyString());
+                inOrder.verify(mMockNwMgt).enableIpv6(anyString());
+                inOrder.verify(mMockCm).registerNetworkAgent(any(), any(), any(), any(), anyInt(),
+                        any());
+                inOrderM.verify(mAwareMetricsMock).recordNdpStatus(eq(NanStatusType.SUCCESS),
+                        eq(true), anyLong());
+                inOrderM.verify(mAwareMetricsMock).recordNdpCreation(anyInt(), any());
+            }
+        }
+
+        // verify that each interface name is unique
+        assertEquals("Number of unique interface names", numNdis, interfaces.size());
+
+        verifyNoMoreInteractions(mMockNative, mMockCm, mMockCallback, mMockSessionCallback,
+                mAwareMetricsMock, mMockNwMgt);
     }
 
     /*
@@ -563,12 +860,12 @@
         InOrder inOrderM = inOrder(mAwareMetricsMock);
 
         // (0) initialize
-        DataPathEndPointInfo res = initDataPathEndPoint(clientId, pubSubId, requestorId,
+        DataPathEndPointInfo res = initDataPathEndPoint(true, clientId, pubSubId, requestorId,
                 peerDiscoveryMac, inOrder, inOrderM, doPublish);
 
         // (1) request network
         NetworkRequest nr = getSessionNetworkRequest(clientId, res.mSessionId, res.mPeerHandle, pmk,
-                null, doPublish);
+                null, doPublish, 0);
 
         // corrupt the network specifier: reverse the role (so it's mis-matched)
         WifiAwareNetworkSpecifier ns =
@@ -603,7 +900,7 @@
                     eq(ndpId), eq(""), eq(null), eq(null), anyBoolean(), any());
         }
 
-        verifyNoMoreInteractions(mMockNative, mMockCm, mAwareMetricsMock);
+        verifyNoMoreInteractions(mMockNative, mMockCm, mAwareMetricsMock, mMockNwMgt);
     }
 
     private void testDataPathInitiatorResponderInvalidUidUtility(boolean doPublish,
@@ -619,12 +916,12 @@
         InOrder inOrderM = inOrder(mAwareMetricsMock);
 
         // (0) initialize
-        DataPathEndPointInfo res = initDataPathEndPoint(clientId, pubSubId, requestorId,
+        DataPathEndPointInfo res = initDataPathEndPoint(true, clientId, pubSubId, requestorId,
                 peerDiscoveryMac, inOrder, inOrderM, doPublish);
 
         // (1) create network request
         NetworkRequest nr = getSessionNetworkRequest(clientId, res.mSessionId, res.mPeerHandle, pmk,
-                null, doPublish);
+                null, doPublish, 0);
 
         // (2) corrupt request's UID
         WifiAwareNetworkSpecifier ns =
@@ -660,7 +957,7 @@
                     eq(ndpId), eq(""), eq(null), eq(null), anyBoolean(), any());
         }
 
-        verifyNoMoreInteractions(mMockNative, mMockCm, mAwareMetricsMock);
+        verifyNoMoreInteractions(mMockNative, mMockCm, mAwareMetricsMock, mMockNwMgt);
     }
 
     private void testDataPathInitiatorUtility(boolean useDirect, boolean provideMac,
@@ -678,7 +975,8 @@
 
         ArgumentCaptor<Messenger> messengerCaptor = ArgumentCaptor.forClass(Messenger.class);
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
-        InOrder inOrder = inOrder(mMockNative, mMockCm, mMockCallback, mMockSessionCallback);
+        InOrder inOrder = inOrder(mMockNative, mMockCm, mMockCallback, mMockSessionCallback,
+                mMockNwMgt);
         InOrder inOrderM = inOrder(mAwareMetricsMock);
 
         if (!providePmk) {
@@ -694,7 +992,7 @@
         }
 
         // (0) initialize
-        DataPathEndPointInfo res = initDataPathEndPoint(clientId, pubSubId, requestorId,
+        DataPathEndPointInfo res = initDataPathEndPoint(true, clientId, pubSubId, requestorId,
                 peerDiscoveryMac, inOrder, inOrderM, false);
 
         // (1) request network
@@ -703,11 +1001,11 @@
             nr = getDirectNetworkRequest(clientId,
                     WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR,
                     provideMac ? peerDiscoveryMac : null, providePmk ? pmk : null,
-                    providePassphrase ? passphrase : null);
+                    providePassphrase ? passphrase : null, 0);
         } else {
             nr = getSessionNetworkRequest(clientId, res.mSessionId,
                     provideMac ? res.mPeerHandle : null, providePmk ? pmk : null,
-                    providePassphrase ? passphrase : null, false);
+                    providePassphrase ? passphrase : null, false, 0);
         }
 
         Message reqNetworkMsg = Message.obtain();
@@ -737,6 +1035,8 @@
             mDut.onDataPathConfirmNotification(ndpId, peerDataPathMac, true, 0,
                     peerToken.getBytes());
             mMockLooper.dispatchAll();
+            inOrder.verify(mMockNwMgt).setInterfaceUp(anyString());
+            inOrder.verify(mMockNwMgt).enableIpv6(anyString());
             inOrder.verify(mMockCm).registerNetworkAgent(messengerCaptor.capture(), any(), any(),
                     any(), anyInt(), any());
             inOrderM.verify(mAwareMetricsMock).recordNdpStatus(eq(NanStatusType.SUCCESS),
@@ -763,16 +1063,16 @@
             Message endNetworkUsageMsg = Message.obtain();
             endNetworkUsageMsg.what = AsyncChannel.CMD_CHANNEL_DISCONNECTED;
             messengerCaptor.getValue().send(endNetworkUsageMsg);
-
-            mMockLooper.dispatchAll();
-            inOrder.verify(mMockNative).endDataPath(transactionId.capture(), eq(ndpId));
             mDut.onEndDataPathResponse(transactionId.getValue(), true, 0);
             mDut.onDataPathEndNotification(ndpId);
             mMockLooper.dispatchAll();
+
+            inOrder.verify(mMockNwMgt).setInterfaceDown(anyString());
+            inOrder.verify(mMockNative).endDataPath(transactionId.capture(), eq(ndpId));
             inOrderM.verify(mAwareMetricsMock).recordNdpSessionDuration(anyLong());
         }
 
-        verifyNoMoreInteractions(mMockNative, mMockCm, mAwareMetricsMock);
+        verifyNoMoreInteractions(mMockNative, mMockCm, mAwareMetricsMock, mMockNwMgt);
     }
 
     private void testDataPathResponderUtility(boolean useDirect, boolean provideMac,
@@ -790,7 +1090,8 @@
 
         ArgumentCaptor<Messenger> messengerCaptor = ArgumentCaptor.forClass(Messenger.class);
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
-        InOrder inOrder = inOrder(mMockNative, mMockCm, mMockCallback, mMockSessionCallback);
+        InOrder inOrder = inOrder(mMockNative, mMockCm, mMockCallback, mMockSessionCallback,
+                mMockNwMgt);
         InOrder inOrderM = inOrder(mAwareMetricsMock);
 
         if (providePmk) {
@@ -800,7 +1101,7 @@
         }
 
         // (0) initialize
-        DataPathEndPointInfo res = initDataPathEndPoint(clientId, pubSubId, requestorId,
+        DataPathEndPointInfo res = initDataPathEndPoint(true, clientId, pubSubId, requestorId,
                 peerDiscoveryMac, inOrder, inOrderM, true);
 
         // (1) request network
@@ -809,11 +1110,11 @@
             nr = getDirectNetworkRequest(clientId,
                     WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER,
                     provideMac ? peerDiscoveryMac : null, providePmk ? pmk : null,
-                    providePassphrase ? passphrase : null);
+                    providePassphrase ? passphrase : null, 0);
         } else {
             nr = getSessionNetworkRequest(clientId, res.mSessionId,
                     provideMac ? res.mPeerHandle : null, providePmk ? pmk : null,
-                    providePassphrase ? passphrase : null, true);
+                    providePassphrase ? passphrase : null, true, 0);
         }
 
         Message reqNetworkMsg = Message.obtain();
@@ -837,6 +1138,8 @@
             mDut.onDataPathConfirmNotification(ndpId, peerDataPathMac, true, 0,
                     peerToken.getBytes());
             mMockLooper.dispatchAll();
+            inOrder.verify(mMockNwMgt).setInterfaceUp(anyString());
+            inOrder.verify(mMockNwMgt).enableIpv6(anyString());
             inOrder.verify(mMockCm).registerNetworkAgent(messengerCaptor.capture(), any(), any(),
                     any(), anyInt(), any());
             inOrderM.verify(mAwareMetricsMock).recordNdpStatus(eq(NanStatusType.SUCCESS),
@@ -864,35 +1167,23 @@
             endNetworkUsageMsg.what = AsyncChannel.CMD_CHANNEL_DISCONNECTED;
             messengerCaptor.getValue().send(endNetworkUsageMsg);
 
-            mMockLooper.dispatchAll();
-            inOrder.verify(mMockNative).endDataPath(transactionId.capture(), eq(ndpId));
             mDut.onEndDataPathResponse(transactionId.getValue(), true, 0);
             mDut.onDataPathEndNotification(ndpId);
             mMockLooper.dispatchAll();
+
+            inOrder.verify(mMockNwMgt).setInterfaceDown(anyString());
+            inOrder.verify(mMockNative).endDataPath(transactionId.capture(), eq(ndpId));
             inOrderM.verify(mAwareMetricsMock).recordNdpSessionDuration(anyLong());
         }
 
-        verifyNoMoreInteractions(mMockNative, mMockCm, mAwareMetricsMock);
-    }
-
-    private void installDataPathStateManagerMocks() throws Exception {
-        Field field = WifiAwareStateManager.class.getDeclaredField("mDataPathMgr");
-        field.setAccessible(true);
-        Object mDataPathMgr = field.get(mDut);
-
-        field = WifiAwareDataPathStateManager.class.getDeclaredField("mNwService");
-        field.setAccessible(true);
-        field.set(mDataPathMgr, mMockNwMgt);
-
-        field = WifiAwareDataPathStateManager.class.getDeclaredField("mNiWrapper");
-        field.setAccessible(true);
-        field.set(mDataPathMgr, mMockNetworkInterface);
+        verifyNoMoreInteractions(mMockNative, mMockCm, mAwareMetricsMock, mMockNwMgt);
     }
 
     private NetworkRequest getSessionNetworkRequest(int clientId, int sessionId,
-            PeerHandle peerHandle, byte[] pmk, String passphrase, boolean doPublish)
+            PeerHandle peerHandle, byte[] pmk, String passphrase, boolean doPublish, int requestId)
             throws Exception {
-        final WifiAwareManager mgr = new WifiAwareManager(mMockContext, mMockAwareService);
+        final IWifiAwareManager mockAwareService = mock(IWifiAwareManager.class);
+        final WifiAwareManager mgr = new WifiAwareManager(mMockContext, mockAwareService);
         final ConfigRequest configRequest = new ConfigRequest.Builder().build();
         final PublishConfig publishConfig = new PublishConfig.Builder().build();
         final SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
@@ -910,30 +1201,33 @@
         DiscoverySessionCallback mockSessionCallback = mock(
                 DiscoverySessionCallback.class);
 
+        InOrder inOrderS = inOrder(mockAwareService, mockCallback, mockSessionCallback);
+
         mgr.attach(mMockLooperHandler, configRequest, mockCallback, null);
-        verify(mMockAwareService).connect(any(), any(),
+        inOrderS.verify(mockAwareService).connect(any(), any(),
                 clientProxyCallback.capture(), eq(configRequest), eq(false));
-        clientProxyCallback.getValue().onConnectSuccess(clientId);
+        IWifiAwareEventCallback iwaec = clientProxyCallback.getValue();
+        iwaec.onConnectSuccess(clientId);
         mMockLooper.dispatchAll();
-        verify(mockCallback).onAttached(sessionCaptor.capture());
+        inOrderS.verify(mockCallback).onAttached(sessionCaptor.capture());
         if (doPublish) {
             sessionCaptor.getValue().publish(publishConfig, mockSessionCallback,
                     mMockLooperHandler);
-            verify(mMockAwareService).publish(eq(clientId), eq(publishConfig),
+            inOrderS.verify(mockAwareService).publish(eq(clientId), eq(publishConfig),
                     sessionProxyCallback.capture());
         } else {
             sessionCaptor.getValue().subscribe(subscribeConfig, mockSessionCallback,
                     mMockLooperHandler);
-            verify(mMockAwareService).subscribe(eq(clientId), eq(subscribeConfig),
+            inOrderS.verify(mockAwareService).subscribe(eq(clientId), eq(subscribeConfig),
                     sessionProxyCallback.capture());
         }
         sessionProxyCallback.getValue().onSessionStarted(sessionId);
         mMockLooper.dispatchAll();
         if (doPublish) {
-            verify(mockSessionCallback).onPublishStarted(
+            inOrderS.verify(mockSessionCallback).onPublishStarted(
                     (PublishDiscoverySession) discoverySession.capture());
         } else {
-            verify(mockSessionCallback).onSubscribeStarted(
+            inOrderS.verify(mockSessionCallback).onSubscribeStarted(
                     (SubscribeDiscoverySession) discoverySession.capture());
         }
 
@@ -957,13 +1251,14 @@
         nc.setLinkDownstreamBandwidthKbps(1);
         nc.setSignalStrength(1);
 
-        return new NetworkRequest(nc, 0, 0, NetworkRequest.Type.NONE);
+        return new NetworkRequest(nc, 0, requestId, NetworkRequest.Type.NONE);
     }
 
     private NetworkRequest getDirectNetworkRequest(int clientId, int role, byte[] peer,
-            byte[] pmk, String passphrase) throws Exception {
+            byte[] pmk, String passphrase, int requestId) throws Exception {
+        final IWifiAwareManager mockAwareService = mock(IWifiAwareManager.class);
         final ConfigRequest configRequest = new ConfigRequest.Builder().build();
-        final WifiAwareManager mgr = new WifiAwareManager(mMockContext, mMockAwareService);
+        final WifiAwareManager mgr = new WifiAwareManager(mMockContext, mockAwareService);
 
         ArgumentCaptor<WifiAwareSession> sessionCaptor = ArgumentCaptor.forClass(
                 WifiAwareSession.class);
@@ -973,7 +1268,7 @@
         AttachCallback mockCallback = mock(AttachCallback.class);
 
         mgr.attach(mMockLooperHandler, configRequest, mockCallback, null);
-        verify(mMockAwareService).connect(any(), any(),
+        verify(mockAwareService).connect(any(), any(),
                 clientProxyCallback.capture(), eq(configRequest), eq(false));
         clientProxyCallback.getValue().onConnectSuccess(clientId);
         mMockLooper.dispatchAll();
@@ -997,64 +1292,25 @@
         nc.setLinkDownstreamBandwidthKbps(1);
         nc.setSignalStrength(1);
 
-        return new NetworkRequest(nc, 0, 0, NetworkRequest.Type.REQUEST);
+        return new NetworkRequest(nc, 0, requestId, NetworkRequest.Type.REQUEST);
     }
 
-    private DataPathEndPointInfo initDataPathEndPoint(int clientId, byte pubSubId,
-            int requestorId, byte[] peerDiscoveryMac, InOrder inOrder, InOrder inOrderM,
-            boolean doPublish)
+    private DataPathEndPointInfo initDataPathEndPoint(boolean isFirstIteration, int clientId,
+            byte pubSubId, int requestorId, byte[] peerDiscoveryMac, InOrder inOrder,
+            InOrder inOrderM, boolean doPublish)
             throws Exception {
-        final int pid = 2000;
-        final String callingPackage = "com.android.somePackage";
         final String someMsg = "some arbitrary message from peer";
-        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
         final PublishConfig publishConfig = new PublishConfig.Builder().build();
         final SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
 
-        Capabilities capabilities = new Capabilities();
-        capabilities.maxNdiInterfaces = 1;
-
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
         ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
         ArgumentCaptor<Integer> peerIdCaptor = ArgumentCaptor.forClass(Integer.class);
-        ArgumentCaptor<Messenger> messengerCaptor = ArgumentCaptor.forClass(Messenger.class);
-        ArgumentCaptor<String> strCaptor = ArgumentCaptor.forClass(String.class);
 
-        // (0) start/registrations
-        inOrder.verify(mMockCm).registerNetworkFactory(messengerCaptor.capture(),
-                strCaptor.capture());
-        collector.checkThat("factory name", "WIFI_AWARE_FACTORY", equalTo(strCaptor.getValue()));
-
-        // (1) get capabilities
-        mDut.queryCapabilities();
-        mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), capabilities);
-        mMockLooper.dispatchAll();
-
-        // (2) enable usage
-        mDut.enableUsage();
-        mMockLooper.dispatchAll();
-        inOrderM.verify(mAwareMetricsMock).recordEnableUsage();
-
-        // (3) create client & session & rx message
-        mDut.connect(clientId, Process.myUid(), pid, callingPackage, mMockCallback, configRequest,
-                false);
-        mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                eq(configRequest), eq(false), eq(true), eq(true), eq(false));
-        mDut.onConfigSuccessResponse(transactionId.getValue());
-        mMockLooper.dispatchAll();
-        inOrder.verify(mMockCallback).onConnectSuccess(clientId);
-        inOrderM.verify(mAwareMetricsMock).recordAttachSession(eq(Process.myUid()), eq(false),
-                any());
-
-        inOrder.verify(mMockNative).createAwareNetworkInterface(transactionId.capture(),
-                strCaptor.capture());
-        collector.checkThat("interface created -- 0", sAwareInterfacePrefix + 0,
-                equalTo(strCaptor.getValue()));
-        mDut.onCreateDataPathInterfaceResponse(transactionId.getValue(), true, 0);
-        mMockLooper.dispatchAll();
+        Messenger messenger = null;
+        if (isFirstIteration) {
+            messenger = initOobDataPathEndPoint(true, 1, clientId, inOrder, inOrderM);
+        }
 
         if (doPublish) {
             mDut.publish(clientId, publishConfig, mMockSessionCallback);
@@ -1084,7 +1340,72 @@
                 eq(someMsg.getBytes()));
 
         return new DataPathEndPointInfo(sessionId.getValue(), peerIdCaptor.getValue(),
-                messengerCaptor.getValue());
+                isFirstIteration ? messenger : null);
+    }
+
+    private Messenger initOobDataPathEndPoint(boolean startUpSequence, int maxNdiInterfaces,
+            int clientId, InOrder inOrder, InOrder inOrderM) throws Exception {
+        final int pid = 2000;
+        final String callingPackage = "com.android.somePackage";
+        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        ArgumentCaptor<Messenger> messengerCaptor = ArgumentCaptor.forClass(Messenger.class);
+        ArgumentCaptor<String> strCaptor = ArgumentCaptor.forClass(String.class);
+
+        Capabilities capabilities = new Capabilities();
+        capabilities.maxNdiInterfaces = maxNdiInterfaces;
+
+        if (startUpSequence) {
+            // (0) start/registrations
+            inOrder.verify(mMockCm).registerNetworkFactory(messengerCaptor.capture(),
+                    strCaptor.capture());
+            collector.checkThat("factory name", "WIFI_AWARE_FACTORY",
+                    equalTo(strCaptor.getValue()));
+
+            // (1) get capabilities
+            mDut.queryCapabilities();
+            mMockLooper.dispatchAll();
+            inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+            mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), capabilities);
+            mMockLooper.dispatchAll();
+
+            // (2) enable usage
+            mDut.enableUsage();
+            mMockLooper.dispatchAll();
+            inOrderM.verify(mAwareMetricsMock).recordEnableUsage();
+        }
+
+        // (3) create client
+        mDut.connect(clientId, Process.myUid(), pid, callingPackage, mMockCallback,
+                configRequest,
+                false);
+        mMockLooper.dispatchAll();
+
+        if (startUpSequence) {
+            inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
+                    eq(configRequest), eq(false), eq(true), eq(true), eq(false));
+            mDut.onConfigSuccessResponse(transactionId.getValue());
+            mMockLooper.dispatchAll();
+        }
+
+        inOrder.verify(mMockCallback).onConnectSuccess(clientId);
+        inOrderM.verify(mAwareMetricsMock).recordAttachSession(eq(Process.myUid()), eq(false),
+                any());
+
+        if (startUpSequence) {
+            for (int i = 0; i < maxNdiInterfaces; ++i) {
+                inOrder.verify(mMockNative).createAwareNetworkInterface(transactionId.capture(),
+                        strCaptor.capture());
+                collector.checkThat("interface created -- " + i, sAwareInterfacePrefix + i,
+                        equalTo(strCaptor.getValue()));
+                mDut.onCreateDataPathInterfaceResponse(transactionId.getValue(), true, 0);
+                mMockLooper.dispatchAll();
+            }
+            return messengerCaptor.getValue();
+        }
+
+        return null;
     }
 
     private static class DataPathEndPointInfo {
diff --git a/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareMetricsTest.java b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareMetricsTest.java
index 8cd1e10..4252cfe 100644
--- a/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareMetricsTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareMetricsTest.java
@@ -655,8 +655,7 @@
         WifiAwareDataPathStateManager.AwareNetworkRequestInformation anri =
                 new WifiAwareDataPathStateManager.AwareNetworkRequestInformation();
         anri.networkSpecifier = ns;
-        anri.state = WifiAwareDataPathStateManager.AwareNetworkRequestInformation
-                .STATE_RESPONDER_CONFIRMED;
+        anri.state = WifiAwareDataPathStateManager.AwareNetworkRequestInformation.STATE_CONFIRMED;
         anri.uid = uid;
         anri.interfaceName = interfaceName;
 
diff --git a/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareRttStateManagerTest.java b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareRttStateManagerTest.java
index 616e68c..3df62f3 100644
--- a/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareRttStateManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareRttStateManagerTest.java
@@ -74,7 +74,7 @@
         mTestLooper = new TestLooper();
         BidirectionalAsyncChannelServer server = new BidirectionalAsyncChannelServer(
                 mMockContext, mTestLooper.getLooper(), mMockHandler);
-        when(mMockRttService.getMessenger()).thenReturn(server.getMessenger());
+        when(mMockRttService.getMessenger(null, new int[1])).thenReturn(server.getMessenger());
 
         mDut.startWithRttService(mMockContext, mTestLooper.getLooper(), mMockRttService);
     }
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/LegacyPasspointConfigParserTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/LegacyPasspointConfigParserTest.java
deleted file mode 100644
index 10ebceb..0000000
--- a/tests/wifitests/src/com/android/server/wifi/hotspot2/LegacyPasspointConfigParserTest.java
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * Copyright (C) 2017 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.hotspot2;
-
-import static org.junit.Assert.*;
-
-import android.os.FileUtils;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import org.junit.Test;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Unit tests for {@link com.android.server.wifi.hotspot2.LegacyPasspointConfigParser}.
- */
-@SmallTest
-public class LegacyPasspointConfigParserTest {
-    private static final String TEST_CONFIG =
-            "tree 3:1.2(urn:wfa:mo:hotspot2dot0-perprovidersubscription:1.0)\n"
-                    + "8:MgmtTree+\n"
-                    + "17:PerProviderSubscription+\n"
-                    + "4:r1i1+\n"
-                    + "6:HomeSP+\n"
-                    + "c:FriendlyName=12:Test Provider 1™\n"
-                    + "4:FQDN=9:test1.net\n"
-                    + "13:RoamingConsortiumOI=9:1234,5678\n"
-                    + ".\n"
-                    + "a:Credential+\n"
-                    + "10:UsernamePassword+\n"
-                    + "8:Username=5:user1\n"
-                    + "8:Password=5:pass1\n"
-                    + "\n"
-                    + "9:EAPMethod+\n"
-                    + "7:EAPType=2:21\n"
-                    + "b:InnerMethod=3:PAP\n"
-                    + ".\n"
-                    + ".\n"
-                    + "5:Realm=9:test1.com\n"
-                    + ".\n"
-                    + ".\n"
-                    + ".\n"
-                    + "17:PerProviderSubscription+\n"
-                    + "4:r1i2+\n"
-                    + "6:HomeSP+\n"
-                    + "c:FriendlyName=f:Test Provider 2\n"
-                    + "4:FQDN=9:test2.net\n"
-                    + ".\n"
-                    + "a:Credential+\n"
-                    + "3:SIM+\n"
-                    + "4:IMSI=4:1234\n"
-                    + "7:EAPType=2:18\n"
-                    + ".\n"
-                    + "5:Realm=9:test2.com\n"
-                    + ".\n"
-                    + ".\n"
-                    + ".\n"
-                    + ".\n";
-
-    /**
-     * Helper function for generating {@link LegacyPasspointConfig} objects based on the predefined
-     * test configuration string {@link #TEST_CONFIG}
-     *
-     * @return Map of FQDN to {@link LegacyPasspointConfig}
-     */
-    private Map<String, LegacyPasspointConfig> generateTestConfig() {
-        Map<String, LegacyPasspointConfig> configs = new HashMap<>();
-
-        LegacyPasspointConfig config1 = new LegacyPasspointConfig();
-        config1.mFqdn = "test1.net";
-        config1.mFriendlyName = "Test Provider 1™";
-        config1.mRoamingConsortiumOis = new long[] {0x1234, 0x5678};
-        config1.mRealm = "test1.com";
-        configs.put("test1.net", config1);
-
-        LegacyPasspointConfig config2 = new LegacyPasspointConfig();
-        config2.mFqdn = "test2.net";
-        config2.mFriendlyName = "Test Provider 2";
-        config2.mRealm = "test2.com";
-        config2.mImsi = "1234";
-        configs.put("test2.net", config2);
-
-        return configs;
-    }
-
-    /**
-     * Helper function for parsing configuration data.
-     *
-     * @param data The configuration data to parse
-     * @return Map of FQDN to {@link LegacyPasspointConfig}
-     * @throws Exception
-     */
-    private Map<String, LegacyPasspointConfig> parseConfig(String data) throws Exception {
-        // Write configuration data to file.
-        File configFile = File.createTempFile("LegacyPasspointConfig", "");
-        FileUtils.stringToFile(configFile, data);
-
-        // Parse the configuration file.
-        LegacyPasspointConfigParser parser = new LegacyPasspointConfigParser();
-        Map<String, LegacyPasspointConfig> configMap =
-                parser.parseConfig(configFile.getAbsolutePath());
-
-        configFile.delete();
-        return configMap;
-    }
-
-    /**
-     * Verify that the expected {@link LegacyPasspointConfig} objects are return when parsing
-     * predefined test configuration data {@link #TEST_CONFIG}.
-     *
-     * @throws Exception
-     */
-    @Test
-    public void parseTestConfig() throws Exception {
-        Map<String, LegacyPasspointConfig> parsedConfig = parseConfig(TEST_CONFIG);
-        assertEquals(generateTestConfig(), parsedConfig);
-    }
-
-    /**
-     * Verify that an empty map is return when parsing a configuration containing an empty
-     * configuration (MgmtTree).
-     *
-     * @throws Exception
-     */
-    @Test
-    public void parseEmptyConfig() throws Exception {
-        String emptyConfig = "tree 3:1.2(urn:wfa:mo:hotspot2dot0-perprovidersubscription:1.0)\n"
-                + "8:MgmtTree+\n"
-                + ".\n";
-        Map<String, LegacyPasspointConfig> parsedConfig = parseConfig(emptyConfig);
-        assertTrue(parsedConfig.isEmpty());
-    }
-
-    /**
-     * Verify that an empty map is return when parsing an empty configuration data.
-     *
-     * @throws Exception
-     */
-    @Test
-    public void parseEmptyData() throws Exception {
-        Map<String, LegacyPasspointConfig> parsedConfig = parseConfig("");
-        assertTrue(parsedConfig.isEmpty());
-    }
-
-    /**
-     * Verify that an IOException is thrown when parsing a configuration containing an unknown
-     * root name.  The expected root name is "MgmtTree".
-     *
-     * @throws Exception
-     */
-    @Test(expected = IOException.class)
-    public void parseConfigWithUnknownRootName() throws Exception {
-        String config = "tree 3:1.2(urn:wfa:mo:hotspot2dot0-perprovidersubscription:1.0)\n"
-                + "8:TestTest+\n"
-                + ".\n";
-        parseConfig(config);
-    }
-
-    /**
-     * Verify that an IOException is thrown when parsing a configuration containing a line with
-     * mismatched string length for the name.
-     *
-     * @throws Exception
-     */
-    @Test(expected = IOException.class)
-    public void parseConfigWithMismatchedStringLengthInName() throws Exception {
-        String config = "tree 3:1.2(urn:wfa:mo:hotspot2dot0-perprovidersubscription:1.0)\n"
-                + "9:MgmtTree+\n"
-                + ".\n";
-        parseConfig(config);
-    }
-
-    /**
-     * Verify that an IOException is thrown when parsing a configuration containing a line with
-     * mismatched string length for the value.
-     *
-     * @throws Exception
-     */
-    @Test(expected = IOException.class)
-    public void parseConfigWithMismatchedStringLengthInValue() throws Exception {
-        String config = "tree 3:1.2(urn:wfa:mo:hotspot2dot0-perprovidersubscription:1.0)\n"
-                + "8:MgmtTree+\n"
-                + "4:test=5:test\n"
-                + ".\n";
-        parseConfig(config);
-    }
-}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointNetworkEvaluatorTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointNetworkEvaluatorTest.java
index 9f61ca0..7ddddd2 100644
--- a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointNetworkEvaluatorTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointNetworkEvaluatorTest.java
@@ -59,6 +59,8 @@
     private static final int TEST_NETWORK_ID = 1;
     private static final String TEST_SSID1 = "ssid1";
     private static final String TEST_SSID2 = "ssid2";
+    private static final String TEST_BSSID1 = "01:23:45:56:78:9a";
+    private static final String TEST_BSSID2 = "23:12:34:90:81:12";
     private static final String TEST_FQDN1 = "test1.com";
     private static final String TEST_FQDN2 = "test2.com";
     private static final WifiConfiguration TEST_CONFIG1 = generateWifiConfig(TEST_FQDN1);
@@ -107,14 +109,18 @@
      * @param rssiLevel The RSSI level associated with the scan
      * @return {@link ScanDetail}
      */
-    private static ScanDetail generateScanDetail(String ssid) {
+    private static ScanDetail generateScanDetail(String ssid, String bssid) {
         NetworkDetail networkDetail = mock(NetworkDetail.class);
         when(networkDetail.isInterworking()).thenReturn(true);
         when(networkDetail.getAnt()).thenReturn(NetworkDetail.Ant.FreePublic);
 
         ScanDetail scanDetail = mock(ScanDetail.class);
+        ScanResult scanResult = new ScanResult();
+        scanResult.SSID = ssid;
+        scanResult.BSSID = bssid;
         when(scanDetail.getSSID()).thenReturn(ssid);
-        when(scanDetail.getScanResult()).thenReturn(new ScanResult());
+        when(scanDetail.getBSSIDString()).thenReturn(bssid);
+        when(scanDetail.getScanResult()).thenReturn(scanResult);
         when(scanDetail.getNetworkDetail()).thenReturn(networkDetail);
         return scanDetail;
     }
@@ -138,7 +144,8 @@
     @Test
     public void evaluateScansWithNoMatch() throws Exception {
         List<ScanDetail> scanDetails = Arrays.asList(new ScanDetail[] {
-                generateScanDetail(TEST_SSID1), generateScanDetail(TEST_SSID2)});
+                generateScanDetail(TEST_SSID1, TEST_BSSID1),
+                generateScanDetail(TEST_SSID2, TEST_BSSID2)});
         List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
         when(mPasspointManager.matchProvider(any(ScanResult.class))).thenReturn(null);
         assertEquals(null, mEvaluator.evaluateNetworks(
@@ -177,7 +184,8 @@
     @Test
     public void evaluateScansWithNetworkMatchingHomeProvider() throws Exception {
         List<ScanDetail> scanDetails = Arrays.asList(new ScanDetail[] {
-                generateScanDetail(TEST_SSID1), generateScanDetail(TEST_SSID2)});
+                generateScanDetail(TEST_SSID1, TEST_BSSID1),
+                generateScanDetail(TEST_SSID2, TEST_BSSID2)});
 
         // Setup matching providers for ScanDetail with TEST_SSID1.
         Pair<PasspointProvider, PasspointMatch> homeProvider = Pair.create(
@@ -219,7 +227,8 @@
     @Test
     public void evaluateScansWithNetworkMatchingRoamingProvider() throws Exception {
         List<ScanDetail> scanDetails = Arrays.asList(new ScanDetail[] {
-                generateScanDetail(TEST_SSID1), generateScanDetail(TEST_SSID2)});
+                generateScanDetail(TEST_SSID1, TEST_BSSID1),
+                generateScanDetail(TEST_SSID2, TEST_BSSID2)});
 
         // Setup matching providers for ScanDetail with TEST_SSID1.
         Pair<PasspointProvider, PasspointMatch> roamingProvider = Pair.create(
@@ -261,7 +270,8 @@
     @Test
     public void evaluateScansWithHomeProviderNewtorkAndRoamingProviderNetwork() throws Exception {
         List<ScanDetail> scanDetails = Arrays.asList(new ScanDetail[] {
-                generateScanDetail(TEST_SSID1), generateScanDetail(TEST_SSID2)});
+                generateScanDetail(TEST_SSID1, TEST_BSSID1),
+                generateScanDetail(TEST_SSID2, TEST_BSSID2)});
 
         // Setup matching providers for ScanDetail with TEST_SSID1.
         Pair<PasspointProvider, PasspointMatch> homeProvider = Pair.create(
@@ -305,7 +315,8 @@
     @Test
     public void evaluateScansWithActiveNetworkMatchingHomeProvider() throws Exception {
         List<ScanDetail> scanDetails = Arrays.asList(new ScanDetail[] {
-                generateScanDetail(TEST_SSID1), generateScanDetail(TEST_SSID2)});
+                generateScanDetail(TEST_SSID1, TEST_BSSID1),
+                generateScanDetail(TEST_SSID2, TEST_BSSID2)});
 
         // Setup matching providers for both ScanDetail.
         Pair<PasspointProvider, PasspointMatch> homeProvider = Pair.create(
@@ -315,7 +326,7 @@
         WifiConfiguration currentNetwork = new WifiConfiguration();
         currentNetwork.networkId = TEST_NETWORK_ID;
         currentNetwork.SSID = ScanResultUtil.createQuotedSSID(TEST_SSID2);
-        String currentBssid = "12:23:34:45:12:0F";
+        String currentBssid = TEST_BSSID2;
 
         // Returning the same matching provider for both ScanDetail.
         List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
@@ -344,7 +355,7 @@
     public void evaluateScanMatchingSIMProviderWithoutSIMCard() throws Exception {
         // Setup ScanDetail and match providers.
         List<ScanDetail> scanDetails = Arrays.asList(new ScanDetail[] {
-                generateScanDetail(TEST_SSID1)});
+                generateScanDetail(TEST_SSID1, TEST_BSSID1)});
         PasspointProvider testProvider = mock(PasspointProvider.class);
         Pair<PasspointProvider, PasspointMatch> homeProvider = Pair.create(
                 testProvider, PasspointMatch.HomeProvider);
@@ -357,6 +368,45 @@
                 scanDetails, null, null, false, false, connectableNetworks));
         assertTrue(connectableNetworks.isEmpty());
         verify(testProvider, never()).getWifiConfig();
+    }
 
+    /**
+     * Verify that when the current active network is matched, the scan info associated with
+     * the network is updated.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void evaluateScansMatchingActiveNetworkWithDifferentBSS() throws Exception {
+        List<ScanDetail> scanDetails = Arrays.asList(new ScanDetail[] {
+                generateScanDetail(TEST_SSID1, TEST_BSSID2)});
+        // Setup matching provider.
+        Pair<PasspointProvider, PasspointMatch> homeProvider = Pair.create(
+                TEST_PROVIDER1, PasspointMatch.HomeProvider);
+
+        // Setup currently connected network.
+        WifiConfiguration currentNetwork = new WifiConfiguration();
+        currentNetwork.networkId = TEST_NETWORK_ID;
+        currentNetwork.SSID = ScanResultUtil.createQuotedSSID(TEST_SSID1);
+        String currentBssid = TEST_BSSID1;
+
+        // Match the current connected network to a home provider.
+        List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
+        when(mPasspointManager.matchProvider(any(ScanResult.class))).thenReturn(homeProvider);
+        WifiConfiguration config = mEvaluator.evaluateNetworks(scanDetails, currentNetwork,
+                currentBssid, true, false, connectableNetworks);
+        assertEquals(1, connectableNetworks.size());
+
+        // Verify network candidate information is updated.
+        ArgumentCaptor<ScanResult> updatedCandidateScanResult =
+                ArgumentCaptor.forClass(ScanResult.class);
+        verify(mWifiConfigManager).setNetworkCandidateScanResult(eq(TEST_NETWORK_ID),
+                updatedCandidateScanResult.capture(), anyInt());
+        assertEquals(TEST_BSSID2, updatedCandidateScanResult.getValue().BSSID);
+        ArgumentCaptor<ScanDetail> updatedCandidateScanDetail =
+                ArgumentCaptor.forClass(ScanDetail.class);
+        verify(mWifiConfigManager).updateScanDetailForNetwork(eq(TEST_NETWORK_ID),
+                updatedCandidateScanDetail.capture());
+        assertEquals(TEST_BSSID2, updatedCandidateScanDetail.getValue().getBSSIDString());
     }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/p2p/SupplicantP2pIfaceCallbackTest.java b/tests/wifitests/src/com/android/server/wifi/p2p/SupplicantP2pIfaceCallbackTest.java
index 4443bb1..8b853a4 100644
--- a/tests/wifitests/src/com/android/server/wifi/p2p/SupplicantP2pIfaceCallbackTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/p2p/SupplicantP2pIfaceCallbackTest.java
@@ -83,8 +83,8 @@
      */
     @Test
     public void testOnDeviceFound_success() throws Exception {
-        byte[] fakePrimaryDeviceTypeBytes = { 0x01, 0x02, 0x03 };
-        String fakePrimaryDeviceTypeString = "010203";
+        byte[] fakePrimaryDeviceTypeBytes = { 0x00, 0x01, 0x02, -1, 0x04, 0x05, 0x06, 0x07 };
+        String fakePrimaryDeviceTypeString = "1-02FF0405-1543";
         String fakeDeviceName = "test device name";
         short fakeConfigMethods = 0x1234;
         byte fakeCapabilities = 123;
@@ -131,8 +131,8 @@
      */
     @Test
     public void testOnDeviceFound_invalidArguments() throws Exception {
-        byte[] fakePrimaryDeviceTypeBytes = { 0x01, 0x02, 0x03 };
-        String fakePrimaryDeviceTypeString = "010203";
+        byte[] fakePrimaryDeviceTypeBytes = { 0x0, 0x01, 0x02, -1, 0x04, 0x05, 0x06, 0x07 };
+        String fakePrimaryDeviceTypeString = "1-02FF0405-1543";
         String fakeDeviceName = "test device name";
         short fakeConfigMethods = 0x1234;
         byte fakeCapabilities = 123;
diff --git a/tests/wifitests/src/com/android/server/wifi/scanner/WificondPnoScannerTest.java b/tests/wifitests/src/com/android/server/wifi/scanner/WificondPnoScannerTest.java
index c63a9a0..974cf66 100644
--- a/tests/wifitests/src/com/android/server/wifi/scanner/WificondPnoScannerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/scanner/WificondPnoScannerTest.java
@@ -429,6 +429,7 @@
         expectHwDisconnectedPnoScanStart(order, pnoSettings);
 
         // Setup scan results
+        when(mWifiNative.getPnoScanResults()).thenReturn(scanResults.getScanDetailArrayList());
         when(mWifiNative.getScanResults()).thenReturn(scanResults.getScanDetailArrayList());
 
         // Notify scan has finished
@@ -451,6 +452,7 @@
 
         order.verify(mWifiNative).scan(eq(expectedScanFreqs), any(Set.class));
 
+        when(mWifiNative.getPnoScanResults()).thenReturn(scanResults.getScanDetailArrayList());
         when(mWifiNative.getScanResults()).thenReturn(scanResults.getScanDetailArrayList());
 
         // Notify scan has finished
@@ -478,6 +480,7 @@
 
         // Setup scan results
         when(mWifiNative.getScanResults()).thenReturn(scanResults.getScanDetailArrayList());
+        when(mWifiNative.getPnoScanResults()).thenReturn(scanResults.getScanDetailArrayList());
 
         // Notify scan has finished
         mWifiMonitor.sendMessage(mWifiNative.getInterfaceName(), WifiMonitor.SCAN_RESULTS_EVENT);
diff --git a/tests/wifitests/src/com/android/server/wifi/scanner/WificondScannerTest.java b/tests/wifitests/src/com/android/server/wifi/scanner/WificondScannerTest.java
index ed7c582..d337cf1 100644
--- a/tests/wifitests/src/com/android/server/wifi/scanner/WificondScannerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/scanner/WificondScannerTest.java
@@ -32,6 +32,7 @@
 import com.android.server.wifi.ScanResults;
 import com.android.server.wifi.WifiMonitor;
 import com.android.server.wifi.WifiNative;
+import com.android.server.wifi.scanner.ChannelHelper.ChannelCollection;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -56,6 +57,39 @@
                 mLooper.getLooper(), mClock);
     }
 
+    /**
+     * Test that WificondScannerImpl will not issue a scan and report scan failure
+     * when there is no channel to scan for.
+     */
+    @Test
+    public void singleScanNotIssuedIfNoAvailableChannels() {
+        // Use mocked ChannelHelper and ChannelCollection to simulate the scenario
+        // that no channel is available for this request.
+        ChannelHelper channelHelper = mock(ChannelHelper.class);
+        ChannelCollection channelCollection = mock(ChannelCollection.class);
+        when(channelHelper.createChannelCollection()).thenReturn(channelCollection);
+        when(channelCollection.isEmpty()).thenReturn(true);
+
+        mScanner = new WificondScannerImpl(mContext, mWifiNative, mWifiMonitor,
+                channelHelper, mLooper.getLooper(), mClock);
+
+        WifiNative.ScanSettings settings = new NativeScanSettingsBuilder()
+                .withBasePeriod(10000) // ms
+                .withMaxApPerScan(10)
+                .addBucketWithBand(10000 /* ms */, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN,
+                        WifiScanner.WIFI_BAND_5_GHZ)
+                .build();
+        WifiNative.ScanEventHandler eventHandler = mock(WifiNative.ScanEventHandler.class);
+        mScanner.startSingleScan(settings, eventHandler);
+
+        mLooper.dispatchAll();
+
+        // No scan is issued to WifiNative.
+        verify(mWifiNative, never()).scan(any(), any(Set.class));
+        // A scan failed event must be reported.
+        verify(eventHandler).onScanStatus(WifiNative.WIFI_SCAN_FAILED);
+    }
+
     @Test
     public void backgroundScanSuccessSingleBucket() {
         WifiNative.ScanSettings settings = new NativeScanSettingsBuilder()
diff --git a/tests/wifitests/src/com/android/server/wifi/util/MatrixTest.java b/tests/wifitests/src/com/android/server/wifi/util/MatrixTest.java
new file mode 100644
index 0000000..bfa1071
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/util/MatrixTest.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2017 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.util;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+import java.util.Random;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.util.Matrix}.
+ */
+public class MatrixTest {
+    /**
+     * Test that both forms of constructor work
+     */
+    @Test
+    public void testConstructors() throws Exception {
+        assertEquals(new Matrix(3, 2), new Matrix(2, new double[]{0, 0, 0, 0, 0, 0}));
+    }
+
+    /**
+     * Test some degenerate cases
+     */
+    @Test
+    public void testDegenerate() throws Exception {
+        Matrix m1 = new Matrix(0, 20);
+        Matrix m2 = new Matrix(20, 0);
+        assertEquals(m1, m2.transpose());
+    }
+
+    /**
+     * Test addition
+     */
+    @Test
+    public void testAddition() throws Exception {
+        Matrix m1 = new Matrix(2, new double[]{1, 2, 3, 4});
+        Matrix m2 = new Matrix(2, new double[]{10, 20, 30, 40});
+        Matrix m3 = new Matrix(2, new double[]{11, 22, 33, 44});
+        assertEquals(m3, m1.plus(m2));
+    }
+
+    /**
+     * Test subtraction.
+     */
+    @Test
+    public void testSubtraction() throws Exception {
+        Matrix m1 = new Matrix(2, new double[]{1, 2, 3, 4});
+        Matrix m2 = new Matrix(2, new double[]{10, 20, 30, 40});
+        Matrix m3 = new Matrix(2, new double[]{11, 22, 33, 44});
+        assertEquals(m1, m3.minus(m2));
+    }
+
+    /**
+     * Test multiplication.
+     */
+    @Test
+    public void testMultiplication() throws Exception {
+        Matrix m1 = new Matrix(2, new double[]{1, 2, 3, 4});
+        Matrix m2 = new Matrix(2, new double[]{-3, 3, 7, 1});
+        Matrix m3 = new Matrix(2, new double[]{11, 5, 19, 13});
+        assertEquals(m3, m1.dot(m2));
+    }
+
+    /**
+     * Test that matrix inverse works (non-singular case).
+     */
+    @Test
+    public void testInverse() throws Exception {
+        Matrix i3 = new Matrix(3, new double[]{1, 0, 0, 0, 1, 0, 0, 0, 1});
+        Matrix f = new Matrix(3, new double[]{100, 100, 100, 100, 100, 100, 100, 100, 100});
+        Matrix m1 = new Matrix(3, new double[]{10, 1, -1, 2, 14, -1, 0, 2, 20});
+        Matrix m2 = m1.inverse();
+        Matrix m12 = m1.dot(m2);
+        Matrix m21 = m2.dot(m1);
+        // Add f here to wash out the roundoff errors
+        assertEquals(i3.plus(f), m12.plus(f));
+        assertEquals(i3.plus(f), m21.plus(f));
+    }
+
+    /**
+     * Test that attempting to invert a singular matrix throws an exception.
+     * @throws Exception
+     */
+    @Test(expected = ArithmeticException.class)
+    public void testSingularity() throws Exception {
+        Matrix m1 = new Matrix(3, new double[]{10, 1, -1, 0, 0, 0, 0, 0, 0});
+        Matrix m2 = m1.inverse();
+    }
+
+    /**
+     * Test that a copy is equal to the original, and that hash codes match,
+     * and that string versions match.
+     */
+    @Test
+    public void testCopy() throws Exception {
+        Random random = new Random();
+        Matrix m1 = new Matrix(3, 4);
+        for (int i = 0; i < m1.mem.length; i++) {
+            m1.mem[i] = random.nextDouble();
+        }
+        Matrix m2 = new Matrix(m1);
+        assertEquals(m1, m2);
+        assertEquals(m1.hashCode(), m2.hashCode());
+        assertEquals(m1.toString(), m2.toString());
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/util/NativeUtilTest.java b/tests/wifitests/src/com/android/server/wifi/util/NativeUtilTest.java
index 3f51c5a..367e6b3 100644
--- a/tests/wifitests/src/com/android/server/wifi/util/NativeUtilTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/util/NativeUtilTest.java
@@ -98,10 +98,25 @@
     }
 
     /**
+     * Test that conversion of ssid bytes to quoted UTF8 string ssid works.
+     */
+    @Test
+    public void testUtf8SsidDecode() throws Exception {
+        assertEquals(
+                new ArrayList<>(
+                        Arrays.asList((byte) 0x41, (byte) 0x6e, (byte) 0x64, (byte) 0x72,
+                                (byte) 0x6f, (byte) 0x69, (byte) 0x64, (byte) 0x41, (byte) 0x50,
+                                (byte) 0xe3, (byte) 0x81, (byte) 0x8f, (byte) 0xe3, (byte) 0x81,
+                                (byte) 0xa0, (byte) 0xe3, (byte) 0x81, (byte) 0x95, (byte) 0xe3,
+                                (byte) 0x81, (byte) 0x84)),
+                NativeUtil.decodeSsid("\"AndroidAPください\""));
+    }
+
+    /**
      * Test that conversion of ssid bytes to hex string ssid works.
      */
     @Test
-    public void testNonAsciiSsidDecode() throws Exception {
+    public void testNonSsidDecode() throws Exception {
         assertEquals(
                 new ArrayList<>(
                         Arrays.asList((byte) 0xf5, (byte) 0xe4, (byte) 0xab, (byte) 0x78,
@@ -110,10 +125,10 @@
     }
 
     /**
-     * Test that conversion of ssid bytes to quoted ASCII string ssid works.
+     * Test that conversion of ssid bytes to quoted string ssid works.
      */
     @Test
-    public void testAsciiSsidEncode() throws Exception {
+    public void testSsidEncode() throws Exception {
         assertEquals(
                 "\"ssid_test123\"",
                 NativeUtil.encodeSsid(new ArrayList<>(
@@ -126,7 +141,7 @@
      * Test that conversion of byte array to hex string works when the byte array contains 0.
      */
     @Test
-    public void testNullCharInAsciiSsidEncode() throws Exception {
+    public void testNullCharInSsidEncode() throws Exception {
         assertEquals(
                 "007369645f74657374313233",
                 NativeUtil.encodeSsid(new ArrayList<>(
@@ -139,7 +154,7 @@
      * Test that conversion of byte array to hex string ssid works.
      */
     @Test
-    public void testNonAsciiSsidEncode() throws Exception {
+    public void testNonSsidEncode() throws Exception {
         assertEquals(
                 "f5e4ab78ab3432439a",
                 NativeUtil.encodeSsid(new ArrayList<>(
diff --git a/tests/wifitests/src/com/android/server/wifi/util/WifiPermissionsUtilTest.java b/tests/wifitests/src/com/android/server/wifi/util/WifiPermissionsUtilTest.java
index 308e267..7f5a66d 100644
--- a/tests/wifitests/src/com/android/server/wifi/util/WifiPermissionsUtilTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/util/WifiPermissionsUtilTest.java
@@ -19,6 +19,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.doAnswer;
@@ -27,12 +28,15 @@
 
 import android.Manifest;
 import android.app.AppOpsManager;
+import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.net.NetworkScoreManager;
+import android.net.NetworkScorerAppData;
+import android.net.wifi.WifiConfiguration;
 import android.os.Build;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -41,6 +45,7 @@
 
 import com.android.server.wifi.BinderUtil;
 import com.android.server.wifi.FakeWifiLog;
+import com.android.server.wifi.FrameworkFacade;
 import com.android.server.wifi.WifiInjector;
 import com.android.server.wifi.WifiSettingsStore;
 
@@ -72,8 +77,10 @@
     @Mock private UserManager mMockUserManager;
     @Mock private WifiSettingsStore mMockWifiSettingsStore;
     @Mock private ContentResolver mMockContentResolver;
-    @Mock private NetworkScoreManager mNetworkScoreManager;
-    @Mock private WifiInjector mWifiInjector;
+    @Mock private NetworkScoreManager mMockNetworkScoreManager;
+    @Mock private WifiInjector mMockWifiInjector;
+    @Mock private FrameworkFacade mMockFrameworkFacade;
+    @Mock private WifiConfiguration mMockWifiConfig;
     @Spy private FakeWifiLog mWifiLog;
 
     private static final String TEST_PACKAGE_NAME = "com.google.somePackage";
@@ -101,6 +108,10 @@
     private boolean mActiveNwScorer;
     private Answer<Integer> mReturnPermission;
     private HashMap<String, Integer> mPermissionsList = new HashMap<String, Integer>();
+    private String mUseOpenWifiPackage;
+    private NetworkScorerAppData mNetworkScorerAppData;
+    private boolean mGetActiveScorerThrowsSecurityException;
+    private boolean mConfigIsOpen;
 
     /**
     * Set up Mockito tests
@@ -124,8 +135,8 @@
         mUid = MANAGED_PROFILE_UID;  // do not really care about this value
         setupTestCase();
         WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
-                mWifiInjector);
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
+                mMockWifiInjector);
         when(mMockPermissionsWrapper.getOverrideWifiConfigPermission(anyInt()))
                 .thenReturn(PackageManager.PERMISSION_GRANTED);
         assertTrue(codeUnderTest.checkConfigOverridePermission(mUid));
@@ -139,8 +150,8 @@
         mUid = OTHER_USER_UID;  // do not really care about this value
         setupTestCase();
         WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
-                mWifiInjector);
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
+                mMockWifiInjector);
         when(mMockPermissionsWrapper.getOverrideWifiConfigPermission(anyInt()))
                 .thenReturn(PackageManager.PERMISSION_DENIED);
         assertFalse(codeUnderTest.checkConfigOverridePermission(mUid));
@@ -154,8 +165,8 @@
         mUid = OTHER_USER_UID;  // do not really care about this value
         setupTestCase();
         WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
-                mWifiInjector);
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
+                mMockWifiInjector);
         doThrow(new RemoteException("Failed to check permissions for " + mUid))
                 .when(mMockPermissionsWrapper).getOverrideWifiConfigPermission(mUid);
         assertFalse(codeUnderTest.checkConfigOverridePermission(mUid));
@@ -179,8 +190,8 @@
         mCurrentUser = UserHandle.USER_CURRENT_OR_SELF;
         setupTestCase();
         WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
-                mWifiInjector);
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
+                mMockWifiInjector);
         try {
             output = codeUnderTest.canAccessScanResults(TEST_PACKAGE_NAME, mUid, mTargetVersion);
         } catch (SecurityException e) {
@@ -207,8 +218,8 @@
         mMockUserInfo.id = mCallingUser;
         setupTestCase();
         WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
-                mWifiInjector);
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
+                mMockWifiInjector);
         try {
             output = codeUnderTest.canAccessScanResults(TEST_PACKAGE_NAME, mUid, mTargetVersion);
         } catch (SecurityException e) {
@@ -230,8 +241,8 @@
         mPermissionsList.put(mMacAddressPermission, mUid);
         setupTestCase();
         WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
-                mWifiInjector);
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
+                mMockWifiInjector);
         try {
             output = codeUnderTest.canAccessScanResults(TEST_PACKAGE_NAME, mUid, mTargetVersion);
         } catch (SecurityException e) {
@@ -259,8 +270,8 @@
         mPermissionsList.put(mInteractAcrossUsersFullPermission, mUid);
         setupTestCase();
         WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
-                mWifiInjector);
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
+                mMockWifiInjector);
         try {
             output = codeUnderTest.canAccessScanResults(TEST_PACKAGE_NAME, mUid, mTargetVersion);
         } catch (SecurityException e) {
@@ -286,8 +297,8 @@
         mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
         setupTestCase();
         WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
-                mWifiInjector);
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
+                mMockWifiInjector);
         try {
             output = codeUnderTest.canAccessScanResults(TEST_PACKAGE_NAME, mUid, mTargetVersion);
         } catch (SecurityException e) {
@@ -312,8 +323,8 @@
         mCurrentUser = UserHandle.USER_CURRENT_OR_SELF;
         setupTestCase();
         WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
-                mWifiInjector);
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
+                mMockWifiInjector);
         try {
             output = codeUnderTest.canAccessScanResults(TEST_PACKAGE_NAME, mUid, mTargetVersion);
         } catch (SecurityException e) {
@@ -323,6 +334,354 @@
     }
 
     /**
+     * Test case setting: Package is valid because it matches the USE_OPEN_WIFI_PACKAGE.
+     *                    User is current
+     *                    The current config is for an open network.
+     * Validate result is true
+     */
+    @Test
+    public void testCanAccessFullConnectionInfo_PackageIsUseOpenWifiPackage() throws Exception {
+        final boolean output;
+        mThrowSecurityException = false;
+        mCurrentUser = UserHandle.USER_CURRENT_OR_SELF;
+        mUseOpenWifiPackage = TEST_PACKAGE_NAME;
+        ComponentName useOpenWifiComponent = new ComponentName(TEST_PACKAGE_NAME, "TestClass");
+        mNetworkScorerAppData = new NetworkScorerAppData(0 /*packageUid*/,
+                null /*recommendationServiceComp*/, null /*recommendationServiceLabel*/,
+                useOpenWifiComponent, null /*networkAvailableNotificationChannelId*/);
+        setupTestCase();
+        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
+                mMockWifiInjector);
+
+        output = codeUnderTest.canAccessFullConnectionInfo(mMockWifiConfig, TEST_PACKAGE_NAME,
+                mUid, mTargetVersion);
+
+        assertEquals(true, output);
+    }
+
+    /**
+     * Test case setting: Package is valid because the caller has access to scan results.
+     *                    Location mode is ON
+     *                    User is current
+     *                    The current config is not for an open network.
+     * Validate result is true
+     */
+    @Test
+    public void testCanAccessFullConnectionInfo_HasAccessToScanResults() throws Exception {
+        final boolean output;
+        mThrowSecurityException = false;
+        mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.GINGERBREAD;
+        mLocationModeSetting = Settings.Secure.LOCATION_MODE_HIGH_ACCURACY;
+        mCoarseLocationPermission = PackageManager.PERMISSION_GRANTED;
+        mAllowCoarseLocationApps = AppOpsManager.MODE_ALLOWED;
+        mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
+        mCurrentUser = UserHandle.USER_CURRENT_OR_SELF;
+        mConfigIsOpen = false;
+
+        setupTestCase();
+        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
+                mMockWifiInjector);
+
+        output = codeUnderTest.canAccessFullConnectionInfo(mMockWifiConfig, TEST_PACKAGE_NAME,
+                mUid, mTargetVersion);
+
+        assertEquals(true, output);
+    }
+
+    /**
+     * Test case setting: Package is valid because it matches the USE_OPEN_WIFI_PACKAGE.
+     *                    User or profile is not current but the uid has
+     *                    permission to INTERACT_ACROSS_USERS_FULL
+     *                    The current config is for an open network.
+     * Validate result is true
+     */
+    @Test
+    public void testCanAccessFullConnectionInfo_UserNotCurrentButHasInteractAcrossUsers()
+            throws Exception {
+        final boolean output;
+        mThrowSecurityException = false;
+        mUid = MANAGED_PROFILE_UID;
+        mPermissionsList.put(mInteractAcrossUsersFullPermission, mUid);
+        mUseOpenWifiPackage = TEST_PACKAGE_NAME;
+        ComponentName useOpenWifiComponent = new ComponentName(TEST_PACKAGE_NAME, "TestClass");
+        mNetworkScorerAppData = new NetworkScorerAppData(0 /*packageUid*/,
+                null /*recommendationServiceComp*/, null /*recommendationServiceLabel*/,
+                useOpenWifiComponent, null /*networkAvailableNotificationChannelId*/);
+        setupTestCase();
+        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
+                mMockWifiInjector);
+
+        output = codeUnderTest.canAccessFullConnectionInfo(mMockWifiConfig, TEST_PACKAGE_NAME,
+                mUid, mTargetVersion);
+
+        assertEquals(true, output);
+    }
+
+    /**
+     * Test case setting: Package is valid because it matches the USE_OPEN_WIFI_PACKAGE.
+     *                    User or profile is NOT current
+     *                    INTERACT_ACROSS_USERS_FULL NOT granted
+     *                    The current config is for an open network.
+     * Validate result is false
+     */
+    @Test
+    public void testCanAccessFullConnectionInfo_UserNotCurrentNoInteractAcrossUsers()
+            throws Exception {
+        final boolean output;
+        mThrowSecurityException = false;
+        mUid = MANAGED_PROFILE_UID;
+        mUseOpenWifiPackage = TEST_PACKAGE_NAME;
+        setupTestCase();
+        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
+                mMockWifiInjector);
+
+        output = codeUnderTest.canAccessFullConnectionInfo(mMockWifiConfig, TEST_PACKAGE_NAME,
+                mUid, mTargetVersion);
+
+        assertEquals(false, output);
+    }
+
+    /**
+     * Test case setting: Package is valid because it matches the USE_OPEN_WIFI_PACKAGE.
+     *                    User is current
+     *                    The current config is NULL.
+     * Validate result is false
+     */
+    @Test
+    public void testCanAccessFullConnectionInfo_WiFiConfigIsNull() throws Exception {
+        final boolean output;
+        mThrowSecurityException = false;
+        mCurrentUser = UserHandle.USER_CURRENT_OR_SELF;
+        mUseOpenWifiPackage = TEST_PACKAGE_NAME;
+        ComponentName useOpenWifiComponent = new ComponentName(TEST_PACKAGE_NAME, "TestClass");
+        mNetworkScorerAppData = new NetworkScorerAppData(0 /*packageUid*/,
+                null /*recommendationServiceComp*/, null /*recommendationServiceLabel*/,
+                useOpenWifiComponent, null /*networkAvailableNotificationChannelId*/);
+        setupTestCase();
+        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
+                mMockWifiInjector);
+
+        output = codeUnderTest.canAccessFullConnectionInfo(null /*config*/, TEST_PACKAGE_NAME,
+                mUid, mTargetVersion);
+
+        assertEquals(false, output);
+    }
+
+    /**
+     * Test case setting: Package is valid because it matches the USE_OPEN_WIFI_PACKAGE.
+     *                    User is current
+     *                    The current config is not for an open network.
+     * Validate result is false
+     */
+    @Test
+    public void testCanAccessFullConnectionInfo_WiFiConfigIsNotOpen() throws Exception {
+        final boolean output;
+        mThrowSecurityException = false;
+        mCurrentUser = UserHandle.USER_CURRENT_OR_SELF;
+        mUseOpenWifiPackage = TEST_PACKAGE_NAME;
+        ComponentName useOpenWifiComponent = new ComponentName(TEST_PACKAGE_NAME, "TestClass");
+        mNetworkScorerAppData = new NetworkScorerAppData(0 /*packageUid*/,
+                null /*recommendationServiceComp*/, null /*recommendationServiceLabel*/,
+                useOpenWifiComponent, null /*networkAvailableNotificationChannelId*/);
+        mConfigIsOpen = false;
+        setupTestCase();
+        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
+                mMockWifiInjector);
+
+        output = codeUnderTest.canAccessFullConnectionInfo(mMockWifiConfig, TEST_PACKAGE_NAME,
+                mUid, mTargetVersion);
+
+        assertEquals(false, output);
+    }
+
+    /**
+     * Test case setting: Package is valid because it matches the USE_OPEN_WIFI_PACKAGE.
+     *                    User is current
+     *                    The current config is for an open network.
+     *                    There is no active scorer
+     * Validate result is false
+     */
+    @Test
+    public void testCanAccessFullConnectionInfo_UseOpenWifiPackageIsSetButNoActiveScorer()
+            throws Exception {
+        final boolean output;
+        mThrowSecurityException = false;
+        mCurrentUser = UserHandle.USER_CURRENT_OR_SELF;
+        mUseOpenWifiPackage = TEST_PACKAGE_NAME;
+        mNetworkScorerAppData = null; // getActiveScorer() will return null
+        setupTestCase();
+        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
+                mMockWifiInjector);
+
+        output = codeUnderTest.canAccessFullConnectionInfo(mMockWifiConfig, TEST_PACKAGE_NAME,
+                mUid, mTargetVersion);
+
+        assertEquals(false, output);
+    }
+
+    /**
+     * Test case setting: Package is valid because it matches the USE_OPEN_WIFI_PACKAGE.
+     *                    User is current
+     *                    The current config is for an open network.
+     *                    The scorer is active but the useOpenWiFi component name doesn't match
+     *                    the provided package.
+     * Validate result is false
+     */
+    @Test
+    public void testCanAccessFullConnectionInfo_MismatchBetweenUseOpenWifiPackages()
+            throws Exception {
+        final boolean output;
+        mThrowSecurityException = false;
+        mCurrentUser = UserHandle.USER_CURRENT_OR_SELF;
+        mUseOpenWifiPackage = TEST_PACKAGE_NAME;
+        ComponentName useOpenWifiComponent =
+                new ComponentName(mUseOpenWifiPackage + ".nomatch", "TestClass");
+        mNetworkScorerAppData = new NetworkScorerAppData(0 /*packageUid*/,
+                null /*recommendationServiceComp*/, null /*recommendationServiceLabel*/,
+                useOpenWifiComponent, null /*networkAvailableNotificationChannelId*/);
+        setupTestCase();
+        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
+                mMockWifiInjector);
+
+        output = codeUnderTest.canAccessFullConnectionInfo(mMockWifiConfig, TEST_PACKAGE_NAME,
+                mUid, mTargetVersion);
+
+        assertEquals(false, output);
+    }
+
+    /**
+     * Test case setting: Package is valid because it matches the USE_OPEN_WIFI_PACKAGE.
+     *                    User is current
+     *                    The current config is for an open network.
+     *                    The scorer is active but the useOpenWiFi component name is null.
+     * Validate result is false
+     */
+    @Test
+    public void testCanAccessFullConnectionInfo_UseOpenWifiPackageFromScorerIsNull()
+            throws Exception {
+        final boolean output;
+        mThrowSecurityException = false;
+        mCurrentUser = UserHandle.USER_CURRENT_OR_SELF;
+        mUseOpenWifiPackage = TEST_PACKAGE_NAME;
+        mNetworkScorerAppData = new NetworkScorerAppData(0 /*packageUid*/,
+                null /*recommendationServiceComp*/, null /*recommendationServiceLabel*/,
+                null /*useOpenWifiComponent*/, null /*networkAvailableNotificationChannelId*/);
+        setupTestCase();
+        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
+                mMockWifiInjector);
+
+        output = codeUnderTest.canAccessFullConnectionInfo(mMockWifiConfig, TEST_PACKAGE_NAME,
+                mUid, mTargetVersion);
+
+        assertEquals(false, output);
+    }
+
+    /**
+     * Test case setting: Package is invalid because USE_OPEN_WIFI_PACKAGE is an empty string.
+     *                    Location mode is ON
+     *                    User is current
+     *                    The current config is for an open network.
+     * Validate result is false
+     */
+    @Test
+    public void testCanAccessFullConnectionInfo_UseOpenWifiPackageIsEmpty() throws Exception {
+        final boolean output;
+        mThrowSecurityException = false;
+        mLocationModeSetting = Settings.Secure.LOCATION_MODE_HIGH_ACCURACY;
+        mCurrentUser = UserHandle.USER_CURRENT_OR_SELF;
+        mUseOpenWifiPackage = "";
+        setupTestCase();
+        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
+                mMockWifiInjector);
+
+        output = codeUnderTest.canAccessFullConnectionInfo(mMockWifiConfig, TEST_PACKAGE_NAME,
+                mUid, mTargetVersion);
+
+        assertEquals(false, output);
+    }
+
+    /**
+     * Test case setting: Package is invalid because it does not match the USE_OPEN_WIFI_PACKAGE.
+     *                    User is current
+     *                    The current config is for an open network.
+     * Validate result is false
+     */
+    @Test
+    public void testCanAccessFullConnectionInfo_DoesNotMatchUseOpenWifiPackage() throws Exception {
+        final boolean output;
+        mThrowSecurityException = false;
+        mCurrentUser = UserHandle.USER_CURRENT_OR_SELF;
+        mUseOpenWifiPackage = TEST_PACKAGE_NAME + ".nomatch";
+        setupTestCase();
+        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
+                mMockWifiInjector);
+
+        output = codeUnderTest.canAccessFullConnectionInfo(mMockWifiConfig, TEST_PACKAGE_NAME,
+                mUid, mTargetVersion);
+
+        assertEquals(false, output);
+    }
+
+    /**
+     * Test case setting: The caller is invalid because its UID does not match the provided package.
+     *
+     * Validate a SecurityException is thrown.
+     */
+    @Test
+    public void testCanAccessFullConnectionInfo_UidPackageCheckFails() throws Exception {
+        mThrowSecurityException = true;
+        setupTestCase();
+        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
+                mMockWifiInjector);
+
+        try {
+            codeUnderTest.canAccessFullConnectionInfo(mMockWifiConfig, TEST_PACKAGE_NAME, mUid,
+                    mTargetVersion);
+            fail("SecurityException not thrown.");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    /**
+     * Test case setting: The getActiveScorer() call fails with a SecurityException.
+     *
+     * Validate a SecurityException is thrown.
+     */
+    @Test
+    public void testCanAccessFullConnectionInfo_GetActiveScorerFails() throws Exception {
+        mThrowSecurityException = false;
+        mGetActiveScorerThrowsSecurityException = true;
+        mLocationModeSetting = Settings.Secure.LOCATION_MODE_HIGH_ACCURACY;
+        mCurrentUser = UserHandle.USER_CURRENT_OR_SELF;
+        mUseOpenWifiPackage = TEST_PACKAGE_NAME;
+        setupTestCase();
+        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
+                mMockWifiInjector);
+
+        try {
+            codeUnderTest.canAccessFullConnectionInfo(mMockWifiConfig, TEST_PACKAGE_NAME, mUid,
+                    mTargetVersion);
+            fail("SecurityException not thrown.");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    /**
      * Test case Setting: Package is valid
      *                    Legacy App
      *                    Foreground
@@ -341,8 +700,8 @@
         mCurrentUser = UserHandle.USER_CURRENT_OR_SELF;
         setupTestCase();
         WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
-                mWifiInjector);
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
+                mMockWifiInjector);
         try {
             output = codeUnderTest.canAccessScanResults(TEST_PACKAGE_NAME, mUid, mTargetVersion);
         } catch (SecurityException e) {
@@ -373,8 +732,8 @@
         mMockUserInfo.id = mCallingUser;
         setupTestCase();
         WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
-                mWifiInjector);
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
+                mMockWifiInjector);
         try {
             output = codeUnderTest.canAccessScanResults(TEST_PACKAGE_NAME, mUid, mTargetVersion);
         } catch (SecurityException e) {
@@ -400,8 +759,8 @@
         mLocationModeSetting = Settings.Secure.LOCATION_MODE_HIGH_ACCURACY;
         setupTestCase();
         WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
-                mWifiInjector);
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
+                mMockWifiInjector);
         try {
             output = codeUnderTest.canAccessScanResults(TEST_PACKAGE_NAME, mUid, mTargetVersion);
         } catch (SecurityException e) {
@@ -419,8 +778,8 @@
         boolean output = false;
         setupTestCase();
         WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
-                mWifiInjector);
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
+                mMockWifiInjector);
         try {
             output = codeUnderTest.canAccessScanResults(TEST_PACKAGE_NAME, mUid, mTargetVersion);
         } catch (SecurityException e) {
@@ -444,8 +803,8 @@
         mMockUserInfo.id = mCallingUser;
         setupTestCase();
         WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
-                mWifiInjector);
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
+                mMockWifiInjector);
         codeUnderTest.enforceLocationPermission(TEST_PACKAGE_NAME, mUid);
     }
 
@@ -457,8 +816,8 @@
     public void testEnforceLocationPermissionExpectSecurityException() throws Exception {
         setupTestCase();
         WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
-                mWifiInjector);
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
+                mMockWifiInjector);
         codeUnderTest.enforceLocationPermission(TEST_PACKAGE_NAME, mUid);
     }
 
@@ -500,7 +859,15 @@
         when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
         when(mMockContext.getSystemService(Context.USER_SERVICE))
             .thenReturn(mMockUserManager);
-        when(mWifiInjector.makeLog(anyString())).thenReturn(mWifiLog);
+        when(mMockWifiInjector.makeLog(anyString())).thenReturn(mWifiLog);
+        when(mMockWifiInjector.getFrameworkFacade()).thenReturn(mMockFrameworkFacade);
+        if (mGetActiveScorerThrowsSecurityException) {
+            when(mMockNetworkScoreManager.getActiveScorer()).thenThrow(
+                    new SecurityException("Caller is neither the system process nor a "
+                            + "score requester."));
+        } else {
+            when(mMockNetworkScoreManager.getActiveScorer()).thenReturn(mNetworkScorerAppData);
+        }
     }
 
     private void initTestVars() {
@@ -518,6 +885,10 @@
         mCoarseLocationPermission = PackageManager.PERMISSION_DENIED;
         mAllowCoarseLocationApps = AppOpsManager.MODE_ERRORED;
         mActiveNwScorer = false;
+        mUseOpenWifiPackage = null;
+        mNetworkScorerAppData = null;
+        mGetActiveScorerThrowsSecurityException = false;
+        mConfigIsOpen = true;
     }
 
     private void setupMockInterface() {
@@ -528,11 +899,14 @@
                         anyString(), anyInt());
         when(mMockPermissionsWrapper.getCallingUserId(mUid)).thenReturn(mCallingUser);
         when(mMockPermissionsWrapper.getCurrentUser()).thenReturn(mCurrentUser);
-        when(mNetworkScoreManager.isCallerActiveScorer(mUid)).thenReturn(mActiveNwScorer);
+        when(mMockNetworkScoreManager.isCallerActiveScorer(mUid)).thenReturn(mActiveNwScorer);
         when(mMockPermissionsWrapper.getUidPermission(mManifestStringCoarse, mUid))
             .thenReturn(mCoarseLocationPermission);
         when(mMockWifiSettingsStore.getLocationModeSetting(mMockContext))
             .thenReturn(mLocationModeSetting);
         when(mMockPermissionsWrapper.getTopPkgName()).thenReturn(mPkgNameOfTopActivity);
+        when(mMockFrameworkFacade.getStringSetting(mMockContext,
+                Settings.Global.USE_OPEN_WIFI_PACKAGE)).thenReturn(mUseOpenWifiPackage);
+        when(mMockWifiConfig.isOpenNetwork()).thenReturn(mConfigIsOpen);
     }
 }