Merge "Check roaming network for Fast Re-Authentication"
am: c707c6d7fb  -s ours

Change-Id: Ia8dddaa6dc26416e725a358d2a2c380d9382f237
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..ca79125
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,18 @@
+// 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.
+
+subdirs = [
+    "libwifi_system",
+    "libwifi_system_iface",
+]
diff --git a/libwifi_system/Android.bp b/libwifi_system/Android.bp
new file mode 100644
index 0000000..877d05f
--- /dev/null
+++ b/libwifi_system/Android.bp
@@ -0,0 +1,76 @@
+// 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.
+
+cc_defaults {
+    name: "libwifi-system-defaults",
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wextra",
+        "-Winit-self",
+        "-Wno-unused-function",
+        "-Wno-unused-parameter",
+        "-Wshadow",
+        "-Wunused-variable",
+        "-Wwrite-strings",
+    ],
+}
+
+// Device independent wifi system logic.
+// ============================================================
+cc_library_shared {
+    name: "libwifi-system",
+    defaults: ["libwifi-system-defaults"],
+    export_include_dirs: ["include"],
+    export_shared_lib_headers: ["libbase"],
+    shared_libs: [
+        "libbase",
+        "libcrypto",
+        "libcutils",
+    ],
+    srcs: [
+        "hostapd_manager.cpp",
+        "supplicant_manager.cpp",
+    ],
+}
+
+// Test utilities (e.g. mock classes) for libwifi-system
+// ============================================================
+cc_library_static {
+    name: "libwifi-system-test",
+    defaults: ["libwifi-system-defaults"],
+    static_libs: ["libgmock"],
+    export_include_dirs: [
+        "include",
+        "testlib/include",
+    ],
+}
+
+// Unit tests for libwifi-system
+// ============================================================
+cc_test {
+    name: "libwifi-system_tests",
+    defaults: ["libwifi-system-defaults"],
+    srcs: [
+        "tests/main.cpp",
+        "tests/hostapd_manager_unittest.cpp",
+    ],
+    static_libs: [
+        "libgmock",
+    ],
+    shared_libs: [
+        "libbase",
+        "libwifi-system",
+    ],
+}
diff --git a/libwifi_system/Android.mk b/libwifi_system/Android.mk
deleted file mode 100644
index 5541867..0000000
--- a/libwifi_system/Android.mk
+++ /dev/null
@@ -1,76 +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.
-
-LOCAL_PATH := $(call my-dir)
-
-wifi_system_cflags := \
-    -Wall \
-    -Werror \
-    -Wextra \
-    -Winit-self \
-    -Wno-unused-function \
-    -Wno-unused-parameter \
-    -Wshadow \
-    -Wunused-variable \
-    -Wwrite-strings
-
-# Device independent wifi system logic.
-# ============================================================
-include $(CLEAR_VARS)
-LOCAL_MODULE := libwifi-system
-LOCAL_CFLAGS := $(wifi_system_cflags)
-LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
-LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
-LOCAL_EXPORT_SHARED_LIBRARY_HEADERS := libbase
-LOCAL_SHARED_LIBRARIES := \
-    libbase \
-    libcrypto \
-    libcutils
-
-LOCAL_SRC_FILES := \
-    hostapd_manager.cpp \
-    interface_tool.cpp \
-    supplicant_manager.cpp
-include $(BUILD_SHARED_LIBRARY)
-
-# Test utilities (e.g. mock classes) for libwifi-system
-# ============================================================
-include $(CLEAR_VARS)
-LOCAL_MODULE := libwifi-system-test
-LOCAL_CFLAGS := $(wifi_system_cflags)
-LOCAL_C_INCLUDES := \
-    $(LOCAL_PATH)/include \
-    $(LOCAL_PATH)/testlib/include
-LOCAL_STATIC_LIBRARIES := libgmock
-LOCAL_EXPORT_C_INCLUDE_DIRS := \
-    $(LOCAL_PATH)/include \
-    $(LOCAL_PATH)/testlib/include
-include $(BUILD_STATIC_LIBRARY)
-
-
-# Unit tests for libwifi-system
-# ============================================================
-include $(CLEAR_VARS)
-LOCAL_MODULE := libwifi-system_tests
-LOCAL_CPPFLAGS := $(wificond_cpp_flags)
-LOCAL_SRC_FILES := \
-    tests/main.cpp \
-    tests/hostapd_manager_unittest.cpp
-LOCAL_STATIC_LIBRARIES := \
-    libgmock \
-    libgtest
-LOCAL_SHARED_LIBRARIES := \
-    libbase \
-    libwifi-system
-include $(BUILD_NATIVE_TEST)
diff --git a/libwifi_system/testlib/include/wifi_system_test/mock_hal_tool.h b/libwifi_system/testlib/include/wifi_system_test/mock_hal_tool.h
index 312428f..eb93e26 100644
--- a/libwifi_system/testlib/include/wifi_system_test/mock_hal_tool.h
+++ b/libwifi_system/testlib/include/wifi_system_test/mock_hal_tool.h
@@ -17,6 +17,7 @@
 #ifndef ANDROID_WIFI_SYSTEM_TEST_MOCK_HAL_TOOL_H
 #define ANDROID_WIFI_SYSTEM_TEST_MOCK_HAL_TOOL_H
 
+#include <gmock/gmock.h>
 #include <wifi_system/hal_tool.h>
 
 namespace android {
diff --git a/libwifi_system/testlib/include/wifi_system_test/mock_hostapd_manager.h b/libwifi_system/testlib/include/wifi_system_test/mock_hostapd_manager.h
index 6dbeafe..94ed41b 100644
--- a/libwifi_system/testlib/include/wifi_system_test/mock_hostapd_manager.h
+++ b/libwifi_system/testlib/include/wifi_system_test/mock_hostapd_manager.h
@@ -17,6 +17,7 @@
 #ifndef ANDROID_WIFI_SYSTEM_TEST_MOCK_HOSTAPD_MANAGER_H
 #define ANDROID_WIFI_SYSTEM_TEST_MOCK_HOSTAPD_MANAGER_H
 
+#include <gmock/gmock.h>
 #include <wifi_system/hostapd_manager.h>
 
 namespace android {
diff --git a/libwifi_system/testlib/include/wifi_system_test/mock_supplicant_manager.h b/libwifi_system/testlib/include/wifi_system_test/mock_supplicant_manager.h
index 01d604f..d55012e 100644
--- a/libwifi_system/testlib/include/wifi_system_test/mock_supplicant_manager.h
+++ b/libwifi_system/testlib/include/wifi_system_test/mock_supplicant_manager.h
@@ -17,6 +17,7 @@
 #ifndef ANDROID_WIFI_SYSTEM_TEST_MOCK_SUPPLICANT_MANAGER_H
 #define ANDROID_WIFI_SYSTEM_TEST_MOCK_SUPPLICANT_MANAGER_H
 
+#include <gmock/gmock.h>
 #include <wifi_system/supplicant_manager.h>
 
 namespace android {
diff --git a/libwifi_system_iface/Android.bp b/libwifi_system_iface/Android.bp
new file mode 100644
index 0000000..4be0aa0
--- /dev/null
+++ b/libwifi_system_iface/Android.bp
@@ -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.
+
+wifi_system_iface_cflags = [
+    "-Wall",
+    "-Werror",
+    "-Wextra",
+    "-Winit-self",
+    "-Wno-unused-function",
+    "-Wno-unused-parameter",
+    "-Wshadow",
+    "-Wunused-variable",
+    "-Wwrite-strings",
+]
+
+// Device independent wifi system logic.
+// ============================================================
+cc_library_shared {
+    name: "libwifi-system-iface",
+    vendor_available: true,
+    cflags: wifi_system_iface_cflags,
+    local_include_dirs: ["include"],
+    export_include_dirs: ["include"],
+    shared_libs: [
+        "libbase",
+    ],
+
+    srcs: [
+        "interface_tool.cpp",
+    ],
+}
+
+// Test utilities (e.g. mock classes) for libwifi-system-iface
+// ============================================================
+cc_library_static {
+    name: "libwifi-system-iface-test",
+    cflags: wifi_system_iface_cflags,
+    local_include_dirs: [
+        "include",
+        "testlib/include",
+    ],
+    static_libs: ["libgmock"],
+    export_include_dirs: [
+        "include",
+        "testlib/include",
+    ],
+}
diff --git a/libwifi_system/include/wifi_system/interface_tool.h b/libwifi_system_iface/include/wifi_system/interface_tool.h
similarity index 100%
rename from libwifi_system/include/wifi_system/interface_tool.h
rename to libwifi_system_iface/include/wifi_system/interface_tool.h
diff --git a/libwifi_system/interface_tool.cpp b/libwifi_system_iface/interface_tool.cpp
similarity index 100%
rename from libwifi_system/interface_tool.cpp
rename to libwifi_system_iface/interface_tool.cpp
diff --git a/libwifi_system/testlib/include/wifi_system_test/mock_interface_tool.h b/libwifi_system_iface/testlib/include/wifi_system_test/mock_interface_tool.h
similarity index 97%
rename from libwifi_system/testlib/include/wifi_system_test/mock_interface_tool.h
rename to libwifi_system_iface/testlib/include/wifi_system_test/mock_interface_tool.h
index b9926c9..200f0c0 100644
--- a/libwifi_system/testlib/include/wifi_system_test/mock_interface_tool.h
+++ b/libwifi_system_iface/testlib/include/wifi_system_test/mock_interface_tool.h
@@ -17,6 +17,7 @@
 #ifndef ANDROID_WIFI_SYSTEM_TEST_MOCK_INTERFACE_TOOL_H
 #define ANDROID_WIFI_SYSTEM_TEST_MOCK_INTERFACE_TOOL_H
 
+#include <gmock/gmock.h>
 #include <wifi_system/interface_tool.h>
 
 namespace android {
diff --git a/service/Android.mk b/service/Android.mk
index 16b063e..155f5b2 100644
--- a/service/Android.mk
+++ b/service/Android.mk
@@ -63,6 +63,7 @@
 	services
 LOCAL_STATIC_JAVA_LIBRARIES := \
 	android.hardware.wifi-V1.0-java \
+	android.hardware.wifi-V1.1-java \
 	android.hardware.wifi.supplicant-V1.0-java
 LOCAL_REQUIRED_MODULES := services
 LOCAL_MODULE_TAGS :=
diff --git a/service/java/com/android/server/wifi/AggressiveConnectedScore.java b/service/java/com/android/server/wifi/AggressiveConnectedScore.java
new file mode 100644
index 0000000..a0de773
--- /dev/null
+++ b/service/java/com/android/server/wifi/AggressiveConnectedScore.java
@@ -0,0 +1,66 @@
+/*
+ * 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.Context;
+import android.net.wifi.WifiInfo;
+
+import com.android.internal.R;
+
+/**
+ * Experimental scorer, used when aggressive handover preference is set.
+ */
+public class AggressiveConnectedScore extends ConnectedScore {
+
+    // Device configs. The values are examples.
+    private final int mThresholdQualifiedRssi5;    // -70
+    private final int mThresholdQualifiedRssi24;   // -73
+
+    private int mFrequencyMHz = 5000;
+    private int mRssi = 0;
+
+    public AggressiveConnectedScore(Context context, Clock clock) {
+        super(clock);
+        mThresholdQualifiedRssi5 = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_5GHz);
+        mThresholdQualifiedRssi24 = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_24GHz);
+    }
+
+    @Override
+    public void updateUsingRssi(int rssi, long millis, double standardDeviation) {
+        mRssi = rssi;
+    }
+
+    @Override
+    public void updateUsingWifiInfo(WifiInfo wifiInfo, long millis) {
+        mFrequencyMHz = wifiInfo.getFrequency();
+        mRssi = wifiInfo.getRssi();
+    }
+
+    @Override
+    public void reset() {
+        mFrequencyMHz = 5000;
+    }
+
+    @Override
+    public int generateScore() {
+        int badRssi = mFrequencyMHz >= 5000 ? mThresholdQualifiedRssi5 : mThresholdQualifiedRssi24;
+        int score = (mRssi - badRssi) + WIFI_TRANSITION_SCORE;
+        return score;
+    }
+}
diff --git a/service/java/com/android/server/wifi/ConfigurationMap.java b/service/java/com/android/server/wifi/ConfigurationMap.java
index fc85f64..0652b40 100644
--- a/service/java/com/android/server/wifi/ConfigurationMap.java
+++ b/service/java/com/android/server/wifi/ConfigurationMap.java
@@ -1,24 +1,21 @@
 package com.android.server.wifi;
 
-import android.content.pm.UserInfo;
+import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
 import android.os.UserHandle;
 import android.os.UserManager;
 
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
-import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 public class ConfigurationMap {
     private final Map<Integer, WifiConfiguration> mPerID = new HashMap<>();
 
     private final Map<Integer, WifiConfiguration> mPerIDForCurrentUser = new HashMap<>();
-    private final Map<String, WifiConfiguration> mPerFQDNForCurrentUser = new HashMap<>();
+    private final Map<ScanResultMatchInfo, WifiConfiguration>
+            mScanResultMatchInfoMapForCurrentUser = new HashMap<>();
 
     private final UserManager mUserManager;
 
@@ -34,9 +31,8 @@
         if (WifiConfigurationUtil.isVisibleToAnyProfile(config,
                 mUserManager.getProfiles(mCurrentUserId))) {
             mPerIDForCurrentUser.put(config.networkId, config);
-            if (config.FQDN != null && config.FQDN.length() > 0) {
-                mPerFQDNForCurrentUser.put(config.FQDN, config);
-            }
+            mScanResultMatchInfoMapForCurrentUser.put(
+                    ScanResultMatchInfo.fromWifiConfiguration(config), config);
         }
         return current;
     }
@@ -48,11 +44,12 @@
         }
 
         mPerIDForCurrentUser.remove(netID);
-        Iterator<Map.Entry<String, WifiConfiguration>> entries =
-                mPerFQDNForCurrentUser.entrySet().iterator();
-        while (entries.hasNext()) {
-            if (entries.next().getValue().networkId == netID) {
-                entries.remove();
+
+        Iterator<Map.Entry<ScanResultMatchInfo, WifiConfiguration>> scanResultMatchInfoEntries =
+                mScanResultMatchInfoMapForCurrentUser.entrySet().iterator();
+        while (scanResultMatchInfoEntries.hasNext()) {
+            if (scanResultMatchInfoEntries.next().getValue().networkId == netID) {
+                scanResultMatchInfoEntries.remove();
                 break;
             }
         }
@@ -62,7 +59,7 @@
     public void clear() {
         mPerID.clear();
         mPerIDForCurrentUser.clear();
-        mPerFQDNForCurrentUser.clear();
+        mScanResultMatchInfoMapForCurrentUser.clear();
     }
 
     /**
@@ -91,10 +88,6 @@
         return mPerIDForCurrentUser.size();
     }
 
-    public WifiConfiguration getByFQDNForCurrentUser(String fqdn) {
-        return mPerFQDNForCurrentUser.get(fqdn);
-    }
-
     public WifiConfiguration getByConfigKeyForCurrentUser(String key) {
         if (key == null) {
             return null;
@@ -107,23 +100,14 @@
         return null;
     }
 
-    public Collection<WifiConfiguration> getEnabledNetworksForCurrentUser() {
-        List<WifiConfiguration> list = new ArrayList<>();
-        for (WifiConfiguration config : mPerIDForCurrentUser.values()) {
-            if (config.status != WifiConfiguration.Status.DISABLED) {
-                list.add(config);
-            }
-        }
-        return list;
-    }
-
-    public WifiConfiguration getEphemeralForCurrentUser(String ssid) {
-        for (WifiConfiguration config : mPerIDForCurrentUser.values()) {
-            if (ssid.equals(config.SSID) && config.ephemeral) {
-                return config;
-            }
-        }
-        return null;
+    /**
+     * Retrieves the |WifiConfiguration| object matching the provided |scanResult| from the internal
+     * map.
+     * Essentially checks if network config and scan result have the same SSID and encryption type.
+     */
+    public WifiConfiguration getByScanResultForCurrentUser(ScanResult scanResult) {
+        return mScanResultMatchInfoMapForCurrentUser.get(
+                ScanResultMatchInfo.fromScanResult(scanResult));
     }
 
     public Collection<WifiConfiguration> valuesForAllUsers() {
diff --git a/service/java/com/android/server/wifi/ConnectedScore.java b/service/java/com/android/server/wifi/ConnectedScore.java
new file mode 100644
index 0000000..5c7ffa8
--- /dev/null
+++ b/service/java/com/android/server/wifi/ConnectedScore.java
@@ -0,0 +1,108 @@
+/*
+ * 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.net.NetworkAgent;
+import android.net.wifi.WifiInfo;
+
+/**
+ * Base class for connection scoring
+ */
+public abstract class ConnectedScore {
+
+    /** Maximum NetworkAgent score that should be generated by wifi */
+    public static final int WIFI_MAX_SCORE = NetworkAgent.WIFI_BASE_SCORE;
+
+    /** Score at which wifi is considered poor enough to give up ant try something else */
+    public static final int WIFI_TRANSITION_SCORE = NetworkAgent.WIFI_BASE_SCORE - 10;
+
+    public static final int WIFI_MIN_SCORE = 0;
+
+    final Clock mClock;
+
+    /** This is a typical STD for the connected RSSI for a phone sitting still */
+    public double mDefaultRssiStandardDeviation = 2.0;
+
+    /**
+     *
+     * @param clock is the time source for getMillis()
+     */
+    public ConnectedScore(Clock clock) {
+        mClock = clock;
+    }
+
+    /**
+     * Returns the current time in milliseconds
+     *
+     * This time is to be passed into the update methods.
+     * The scoring methods generally don't need a particular epoch, depending
+     * only on deltas. So a different time source may be used, as long as it is consistent.
+     *
+     * Note that when there are long intervals between updates, it is unlikely to matter much
+     * how large the interval is, so a time source that does not update while the processor is
+     * asleep could be just fine.
+     *
+     * @return millisecond-resolution time.
+     */
+    public long getMillis() {
+        return mClock.getWallClockMillis();
+    }
+
+    /**
+     * Updates scoring state using RSSI alone
+     *
+     * @param rssi signal strength (dB).
+     * @param millis millisecond-resolution time.
+     */
+    public void updateUsingRssi(int rssi, long millis) {
+        updateUsingRssi(rssi, millis, mDefaultRssiStandardDeviation);
+    }
+
+    /**
+     * Updates scoring state using RSSI and noise estimate
+     *
+     * This is useful if an RSSI comes from another source (e.g. scan results) and the
+     * expected noise varies by source.
+     *
+     * @param rssi signal strength (dB).
+     * @param millis millisecond-resolution time.
+     * @param standardDeviation of the RSSI.
+     */
+    public abstract void updateUsingRssi(int rssi, long millis, double standardDeviation);
+
+    /**
+     * Updates the score using relevant parts of WifiInfo
+     *
+     * @param wifiInfo object holding relevant values.
+     * @param millis millisecond-resolution time.
+     */
+    public void updateUsingWifiInfo(WifiInfo wifiInfo, long millis) {
+        updateUsingRssi(wifiInfo.getRssi(), millis);
+    }
+
+    /**
+     * Generates a score based on the current state
+     *
+     * @return network score - on NetworkAgent scale.
+     */
+    public abstract int generateScore();
+
+    /**
+     * Clears out state associated with the connection
+     */
+    public abstract void reset();
+}
diff --git a/service/java/com/android/server/wifi/FrameworkFacade.java b/service/java/com/android/server/wifi/FrameworkFacade.java
index ba114df..760ee69 100644
--- a/service/java/com/android/server/wifi/FrameworkFacade.java
+++ b/service/java/com/android/server/wifi/FrameworkFacade.java
@@ -18,6 +18,7 @@
 
 import android.app.ActivityManager;
 import android.app.AppGlobals;
+import android.app.Notification;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
@@ -94,6 +95,13 @@
         return PendingIntent.getBroadcast(context, requestCode, intent, flags);
     }
 
+    /**
+     * Wrapper for {@link PendingIntent#getActivity}.
+     */
+    public PendingIntent getActivity(Context context, int requestCode, Intent intent, int flags) {
+        return PendingIntent.getActivity(context, requestCode, intent, flags);
+    }
+
     public SupplicantStateTracker makeSupplicantStateTracker(Context context,
             WifiConfigManager configManager, Handler handler) {
         return new SupplicantStateTracker(context, configManager, this, handler);
@@ -172,4 +180,14 @@
     public boolean isAppForeground(int uid) throws RemoteException {
         return ActivityManager.getService().isAppForeground(uid);
     }
+
+    /**
+     * Create a new instance of {@link Notification.Builder}.
+     * @param context reference to a Context
+     * @param channelId ID of the notification channel
+     * @return an instance of Notification.Builder
+     */
+    public Notification.Builder makeNotificationBuilder(Context context, String channelId) {
+        return new Notification.Builder(context, channelId);
+    }
 }
diff --git a/service/java/com/android/server/wifi/HalDeviceManager.java b/service/java/com/android/server/wifi/HalDeviceManager.java
index a5e1273..d6009c7 100644
--- a/service/java/com/android/server/wifi/HalDeviceManager.java
+++ b/service/java/com/android/server/wifi/HalDeviceManager.java
@@ -170,7 +170,7 @@
      *
      * @return A set of IfaceTypes constants (possibly empty, e.g. on error).
      */
-    Set<Integer> getSupportedIfaceTypes() {
+    public Set<Integer> getSupportedIfaceTypes() {
         return getSupportedIfaceTypesInternal(null);
     }
 
@@ -179,7 +179,7 @@
      *
      * @return A set of IfaceTypes constants  (possibly empty, e.g. on error).
      */
-    Set<Integer> getSupportedIfaceTypes(IWifiChip chip) {
+    public Set<Integer> getSupportedIfaceTypes(IWifiChip chip) {
         return getSupportedIfaceTypesInternal(chip);
     }
 
@@ -244,12 +244,13 @@
      * other functions - e.g. calling the debug/trace methods.
      */
     public IWifiChip getChip(IWifiIface iface) {
-        if (DBG) Log.d(TAG, "getChip: iface(name)=" + getName(iface));
+        String name = getName(iface);
+        if (DBG) Log.d(TAG, "getChip: iface(name)=" + name);
 
         synchronized (mLock) {
-            InterfaceCacheEntry cacheEntry = mInterfaceInfoCache.get(iface);
+            InterfaceCacheEntry cacheEntry = mInterfaceInfoCache.get(name);
             if (cacheEntry == null) {
-                Log.e(TAG, "getChip: no entry for iface(name)=" + getName(iface));
+                Log.e(TAG, "getChip: no entry for iface(name)=" + name);
                 return null;
             }
 
@@ -267,13 +268,13 @@
     public boolean registerDestroyedListener(IWifiIface iface,
             InterfaceDestroyedListener destroyedListener,
             Looper looper) {
-        if (DBG) Log.d(TAG, "registerDestroyedListener: iface(name)=" + getName(iface));
+        String name = getName(iface);
+        if (DBG) Log.d(TAG, "registerDestroyedListener: iface(name)=" + name);
 
         synchronized (mLock) {
-            InterfaceCacheEntry cacheEntry = mInterfaceInfoCache.get(iface);
+            InterfaceCacheEntry cacheEntry = mInterfaceInfoCache.get(name);
             if (cacheEntry == null) {
-                Log.e(TAG, "registerDestroyedListener: no entry for iface(name)="
-                        + getName(iface));
+                Log.e(TAG, "registerDestroyedListener: no entry for iface(name)=" + name);
                 return false;
             }
 
@@ -303,9 +304,13 @@
      */
     public void registerInterfaceAvailableForRequestListener(int ifaceType,
             InterfaceAvailableForRequestListener listener, Looper looper) {
-        mInterfaceAvailableForRequestListeners.get(ifaceType).add(
-                new InterfaceAvailableForRequestListenerProxy(listener,
-                        looper == null ? Looper.myLooper() : looper));
+        if (DBG) Log.d(TAG, "registerInterfaceAvailableForRequestListener: ifaceType=" + ifaceType);
+
+        synchronized (mLock) {
+            mInterfaceAvailableForRequestListeners.get(ifaceType).add(
+                    new InterfaceAvailableForRequestListenerProxy(listener,
+                            looper == null ? Looper.myLooper() : looper));
+        }
 
         WifiChipInfo[] chipInfos = getAllChipInfo();
         if (chipInfos == null) {
@@ -323,12 +328,18 @@
     public void unregisterInterfaceAvailableForRequestListener(
             int ifaceType,
             InterfaceAvailableForRequestListener listener) {
-        Iterator<InterfaceAvailableForRequestListenerProxy> it =
-                mInterfaceAvailableForRequestListeners.get(ifaceType).iterator();
-        while (it.hasNext()) {
-            if (it.next().mListener == listener) {
-                it.remove();
-                return;
+        if (DBG) {
+            Log.d(TAG, "unregisterInterfaceAvailableForRequestListener: ifaceType=" + ifaceType);
+        }
+
+        synchronized (mLock) {
+            Iterator<InterfaceAvailableForRequestListenerProxy> it =
+                    mInterfaceAvailableForRequestListeners.get(ifaceType).iterator();
+            while (it.hasNext()) {
+                if (it.next().mListener == listener) {
+                    it.remove();
+                    return;
+                }
             }
         }
     }
@@ -443,13 +454,14 @@
     private final Set<ManagerStatusListenerProxy> mManagerStatusListeners = new HashSet<>();
     private final SparseArray<Set<InterfaceAvailableForRequestListenerProxy>>
             mInterfaceAvailableForRequestListeners = new SparseArray<>();
+    private final SparseArray<IWifiChipEventCallback.Stub> mDebugCallbacks = new SparseArray<>();
 
     /*
      * This is the only place where we cache HIDL information in this manager. Necessary since
      * we need to keep a list of registered destroyed listeners. Will be validated regularly
      * in getAllChipInfoAndValidateCache().
      */
-    private final Map<IWifiIface, InterfaceCacheEntry> mInterfaceInfoCache = new HashMap<>();
+    private final Map<String, InterfaceCacheEntry> mInterfaceInfoCache = new HashMap<>();
 
     private class InterfaceCacheEntry {
         public IWifiChip chip;
@@ -520,6 +532,9 @@
 
     private void initializeInternal() {
         initIServiceManagerIfNecessary();
+        if (isSupportedInternal()) {
+            initIWifiIfNecessary();
+        }
     }
 
     private void teardownInternal() {
@@ -548,9 +563,9 @@
                                            boolean preexisting) {
                     Log.d(TAG, "IWifi registration notification: fqName=" + fqName
                             + ", name=" + name + ", preexisting=" + preexisting);
-                    mWifi = null; // get rid of old copy!
-                    initIWifiIfNecessary();
-                    stopWifi(); // just in case
+                    synchronized (mLock) {
+                        initIWifiIfNecessary();
+                    }
                 }
             };
 
@@ -659,7 +674,8 @@
                     mWifi = null;
                     return;
                 }
-                managerStatusListenerDispatch();
+                // Stopping wifi just in case. This would also trigger the status callback.
+                stopWifi();
             } catch (RemoteException e) {
                 Log.e(TAG, "Exception while operating on IWifi: " + e);
             }
@@ -720,7 +736,7 @@
                         continue; // still try next one?
                     }
 
-                    WifiStatus status = chipResp.value.registerEventCallback(
+                    IWifiChipEventCallback.Stub callback =
                             new IWifiChipEventCallback.Stub() {
                                 @Override
                                 public void onChipReconfigured(int modeId) throws RemoteException {
@@ -759,7 +775,9 @@
                                         throws RemoteException {
                                     Log.d(TAG, "onDebugErrorAlert");
                                 }
-                            });
+                            };
+                    mDebugCallbacks.put(chipId, callback); // store to prevent GC: needed by HIDL
+                    WifiStatus status = chipResp.value.registerEventCallback(callback);
                     if (status.code != WifiStatusCode.SUCCESS) {
                         Log.e(TAG, "registerEventCallback failed: " + statusString(status));
                         continue; // still try next one?
@@ -1028,37 +1046,36 @@
         if (DBG) Log.d(TAG, "validateInterfaceCache");
 
         synchronized (mLock) {
-            for (Map.Entry<IWifiIface, InterfaceCacheEntry> entry: mInterfaceInfoCache.entrySet()) {
+            for (InterfaceCacheEntry entry: mInterfaceInfoCache.values()) {
                 // search for chip
                 WifiChipInfo matchingChipInfo = null;
                 for (WifiChipInfo ci: chipInfos) {
-                    if (ci.chipId == entry.getValue().chipId) {
+                    if (ci.chipId == entry.chipId) {
                         matchingChipInfo = ci;
                         break;
                     }
                 }
                 if (matchingChipInfo == null) {
-                    Log.e(TAG, "validateInterfaceCache: no chip found for " + entry.getValue());
+                    Log.e(TAG, "validateInterfaceCache: no chip found for " + entry);
                     return false;
                 }
 
                 // search for interface
-                WifiIfaceInfo[] ifaceInfoList = matchingChipInfo.ifaces[entry.getValue().type];
+                WifiIfaceInfo[] ifaceInfoList = matchingChipInfo.ifaces[entry.type];
                 if (ifaceInfoList == null) {
-                    Log.e(TAG, "validateInterfaceCache: invalid type on entry " + entry.getValue());
+                    Log.e(TAG, "validateInterfaceCache: invalid type on entry " + entry);
                     return false;
                 }
 
                 boolean matchFound = false;
                 for (WifiIfaceInfo ifaceInfo: ifaceInfoList) {
-                    if (ifaceInfo.name.equals(entry.getValue().name)) {
+                    if (ifaceInfo.name.equals(entry.name)) {
                         matchFound = true;
                         break;
                     }
                 }
                 if (!matchFound) {
-                    Log.e(TAG, "validateInterfaceCache: no interface found for "
-                            + entry.getValue());
+                    Log.e(TAG, "validateInterfaceCache: no interface found for " + entry);
                     return false;
                 }
             }
@@ -1323,7 +1340,8 @@
                                         looper == null ? Looper.myLooper() : looper));
                     }
 
-                    mInterfaceInfoCache.put(iface, cacheEntry);
+                    if (DBG) Log.d(TAG, "createIfaceIfPossible: added cacheEntry=" + cacheEntry);
+                    mInterfaceInfoCache.put(cacheEntry.name, cacheEntry);
                     return iface;
                 }
             }
@@ -1676,21 +1694,21 @@
     }
 
     private boolean removeIfaceInternal(IWifiIface iface) {
-        if (DBG) Log.d(TAG, "removeIfaceInternal: iface(name)=" + getName(iface));
+        String name = getName(iface);
+        if (DBG) Log.d(TAG, "removeIfaceInternal: iface(name)=" + name);
 
         synchronized (mLock) {
             if (mWifi == null) {
-                Log.e(TAG, "removeIfaceInternal: null IWifi -- iface(name)=" + getName(iface));
+                Log.e(TAG, "removeIfaceInternal: null IWifi -- iface(name)=" + name);
                 return false;
             }
 
             IWifiChip chip = getChip(iface);
             if (chip == null) {
-                Log.e(TAG, "removeIfaceInternal: null IWifiChip -- iface(name)=" + getName(iface));
+                Log.e(TAG, "removeIfaceInternal: null IWifiChip -- iface(name)=" + name);
                 return false;
             }
 
-            String name = getName(iface);
             if (name == null) {
                 Log.e(TAG, "removeIfaceInternal: can't get name");
                 return false;
@@ -1698,7 +1716,7 @@
 
             int type = getType(iface);
             if (type == -1) {
-                Log.e(TAG, "removeIfaceInternal: can't get type -- iface(name)=" + getName(iface));
+                Log.e(TAG, "removeIfaceInternal: can't get type -- iface(name)=" + name);
                 return false;
             }
 
@@ -1726,7 +1744,7 @@
             }
 
             // dispatch listeners no matter what status
-            dispatchDestroyedListeners(iface);
+            dispatchDestroyedListeners(name);
 
             if (status != null && status.code == WifiStatusCode.SUCCESS) {
                 return true;
@@ -1786,14 +1804,13 @@
 
     // dispatch all destroyed listeners registered for the specified interface AND remove the
     // cache entry
-    private void dispatchDestroyedListeners(IWifiIface iface) {
-        if (DBG) Log.d(TAG, "dispatchDestroyedListeners: iface(name)=" + getName(iface));
+    private void dispatchDestroyedListeners(String name) {
+        if (DBG) Log.d(TAG, "dispatchDestroyedListeners: iface(name)=" + name);
 
         synchronized (mLock) {
-            InterfaceCacheEntry entry = mInterfaceInfoCache.get(iface);
+            InterfaceCacheEntry entry = mInterfaceInfoCache.get(name);
             if (entry == null) {
-                Log.e(TAG, "dispatchDestroyedListeners: no cache entry for iface(name)="
-                        + getName(iface));
+                Log.e(TAG, "dispatchDestroyedListeners: no cache entry for iface(name)=" + name);
                 return;
             }
 
@@ -1801,7 +1818,7 @@
                 listener.trigger();
             }
             entry.destroyedListeners.clear(); // for insurance (though cache entry is removed)
-            mInterfaceInfoCache.remove(iface);
+            mInterfaceInfoCache.remove(name);
         }
     }
 
@@ -1810,7 +1827,7 @@
         if (DBG) Log.d(TAG, "dispatchAllDestroyedListeners");
 
         synchronized (mLock) {
-            Iterator<Map.Entry<IWifiIface, InterfaceCacheEntry>> it =
+            Iterator<Map.Entry<String, InterfaceCacheEntry>> it =
                     mInterfaceInfoCache.entrySet().iterator();
             while (it.hasNext()) {
                 InterfaceCacheEntry entry = it.next().getValue();
diff --git a/service/java/com/android/server/wifi/LegacyConnectedScore.java b/service/java/com/android/server/wifi/LegacyConnectedScore.java
new file mode 100644
index 0000000..facab0a
--- /dev/null
+++ b/service/java/com/android/server/wifi/LegacyConnectedScore.java
@@ -0,0 +1,249 @@
+/*
+ * 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.Context;
+import android.net.NetworkAgent;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+
+import com.android.internal.R;
+
+/**
+ * Class used to calculate scores for connected wifi networks and report it to the associated
+ * network agent.
+ */
+public class LegacyConnectedScore extends ConnectedScore {
+
+    private static final int STARTING_SCORE = 56;
+
+    private static final int SCAN_CACHE_VISIBILITY_MS = 12000;
+    private static final int HOME_VISIBLE_NETWORK_MAX_COUNT = 6;
+    private static final int SCAN_CACHE_COUNT_PENALTY = 2;
+    private static final int MAX_SUCCESS_RATE_OF_STUCK_LINK = 3; // proportional to packets per sec
+    private static final int MAX_STUCK_LINK_COUNT = 5;
+    private static final int MAX_BAD_RSSI_COUNT = 7;
+    private static final int BAD_RSSI_COUNT_PENALTY = 2;
+    private static final int MAX_LOW_RSSI_COUNT = 1;
+    private static final double MIN_TX_FAILURE_RATE_FOR_WORKING_LINK = 0.3;
+    private static final int MIN_SUSTAINED_LINK_STUCK_COUNT = 1;
+    private static final int LINK_STUCK_PENALTY = 2;
+    private static final int BAD_LINKSPEED_PENALTY = 4;
+    private static final int GOOD_LINKSPEED_BONUS = 4;
+
+    // Device configs. The values are examples.
+    private final int mThresholdMinimumRssi5;      // -82
+    private final int mThresholdQualifiedRssi5;    // -70
+    private final int mThresholdSaturatedRssi5;    // -57
+    private final int mThresholdMinimumRssi24;     // -85
+    private final int mThresholdQualifiedRssi24;   // -73
+    private final int mThresholdSaturatedRssi24;   // -60
+    private final int mBadLinkSpeed24;             //  6 Mbps
+    private final int mBadLinkSpeed5;              // 12 Mbps
+    private final int mGoodLinkSpeed24;            // 24 Mbps
+    private final int mGoodLinkSpeed5;             // 36 Mbps
+
+    private final WifiConfigManager mWifiConfigManager;
+    private boolean mVerboseLoggingEnabled = false;
+
+    private boolean mMultiBandScanResults;
+    private boolean mIsHomeNetwork;
+    private int mScore = 0;
+
+    LegacyConnectedScore(Context context, WifiConfigManager wifiConfigManager, Clock clock) {
+        super(clock);
+        // Fetch all the device configs.
+        mThresholdMinimumRssi5 = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz);
+        mThresholdQualifiedRssi5 = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_5GHz);
+        mThresholdSaturatedRssi5 = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_5GHz);
+        mThresholdMinimumRssi24 = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz);
+        mThresholdQualifiedRssi24 = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_24GHz);
+        mThresholdSaturatedRssi24 = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz);
+        mBadLinkSpeed24 = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_bad_link_speed_24);
+        mBadLinkSpeed5 = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_bad_link_speed_5);
+        mGoodLinkSpeed24 = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_good_link_speed_24);
+        mGoodLinkSpeed5 = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_good_link_speed_5);
+
+        mWifiConfigManager = wifiConfigManager;
+    }
+
+    @Override
+    public void updateUsingWifiInfo(WifiInfo wifiInfo, long millis) {
+        mMultiBandScanResults = multiBandScanResults(wifiInfo);
+        mIsHomeNetwork = isHomeNetwork(wifiInfo);
+
+        int rssiThreshBad = mThresholdMinimumRssi24;
+        int rssiThreshLow = mThresholdQualifiedRssi24;
+
+        if (wifiInfo.is5GHz() && !mMultiBandScanResults) {
+            rssiThreshBad = mThresholdMinimumRssi5;
+            rssiThreshLow = mThresholdQualifiedRssi5;
+        }
+
+        int rssi =  wifiInfo.getRssi();
+        if (mIsHomeNetwork) {
+            rssi += WifiConfiguration.HOME_NETWORK_RSSI_BOOST;
+        }
+
+        if ((wifiInfo.txBadRate >= 1)
+                && (wifiInfo.txSuccessRate < MAX_SUCCESS_RATE_OF_STUCK_LINK)
+                && rssi < rssiThreshLow) {
+            // Link is stuck
+            if (wifiInfo.linkStuckCount < MAX_STUCK_LINK_COUNT) {
+                wifiInfo.linkStuckCount += 1;
+            }
+        } else if (wifiInfo.txBadRate < MIN_TX_FAILURE_RATE_FOR_WORKING_LINK) {
+            if (wifiInfo.linkStuckCount > 0) {
+                wifiInfo.linkStuckCount -= 1;
+            }
+        }
+
+        if (rssi < rssiThreshBad) {
+            if (wifiInfo.badRssiCount < MAX_BAD_RSSI_COUNT) {
+                wifiInfo.badRssiCount += 1;
+            }
+        } else if (rssi < rssiThreshLow) {
+            wifiInfo.lowRssiCount = MAX_LOW_RSSI_COUNT; // Dont increment the lowRssi count above 1
+            if (wifiInfo.badRssiCount > 0) {
+                // Decrement bad Rssi count
+                wifiInfo.badRssiCount -= 1;
+            }
+        } else {
+            wifiInfo.badRssiCount = 0;
+            wifiInfo.lowRssiCount = 0;
+        }
+
+        // Ugh, we need to finish the score calculation while we have wifiInfo
+        mScore = calculateScore(wifiInfo);
+
+    }
+
+    @Override
+    public void updateUsingRssi(int rssi, long millis, double standardDeviation) {
+        // This scorer needs more than just the RSSI. Just ignore.
+    }
+
+    @Override
+    public int generateScore() {
+        return mScore;
+    }
+
+    @Override
+    public void reset() {
+        mScore = 0;
+    }
+
+    /**
+     * Calculates a score based on the current state and wifiInfo
+     */
+    private int calculateScore(WifiInfo wifiInfo) {
+        int score = STARTING_SCORE;
+
+        int rssiThreshSaturated = mThresholdSaturatedRssi24;
+        int linkspeedThreshBad = mBadLinkSpeed24;
+        int linkspeedThreshGood = mGoodLinkSpeed24;
+
+        if (wifiInfo.is5GHz()) {
+            if (!mMultiBandScanResults) {
+                rssiThreshSaturated = mThresholdSaturatedRssi5;
+            }
+            linkspeedThreshBad = mBadLinkSpeed5;
+            linkspeedThreshGood = mGoodLinkSpeed5;
+        }
+
+        int rssi =  wifiInfo.getRssi();
+        if (mIsHomeNetwork) {
+            rssi += WifiConfiguration.HOME_NETWORK_RSSI_BOOST;
+        }
+
+        int linkSpeed = wifiInfo.getLinkSpeed();
+
+        if (wifiInfo.linkStuckCount > MIN_SUSTAINED_LINK_STUCK_COUNT) {
+            // Once link gets stuck for more than 3 seconds, start reducing the score
+            score = score - LINK_STUCK_PENALTY * (wifiInfo.linkStuckCount - 1);
+        }
+
+        if (linkSpeed < linkspeedThreshBad) {
+            score -= BAD_LINKSPEED_PENALTY;
+        } else if ((linkSpeed >= linkspeedThreshGood) && (wifiInfo.txSuccessRate > 5)) {
+            score += GOOD_LINKSPEED_BONUS; // So as bad rssi alone doesn't kill us
+        }
+
+        score -= wifiInfo.badRssiCount * BAD_RSSI_COUNT_PENALTY + wifiInfo.lowRssiCount;
+
+        if (rssi >= rssiThreshSaturated) score += 5;
+
+        if (score > NetworkAgent.WIFI_BASE_SCORE) score = NetworkAgent.WIFI_BASE_SCORE;
+        if (score < 0) score = 0;
+
+        return score;
+    }
+
+    /**
+     * Determines if we can see both 2.4GHz and 5GHz for current config
+     */
+    private boolean multiBandScanResults(WifiInfo wifiInfo) {
+        WifiConfiguration currentConfiguration =
+                mWifiConfigManager.getConfiguredNetwork(wifiInfo.getNetworkId());
+        if (currentConfiguration == null) return false;
+        ScanDetailCache scanDetailCache =
+                mWifiConfigManager.getScanDetailCacheForNetwork(wifiInfo.getNetworkId());
+        if (scanDetailCache == null) return false;
+        // Nasty that we change state here...
+        currentConfiguration.setVisibility(scanDetailCache.getVisibility(SCAN_CACHE_VISIBILITY_MS));
+        if (currentConfiguration.visibility == null) return false;
+        if (currentConfiguration.visibility.rssi24 == WifiConfiguration.INVALID_RSSI) return false;
+        if (currentConfiguration.visibility.rssi5 == WifiConfiguration.INVALID_RSSI) return false;
+        // N.B. this does not do exactly what is claimed!
+        if (currentConfiguration.visibility.rssi24
+                >= currentConfiguration.visibility.rssi5 - SCAN_CACHE_COUNT_PENALTY) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Decides whether the current network is a "home" network
+     */
+    private boolean isHomeNetwork(WifiInfo wifiInfo) {
+        WifiConfiguration currentConfiguration =
+                mWifiConfigManager.getConfiguredNetwork(wifiInfo.getNetworkId());
+        if (currentConfiguration == null) return false;
+        // This seems like it will only return true for really old routers!
+        if (currentConfiguration.allowedKeyManagement.cardinality() != 1) return false;
+        if (!currentConfiguration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
+            return false;
+        }
+        ScanDetailCache scanDetailCache =
+                mWifiConfigManager.getScanDetailCacheForNetwork(wifiInfo.getNetworkId());
+        if (scanDetailCache == null) return false;
+        if (scanDetailCache.size() <= HOME_VISIBLE_NETWORK_MAX_COUNT) {
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/service/java/com/android/server/wifi/OWNERS b/service/java/com/android/server/wifi/OWNERS
index 8487100..7cc644a 100644
--- a/service/java/com/android/server/wifi/OWNERS
+++ b/service/java/com/android/server/wifi/OWNERS
@@ -7,76 +7,116 @@
 mett@google.com
 mplass@google.com
 nywang@google.com
-pstew@google.com
 quiche@google.com
 rpius@google.com
 silberst@google.com
 sohanirao@google.com
-zpan@google.com
 zqiu@google.com
 
 # configuration
 per-file WifiBackupRestore*=rpius@google.com
+per-file WifiBackupRestore*=satk@google.com
 per-file WifiConfigManager*=rpius@google.com
+per-file WifiConfigManager*=satk@google.com
 per-file WifiConfigStore*=rpius@google.com
+per-file WifiConfigStore*=satk@google.com
 per-file WifiSupplicantControl*=rpius@google.com
+per-file WifiSupplicantControl*=satk@google.com
 
 # diagnostics
 per-file BaseWifiDiagnostics*=quiche@google.com
+per-file BaseWifiDiagnostics*=satk@google.com
 per-file LastMileLogger*=quiche@google.com
+per-file LastMileLogger*=satk@google.com
 per-file WifiDiagnostics*=quiche@google.com
+per-file WifiDiagnostics*=satk@google.com
 per-file WifiLoggerHal*=quiche@google.com
+per-file WifiLoggerHal*=satk@google.com
 
 # HAL support
 per-file HalDeviceManager*=etancohen@google.com
+per-file HalDeviceManager*=satk@google.com
 per-file SupplicantStaNetworkHal*=kuh@google.com
 per-file SupplicantStaNetworkHal*=rpius@google.com
+per-file SupplicantStaNetworkHal*=satk@google.com
 per-file SupplicantStaIfaceHal*=kuh@google.com
 per-file SupplicantStaIfaceHal*=rpius@google.com
+per-file SupplicantStaIfaceHal*=satk@google.com
 per-file WifiVendorHal*=mplass@google.com
+per-file WifiVendorHal*=satk@google.com
 
 # logging
 per-file DummyLogMessage*=quiche@google.com
+per-file DummyLogMessage*=satk@google.com
 per-file FakeWifiLog*=quiche@google.com
+per-file FakeWifiLog*=satk@google.com
 per-file LogcatLog*=quiche@google.com
+per-file LogcatLog*=satk@google.com
 per-file WifiLog*=quiche@google.com
+per-file WifiLog*=satk@google.com
 
 # mode management
 per-file ActiveModeManager*=silberst@google.com
+per-file ActiveModeManager*=satk@google.com
 per-file ClientModeManager*=silberst@google.com
+per-file ClientModeManager*=satk@google.com
 per-file ScanOnlyModeManager*=silberst@google.com
+per-file ScanOnlyModeManager*=satk@google.com
 per-file WifiController*=silberst@google.com
+per-file WifiController*=satk@google.com
 per-file WifiService*=silberst@google.com
+per-file WifiService*=satk@google.com
 per-file WifiServiceImpl*=silberst@google.com
+per-file WifiServiceImpl*=satk@google.com
 per-file WifiStateMachine*=silberst@google.com
+per-file WifiStateMachine*=satk@google.com
 
 # network selection
-per-file SavedNetworkEvaluator*=zpan@google.com
-per-file WifiConnectivityManager*=zpan@google.com
-per-file WifiNetworkSelector*=zpan@google.com
+per-file SavedNetworkEvaluator*=quiche@google.com
+per-file SavedNetworkEvaluator*=silberst@google.com
+per-file SavedNetworkEvaluator*=satk@google.com
+per-file WifiConnectivityManager*=quiche@google.com
+per-file WifiConnectivityManager*=silberst@google.com
+per-file WifiConnectivityManager*=satk@google.com
+per-file WifiNetworkSelector*=quiche@google.com
+per-file WifiNetworkSelector*=silberst@google.com
+per-file WifiNetworkSelector*=satk@google.com
 
 # random bits
 per-file ByteBufferReader*=zqiu@google.com
+per-file ByteBufferReader*=satk@google.com
 per-file IMSIParameter*=zqiu@google.com
+per-file IMSIParameter*=satk@google.com
 per-file SIMAccessor*=zqiu@google.com
+per-file SIMAccessor*=satk@google.com
 per-file WifiCountryCode*=nywang@google.com
+per-file WifiCountryCode*=satk@google.com
 per-file WifiLastResortWatchdog*=kuh@google.com
 per-file WifiLastResortWatchdog*=silberst@google.com
+per-file WifiLastResortWatchdog*=satk@google.com
 per-file WifiMetrics*=kuh@google.com
+per-file WifiMetrics*=satk@google.com
 per-file WifiScoreReport*=mplass@google.com
+per-file WifiScoreReport*=satk@google.com
 
 # soft-ap/tethering
 per-file SoftApManager*=silberst@google.com
+per-file SoftApManager*=satk@google.com
 per-file WifiApConfigStore*=silberst@google.com
+per-file WifiApConfigStore*=satk@google.com
 
 # test-support
 per-file Clock*=quiche@google.com
+per-file Clock*=satk@google.com
 per-file FrameworkFacade*=kuh@google.com
 per-file FrameworkFacade*=rpius@google.com
 per-file FrameworkFacade*=silberst@google.com
+per-file FrameworkFacade*=satk@google.com
 per-file WifiInjector*=kuh@google.com
 per-file WifiInjector*=rpius@google.com
 per-file WifiInjector*=silberst@google.com
+per-file WifiInjector*=satk@google.com
 
 # wificond
 per-file WificondControl*=nywang@google.com
+per-file WificondControl*=satk@google.com
diff --git a/service/java/com/android/server/wifi/SavedNetworkEvaluator.java b/service/java/com/android/server/wifi/SavedNetworkEvaluator.java
index 9ac7068..59d4ab2 100644
--- a/service/java/com/android/server/wifi/SavedNetworkEvaluator.java
+++ b/service/java/com/android/server/wifi/SavedNetworkEvaluator.java
@@ -23,6 +23,7 @@
 import android.util.Pair;
 
 import com.android.internal.R;
+import com.android.server.wifi.util.TelephonyUtil;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -248,7 +249,7 @@
             // the scores and use the highest one as the ScanResult's score.
             List<WifiConfiguration> associatedConfigurations = null;
             WifiConfiguration associatedConfiguration =
-                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(scanDetail);
+                    mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(scanDetail);
 
             if (associatedConfiguration == null) {
                 continue;
@@ -282,6 +283,10 @@
                             + " has specified BSSID " + network.BSSID + ". Skip "
                             + scanResult.BSSID);
                     continue;
+                } else if (TelephonyUtil.isSimConfig(network)
+                        && !mWifiConfigManager.isSimPresent()) {
+                    // Don't select if security type is EAP SIM/AKA/AKA' when SIM is not present.
+                    continue;
                 }
 
                 int score = calculateBssidScore(scanResult, network, currentNetwork, currentBssid,
diff --git a/service/java/com/android/server/wifi/ScanDetailCache.java b/service/java/com/android/server/wifi/ScanDetailCache.java
index a651379..3b69a64 100644
--- a/service/java/com/android/server/wifi/ScanDetailCache.java
+++ b/service/java/com/android/server/wifi/ScanDetailCache.java
@@ -67,12 +67,24 @@
         mMap.put(scanDetail.getBSSIDString(), scanDetail);
     }
 
-    ScanResult get(String bssid) {
+    /**
+     * Get ScanResult object corresponding to the provided BSSID.
+     *
+     * @param bssid provided BSSID
+     * @return {@code null} if no match ScanResult is found.
+     */
+    public ScanResult get(String bssid) {
         ScanDetail scanDetail = getScanDetail(bssid);
         return scanDetail == null ? null : scanDetail.getScanResult();
     }
 
-    ScanDetail getScanDetail(String bssid) {
+    /**
+     * Get ScanDetail object corresponding to the provided BSSID.
+     *
+     * @param bssid provided BSSID
+     * @return {@code null} if no match ScanDetail is found.
+     */
+    public ScanDetail getScanDetail(String bssid) {
         return mMap.get(bssid);
     }
 
diff --git a/service/java/com/android/server/wifi/ScanResultMatchInfo.java b/service/java/com/android/server/wifi/ScanResultMatchInfo.java
new file mode 100644
index 0000000..ad29c23
--- /dev/null
+++ b/service/java/com/android/server/wifi/ScanResultMatchInfo.java
@@ -0,0 +1,108 @@
+/*
+ * 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.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+
+import com.android.server.wifi.util.ScanResultUtil;
+
+import java.util.Objects;
+
+/**
+ * Class to store the info needed to match a scan result to the provided network configuration.
+ */
+public class ScanResultMatchInfo {
+    public static final int NETWORK_TYPE_OPEN = 0;
+    public static final int NETWORK_TYPE_WEP = 1;
+    public static final int NETWORK_TYPE_PSK = 2;
+    public static final int NETWORK_TYPE_EAP = 3;
+
+    /**
+     * SSID of the network.
+     */
+    public String networkSsid;
+    /**
+     * Security Type of the network.
+     */
+    public int networkType;
+
+    /**
+     * Get the ScanResultMatchInfo for the given WifiConfiguration
+     */
+    public static ScanResultMatchInfo fromWifiConfiguration(WifiConfiguration config) {
+        ScanResultMatchInfo info = new ScanResultMatchInfo();
+        info.networkSsid = config.SSID;
+        if (WifiConfigurationUtil.isConfigForPskNetwork(config)) {
+            info.networkType = NETWORK_TYPE_PSK;
+        } else if (WifiConfigurationUtil.isConfigForEapNetwork(config)) {
+            info.networkType = NETWORK_TYPE_EAP;
+        } else if (WifiConfigurationUtil.isConfigForWepNetwork(config)) {
+            info.networkType = NETWORK_TYPE_WEP;
+        } else if (WifiConfigurationUtil.isConfigForOpenNetwork(config)) {
+            info.networkType = NETWORK_TYPE_OPEN;
+        } else {
+            throw new IllegalArgumentException("Invalid WifiConfiguration: " + config);
+        }
+        return info;
+    }
+
+    /**
+     * Get the ScanResultMatchInfo for the given ScanResult
+     */
+    public static ScanResultMatchInfo fromScanResult(ScanResult scanResult) {
+        ScanResultMatchInfo info = new ScanResultMatchInfo();
+        // Scan result ssid's are not quoted, hence add quotes.
+        // TODO: This matching algo works only if the scan result contains a string SSID.
+        // However, according to our public documentation ths {@link WifiConfiguration#SSID} can
+        // either have a hex string or quoted ASCII string SSID.
+        info.networkSsid = ScanResultUtil.createQuotedSSID(scanResult.SSID);
+        if (ScanResultUtil.isScanResultForPskNetwork(scanResult)) {
+            info.networkType = NETWORK_TYPE_PSK;
+        } else if (ScanResultUtil.isScanResultForEapNetwork(scanResult)) {
+            info.networkType = NETWORK_TYPE_EAP;
+        } else if (ScanResultUtil.isScanResultForWepNetwork(scanResult)) {
+            info.networkType = NETWORK_TYPE_WEP;
+        } else if (ScanResultUtil.isScanResultForOpenNetwork(scanResult)) {
+            info.networkType = NETWORK_TYPE_OPEN;
+        } else {
+            throw new IllegalArgumentException("Invalid ScanResult: " + scanResult);
+        }
+        return info;
+    }
+
+    @Override
+    public boolean equals(Object otherObj) {
+        if (this == otherObj) {
+            return true;
+        } else if (!(otherObj instanceof ScanResultMatchInfo)) {
+            return false;
+        }
+        ScanResultMatchInfo other = (ScanResultMatchInfo) otherObj;
+        return Objects.equals(networkSsid, other.networkSsid)
+                && networkType == other.networkType;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(networkSsid, networkType);
+    }
+
+    @Override
+    public String toString() {
+        return "ScanResultMatchInfo: " + networkSsid + ", type: " + networkType;
+    }
+}
diff --git a/service/java/com/android/server/wifi/ScoredNetworkEvaluator.java b/service/java/com/android/server/wifi/ScoredNetworkEvaluator.java
index 483da99..e36cf66 100644
--- a/service/java/com/android/server/wifi/ScoredNetworkEvaluator.java
+++ b/service/java/com/android/server/wifi/ScoredNetworkEvaluator.java
@@ -125,7 +125,7 @@
                 continue;
             }
             final WifiConfiguration configuredNetwork =
-                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(scanDetail);
+                    mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(scanDetail);
             boolean untrustedScanResult = configuredNetwork == null || configuredNetwork.ephemeral;
 
             if (!untrustedNetworkAllowed && untrustedScanResult) {
diff --git a/service/java/com/android/server/wifi/SelfRecovery.java b/service/java/com/android/server/wifi/SelfRecovery.java
index b35b7cc..21a3e0a 100644
--- a/service/java/com/android/server/wifi/SelfRecovery.java
+++ b/service/java/com/android/server/wifi/SelfRecovery.java
@@ -18,6 +18,9 @@
 
 import android.util.Log;
 
+import java.util.Iterator;
+import java.util.LinkedList;
+
 /**
  * This class is used to recover the wifi stack from a fatal failure. The recovery mechanism
  * involves triggering a stack restart (essentially simulating an airplane mode toggle) using
@@ -36,7 +39,8 @@
     public static final int REASON_LAST_RESORT_WATCHDOG = 0;
     public static final int REASON_HAL_CRASH = 1;
     public static final int REASON_WIFICOND_CRASH = 2;
-
+    public static final long MAX_RESTARTS_IN_TIME_WINDOW = 2; // 2 restarts per hour
+    public static final long MAX_RESTARTS_TIME_WINDOW_MILLIS = 60 * 60 * 1000; // 1 hour
     private static final String[] REASON_STRINGS = {
             "Last Resort Watchdog", // REASON_LAST_RESORT_WATCHDOG
             "Hal Crash",            // REASON_HAL_CRASH
@@ -44,9 +48,13 @@
     };
 
     private final WifiController mWifiController;
-
-    SelfRecovery(WifiController wifiController) {
+    private final Clock mClock;
+    // Time since boot (in millis) that restart occurred
+    private final LinkedList<Long> mPastRestartTimes;
+    public SelfRecovery(WifiController wifiController, Clock clock) {
         mWifiController = wifiController;
+        mClock = clock;
+        mPastRestartTimes = new LinkedList<Long>();
     }
 
     /**
@@ -59,11 +67,38 @@
      * @param reason One of the above |REASON_*| codes.
      */
     public void trigger(int reason) {
-        if (reason < REASON_LAST_RESORT_WATCHDOG || reason > REASON_WIFICOND_CRASH) {
+        if (!(reason == REASON_LAST_RESORT_WATCHDOG || reason == REASON_HAL_CRASH
+                || reason == REASON_WIFICOND_CRASH)) {
             Log.e(TAG, "Invalid trigger reason. Ignoring...");
             return;
         }
         Log.wtf(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
+            if (mPastRestartTimes.size() >= MAX_RESTARTS_IN_TIME_WINDOW) {
+                Log.e(TAG, "Already restarted wifi (" + MAX_RESTARTS_IN_TIME_WINDOW + ") times in"
+                        + " last (" + MAX_RESTARTS_TIME_WINDOW_MILLIS + "ms ). Ignoring...");
+                return;
+            }
+            mPastRestartTimes.add(mClock.getElapsedSinceBootMillis());
+        }
         mWifiController.sendMessage(WifiController.CMD_RESTART_WIFI);
     }
+
+    /**
+     * Process the mPastRestartTimes list, removing elements outside the max restarts time window
+     */
+    private void trimPastRestartTimes() {
+        Iterator<Long> iter = mPastRestartTimes.iterator();
+        long now = mClock.getElapsedSinceBootMillis();
+        while (iter.hasNext()) {
+            Long restartTimeMillis = iter.next();
+            if (now - restartTimeMillis > MAX_RESTARTS_TIME_WINDOW_MILLIS) {
+                iter.remove();
+            } else {
+                break;
+            }
+        }
+    }
 }
diff --git a/service/java/com/android/server/wifi/SoftApManager.java b/service/java/com/android/server/wifi/SoftApManager.java
index 64f4a14..d4a1ea5 100644
--- a/service/java/com/android/server/wifi/SoftApManager.java
+++ b/service/java/com/android/server/wifi/SoftApManager.java
@@ -161,12 +161,16 @@
 
         int encryptionType = getIApInterfaceEncryptionType(localConfig);
 
+        if (localConfig.hiddenSSID) {
+            Log.d(TAG, "SoftAP is a hidden network");
+        }
+
         try {
             // Note that localConfig.SSID is intended to be either a hex string or "double quoted".
             // However, it seems that whatever is handing us these configurations does not obey
             // this convention.
             boolean success = mApInterface.writeHostapdConfig(
-                    localConfig.SSID.getBytes(StandardCharsets.UTF_8), false,
+                    localConfig.SSID.getBytes(StandardCharsets.UTF_8), localConfig.hiddenSSID,
                     localConfig.apChannel, encryptionType,
                     (localConfig.preSharedKey != null)
                             ? localConfig.preSharedKey.getBytes(StandardCharsets.UTF_8)
diff --git a/service/java/com/android/server/wifi/SupplicantStaIfaceHal.java b/service/java/com/android/server/wifi/SupplicantStaIfaceHal.java
index 703c798..d2182fc 100644
--- a/service/java/com/android/server/wifi/SupplicantStaIfaceHal.java
+++ b/service/java/com/android/server/wifi/SupplicantStaIfaceHal.java
@@ -69,14 +69,18 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import javax.annotation.concurrent.ThreadSafe;
+
 /**
  * Hal calls for bring up/shut down of the supplicant daemon and for
  * sending requests to the supplicant daemon
+ * To maintain thread-safety, the locking protocol is that every non-static method (regardless of
+ * access level) acquires mLock.
  */
+@ThreadSafe
 public class SupplicantStaIfaceHal {
     private static final String TAG = "SupplicantStaIfaceHal";
     /**
@@ -113,16 +117,16 @@
     };
     private final HwRemoteBinder.DeathRecipient mServiceManagerDeathRecipient =
             cookie -> {
-                Log.w(TAG, "IServiceManager died: cookie=" + cookie);
                 synchronized (mLock) {
+                    Log.w(TAG, "IServiceManager died: cookie=" + cookie);
                     supplicantServiceDiedHandler();
                     mIServiceManager = null; // Will need to register a new ServiceNotification
                 }
             };
     private final HwRemoteBinder.DeathRecipient mSupplicantDeathRecipient =
             cookie -> {
-                Log.w(TAG, "ISupplicant/ISupplicantStaIface died: cookie=" + cookie);
                 synchronized (mLock) {
+                    Log.w(TAG, "ISupplicant/ISupplicantStaIface died: cookie=" + cookie);
                     supplicantServiceDiedHandler();
                 }
             };
@@ -145,23 +149,27 @@
      * @param enable true to enable, false to disable.
      */
     void enableVerboseLogging(boolean enable) {
-        mVerboseLoggingEnabled = enable;
+        synchronized (mLock) {
+            mVerboseLoggingEnabled = enable;
+        }
     }
 
     private boolean linkToServiceManagerDeath() {
-        if (mIServiceManager == null) return false;
-        try {
-            if (!mIServiceManager.linkToDeath(mServiceManagerDeathRecipient, 0)) {
-                Log.wtf(TAG, "Error on linkToDeath on IServiceManager");
-                supplicantServiceDiedHandler();
-                mIServiceManager = null; // Will need to register a new ServiceNotification
+        synchronized (mLock) {
+            if (mIServiceManager == null) return false;
+            try {
+                if (!mIServiceManager.linkToDeath(mServiceManagerDeathRecipient, 0)) {
+                    Log.wtf(TAG, "Error on linkToDeath on IServiceManager");
+                    supplicantServiceDiedHandler();
+                    mIServiceManager = null; // Will need to register a new ServiceNotification
+                    return false;
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "IServiceManager.linkToDeath exception", e);
                 return false;
             }
-        } catch (RemoteException e) {
-            Log.e(TAG, "IServiceManager.linkToDeath exception", e);
-            return false;
+            return true;
         }
-        return true;
     }
 
     /**
@@ -170,8 +178,10 @@
      * @return true if the service notification was successfully registered
      */
     public boolean initialize() {
-        if (mVerboseLoggingEnabled) Log.i(TAG, "Registering ISupplicant service ready callback.");
         synchronized (mLock) {
+            if (mVerboseLoggingEnabled) {
+                Log.i(TAG, "Registering ISupplicant service ready callback.");
+            }
             mISupplicant = null;
             mISupplicantStaIface = null;
             if (mIServiceManager != null) {
@@ -207,18 +217,20 @@
     }
 
     private boolean linkToSupplicantDeath() {
-        if (mISupplicant == null) return false;
-        try {
-            if (!mISupplicant.linkToDeath(mSupplicantDeathRecipient, 0)) {
-                Log.wtf(TAG, "Error on linkToDeath on ISupplicant");
-                supplicantServiceDiedHandler();
+        synchronized (mLock) {
+            if (mISupplicant == null) return false;
+            try {
+                if (!mISupplicant.linkToDeath(mSupplicantDeathRecipient, 0)) {
+                    Log.wtf(TAG, "Error on linkToDeath on ISupplicant");
+                    supplicantServiceDiedHandler();
+                    return false;
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicant.linkToDeath exception", e);
                 return false;
             }
-        } catch (RemoteException e) {
-            Log.e(TAG, "ISupplicant.linkToDeath exception", e);
-            return false;
+            return true;
         }
-        return true;
     }
 
     private boolean initSupplicantService() {
@@ -241,25 +253,29 @@
     }
 
     private boolean linkToSupplicantStaIfaceDeath() {
-        if (mISupplicantStaIface == null) return false;
-        try {
-            if (!mISupplicantStaIface.linkToDeath(mSupplicantDeathRecipient, 0)) {
-                Log.wtf(TAG, "Error on linkToDeath on ISupplicantStaIface");
-                supplicantServiceDiedHandler();
+        synchronized (mLock) {
+            if (mISupplicantStaIface == null) return false;
+            try {
+                if (!mISupplicantStaIface.linkToDeath(mSupplicantDeathRecipient, 0)) {
+                    Log.wtf(TAG, "Error on linkToDeath on ISupplicantStaIface");
+                    supplicantServiceDiedHandler();
+                    return false;
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantStaIface.linkToDeath exception", e);
                 return false;
             }
-        } catch (RemoteException e) {
-            Log.e(TAG, "ISupplicantStaIface.linkToDeath exception", e);
-            return false;
+            return true;
         }
-        return true;
     }
 
     private int getCurrentNetworkId() {
-        if (mCurrentNetworkLocalConfig == null) {
-            return WifiConfiguration.INVALID_NETWORK_ID;
+        synchronized (mLock) {
+            if (mCurrentNetworkLocalConfig == null) {
+                return WifiConfiguration.INVALID_NETWORK_ID;
+            }
+            return mCurrentNetworkLocalConfig.networkId;
         }
-        return mCurrentNetworkLocalConfig.networkId;
     }
 
     private boolean initSupplicantStaIface() {
@@ -332,29 +348,39 @@
      * Signals whether Initialization completed successfully.
      */
     public boolean isInitializationStarted() {
-        return mIServiceManager != null;
+        synchronized (mLock) {
+            return mIServiceManager != null;
+        }
     }
 
     /**
      * Signals whether Initialization completed successfully.
      */
     public boolean isInitializationComplete() {
-        return mISupplicantStaIface != null;
+        synchronized (mLock) {
+            return mISupplicantStaIface != null;
+        }
     }
 
     /**
      * Wrapper functions to access static HAL methods, created to be mockable in unit tests
      */
     protected IServiceManager getServiceManagerMockable() throws RemoteException {
-        return IServiceManager.getService();
+        synchronized (mLock) {
+            return IServiceManager.getService();
+        }
     }
 
     protected ISupplicant getSupplicantMockable() throws RemoteException {
-        return ISupplicant.getService();
+        synchronized (mLock) {
+            return ISupplicant.getService();
+        }
     }
 
     protected ISupplicantStaIface getStaIfaceMockable(ISupplicantIface iface) {
-        return ISupplicantStaIface.asInterface(iface.asBinder());
+        synchronized (mLock) {
+            return ISupplicantStaIface.asInterface(iface.asBinder());
+        }
     }
 
     /**
@@ -366,30 +392,32 @@
      */
     private Pair<SupplicantStaNetworkHal, WifiConfiguration>
             addNetworkAndSaveConfig(WifiConfiguration config) {
-        logi("addSupplicantStaNetwork via HIDL");
-        if (config == null) {
-            loge("Cannot add NULL network!");
-            return null;
-        }
-        SupplicantStaNetworkHal network = addNetwork();
-        if (network == null) {
-            loge("Failed to add a network!");
-            return null;
-        }
-        boolean saveSuccess = false;
-        try {
-            saveSuccess = network.saveWifiConfiguration(config);
-        } catch (IllegalArgumentException e) {
-            Log.e(TAG, "Exception while saving config params: " + config, e);
-        }
-        if (!saveSuccess) {
-            loge("Failed to save variables for: " + config.configKey());
-            if (!removeAllNetworks()) {
-                loge("Failed to remove all networks on failure.");
+        synchronized (mLock) {
+            logi("addSupplicantStaNetwork via HIDL");
+            if (config == null) {
+                loge("Cannot add NULL network!");
+                return null;
             }
-            return null;
+            SupplicantStaNetworkHal network = addNetwork();
+            if (network == null) {
+                loge("Failed to add a network!");
+                return null;
+            }
+            boolean saveSuccess = false;
+            try {
+                saveSuccess = network.saveWifiConfiguration(config);
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Exception while saving config params: " + config, e);
+            }
+            if (!saveSuccess) {
+                loge("Failed to save variables for: " + config.configKey());
+                if (!removeAllNetworks()) {
+                    loge("Failed to remove all networks on failure.");
+                }
+                return null;
+            }
+            return new Pair(network, new WifiConfiguration(config));
         }
-        return new Pair(network, new WifiConfiguration(config));
     }
 
     /**
@@ -403,45 +431,33 @@
      * @return {@code true} if it succeeds, {@code false} otherwise
      */
     public boolean connectToNetwork(@NonNull WifiConfiguration config) {
-        logd("connectToNetwork " + config.configKey());
-        if (WifiConfigurationUtil.isSameNetwork(config, mCurrentNetworkLocalConfig)) {
-            String networkSelectionBSSID = config.getNetworkSelectionStatus()
-                    .getNetworkSelectionBSSID();
-            String networkSelectionBSSIDCurrent =
-                    mCurrentNetworkLocalConfig.getNetworkSelectionStatus()
-                            .getNetworkSelectionBSSID();
-            if (Objects.equals(networkSelectionBSSID, networkSelectionBSSIDCurrent)) {
+        synchronized (mLock) {
+            logd("connectToNetwork " + config.configKey());
+            if (WifiConfigurationUtil.isSameNetwork(config, mCurrentNetworkLocalConfig)) {
                 logd("Network is already saved, will not trigger remove and add operation.");
             } else {
-                logd("Network is already saved, but need to update BSSID.");
-                if (!setCurrentNetworkBssid(
-                        config.getNetworkSelectionStatus().getNetworkSelectionBSSID())) {
-                    loge("Failed to set current network BSSID.");
+                mCurrentNetworkRemoteHandle = null;
+                mCurrentNetworkLocalConfig = null;
+                if (!removeAllNetworks()) {
+                    loge("Failed to remove existing networks");
                     return false;
                 }
-                mCurrentNetworkLocalConfig = new WifiConfiguration(config);
+                Pair<SupplicantStaNetworkHal, WifiConfiguration> pair =
+                        addNetworkAndSaveConfig(config);
+                if (pair == null) {
+                    loge("Failed to add/save network configuration: " + config.configKey());
+                    return false;
+                }
+                mCurrentNetworkRemoteHandle = pair.first;
+                mCurrentNetworkLocalConfig = pair.second;
             }
-        } else {
-            mCurrentNetworkRemoteHandle = null;
-            mCurrentNetworkLocalConfig = null;
-            if (!removeAllNetworks()) {
-                loge("Failed to remove existing networks");
-                return false;
-            }
-            Pair<SupplicantStaNetworkHal, WifiConfiguration> pair = addNetworkAndSaveConfig(config);
-            if (pair == null) {
-                loge("Failed to add/save network configuration: " + config.configKey());
-                return false;
-            }
-            mCurrentNetworkRemoteHandle = pair.first;
-            mCurrentNetworkLocalConfig = pair.second;
-        }
 
-        if (!mCurrentNetworkRemoteHandle.select()) {
-            loge("Failed to select network configuration: " + config.configKey());
-            return false;
+            if (!mCurrentNetworkRemoteHandle.select()) {
+                loge("Failed to select network configuration: " + config.configKey());
+                return false;
+            }
+            return true;
         }
-        return true;
     }
 
     /**
@@ -457,22 +473,24 @@
      * @return {@code true} if it succeeds, {@code false} otherwise
      */
     public boolean roamToNetwork(WifiConfiguration config) {
-        if (getCurrentNetworkId() != config.networkId) {
-            Log.w(TAG, "Cannot roam to a different network, initiate new connection. "
-                    + "Current network ID: " + getCurrentNetworkId());
-            return connectToNetwork(config);
+        synchronized (mLock) {
+            if (getCurrentNetworkId() != config.networkId) {
+                Log.w(TAG, "Cannot roam to a different network, initiate new connection. "
+                        + "Current network ID: " + getCurrentNetworkId());
+                return connectToNetwork(config);
+            }
+            String bssid = config.getNetworkSelectionStatus().getNetworkSelectionBSSID();
+            logd("roamToNetwork" + config.configKey() + " (bssid " + bssid + ")");
+            if (!mCurrentNetworkRemoteHandle.setBssid(bssid)) {
+                loge("Failed to set new bssid on network: " + config.configKey());
+                return false;
+            }
+            if (!reassociate()) {
+                loge("Failed to trigger reassociate");
+                return false;
+            }
+            return true;
         }
-        String bssid = config.getNetworkSelectionStatus().getNetworkSelectionBSSID();
-        logd("roamToNetwork" + config.configKey() + " (bssid " + bssid + ")");
-        if (!mCurrentNetworkRemoteHandle.setBssid(bssid)) {
-            loge("Failed to set new bssid on network: " + config.configKey());
-            return false;
-        }
-        if (!reassociate()) {
-            loge("Failed to trigger reassociate");
-            return false;
-        }
-        return true;
     }
 
     /**
@@ -485,45 +503,48 @@
      */
     public boolean loadNetworks(Map<String, WifiConfiguration> configs,
                                 SparseArray<Map<String, String>> networkExtras) {
-        List<Integer> networkIds = listNetworks();
-        if (networkIds == null) {
-            Log.e(TAG, "Failed to list networks");
-            return false;
-        }
-        for (Integer networkId : networkIds) {
-            SupplicantStaNetworkHal network = getNetwork(networkId);
-            if (network == null) {
-                Log.e(TAG, "Failed to get network with ID: " + networkId);
+        synchronized (mLock) {
+            List<Integer> networkIds = listNetworks();
+            if (networkIds == null) {
+                Log.e(TAG, "Failed to list networks");
                 return false;
             }
-            WifiConfiguration config = new WifiConfiguration();
-            Map<String, String> networkExtra = new HashMap<>();
-            boolean loadSuccess = false;
-            try {
-                loadSuccess = network.loadWifiConfiguration(config, networkExtra);
-            } catch (IllegalArgumentException e) {
-                Log.wtf(TAG, "Exception while loading config params: " + config, e);
-            }
-            if (!loadSuccess) {
-                Log.e(TAG, "Failed to load wifi configuration for network with ID: " + networkId
-                        + ". Skipping...");
-                continue;
-            }
-            // Set the default IP assignments.
-            config.setIpAssignment(IpConfiguration.IpAssignment.DHCP);
-            config.setProxySettings(IpConfiguration.ProxySettings.NONE);
+            for (Integer networkId : networkIds) {
+                SupplicantStaNetworkHal network = getNetwork(networkId);
+                if (network == null) {
+                    Log.e(TAG, "Failed to get network with ID: " + networkId);
+                    return false;
+                }
+                WifiConfiguration config = new WifiConfiguration();
+                Map<String, String> networkExtra = new HashMap<>();
+                boolean loadSuccess = false;
+                try {
+                    loadSuccess = network.loadWifiConfiguration(config, networkExtra);
+                } catch (IllegalArgumentException e) {
+                    Log.wtf(TAG, "Exception while loading config params: " + config, e);
+                }
+                if (!loadSuccess) {
+                    Log.e(TAG, "Failed to load wifi configuration for network with ID: " + networkId
+                            + ". Skipping...");
+                    continue;
+                }
+                // Set the default IP assignments.
+                config.setIpAssignment(IpConfiguration.IpAssignment.DHCP);
+                config.setProxySettings(IpConfiguration.ProxySettings.NONE);
 
-            networkExtras.put(networkId, networkExtra);
-            String configKey = networkExtra.get(SupplicantStaNetworkHal.ID_STRING_KEY_CONFIG_KEY);
-            final WifiConfiguration duplicateConfig = configs.put(configKey, config);
-            if (duplicateConfig != null) {
-                // The network is already known. Overwrite the duplicate entry.
-                Log.i(TAG, "Replacing duplicate network: " + duplicateConfig.networkId);
-                removeNetwork(duplicateConfig.networkId);
-                networkExtras.remove(duplicateConfig.networkId);
+                networkExtras.put(networkId, networkExtra);
+                String configKey =
+                        networkExtra.get(SupplicantStaNetworkHal.ID_STRING_KEY_CONFIG_KEY);
+                final WifiConfiguration duplicateConfig = configs.put(configKey, config);
+                if (duplicateConfig != null) {
+                    // The network is already known. Overwrite the duplicate entry.
+                    Log.i(TAG, "Replacing duplicate network: " + duplicateConfig.networkId);
+                    removeNetwork(duplicateConfig.networkId);
+                    networkExtras.remove(duplicateConfig.networkId);
+                }
             }
+            return true;
         }
-        return true;
     }
 
     /**
@@ -557,12 +578,12 @@
                     return false;
                 }
             }
+            // Reset current network info.  Probably not needed once we add support to remove/reset
+            // current network on receiving disconnection event from supplicant (b/32898136).
+            mCurrentNetworkLocalConfig = null;
+            mCurrentNetworkRemoteHandle = null;
+            return true;
         }
-        // Reset current network info.  Probably not needed once we add support to remove/reset
-        // current network on receiving disconnection event from supplicant (b/32898136).
-        mCurrentNetworkLocalConfig = null;
-        mCurrentNetworkRemoteHandle = null;
-        return true;
     }
 
     /**
@@ -572,8 +593,10 @@
      * @return true if succeeds, false otherwise.
      */
     public boolean setCurrentNetworkBssid(String bssidStr) {
-        if (mCurrentNetworkRemoteHandle == null) return false;
-        return mCurrentNetworkRemoteHandle.setBssid(bssidStr);
+        synchronized (mLock) {
+            if (mCurrentNetworkRemoteHandle == null) return false;
+            return mCurrentNetworkRemoteHandle.setBssid(bssidStr);
+        }
     }
 
     /**
@@ -582,8 +605,10 @@
      * @return Hex string corresponding to the WPS NFC token.
      */
     public String getCurrentNetworkWpsNfcConfigurationToken() {
-        if (mCurrentNetworkRemoteHandle == null) return null;
-        return mCurrentNetworkRemoteHandle.getWpsNfcConfigurationToken();
+        synchronized (mLock) {
+            if (mCurrentNetworkRemoteHandle == null) return null;
+            return mCurrentNetworkRemoteHandle.getWpsNfcConfigurationToken();
+        }
     }
 
     /**
@@ -592,8 +617,10 @@
      * @return anonymous identity string if succeeds, null otherwise.
      */
     public String getCurrentNetworkEapAnonymousIdentity() {
-        if (mCurrentNetworkRemoteHandle == null) return null;
-        return mCurrentNetworkRemoteHandle.fetchEapAnonymousIdentity();
+        synchronized (mLock) {
+            if (mCurrentNetworkRemoteHandle == null) return null;
+            return mCurrentNetworkRemoteHandle.fetchEapAnonymousIdentity();
+        }
     }
 
     /**
@@ -603,8 +630,10 @@
      * @return true if succeeds, false otherwise.
      */
     public boolean sendCurrentNetworkEapIdentityResponse(String identityStr) {
-        if (mCurrentNetworkRemoteHandle == null) return false;
-        return mCurrentNetworkRemoteHandle.sendNetworkEapIdentityResponse(identityStr);
+        synchronized (mLock) {
+            if (mCurrentNetworkRemoteHandle == null) return false;
+            return mCurrentNetworkRemoteHandle.sendNetworkEapIdentityResponse(identityStr);
+        }
     }
 
     /**
@@ -614,8 +643,10 @@
      * @return true if succeeds, false otherwise.
      */
     public boolean sendCurrentNetworkEapSimGsmAuthResponse(String paramsStr) {
-        if (mCurrentNetworkRemoteHandle == null) return false;
-        return mCurrentNetworkRemoteHandle.sendNetworkEapSimGsmAuthResponse(paramsStr);
+        synchronized (mLock) {
+            if (mCurrentNetworkRemoteHandle == null) return false;
+            return mCurrentNetworkRemoteHandle.sendNetworkEapSimGsmAuthResponse(paramsStr);
+        }
     }
 
     /**
@@ -624,8 +655,10 @@
      * @return true if succeeds, false otherwise.
      */
     public boolean sendCurrentNetworkEapSimGsmAuthFailure() {
-        if (mCurrentNetworkRemoteHandle == null) return false;
-        return mCurrentNetworkRemoteHandle.sendNetworkEapSimGsmAuthFailure();
+        synchronized (mLock) {
+            if (mCurrentNetworkRemoteHandle == null) return false;
+            return mCurrentNetworkRemoteHandle.sendNetworkEapSimGsmAuthFailure();
+        }
     }
 
     /**
@@ -635,8 +668,10 @@
      * @return true if succeeds, false otherwise.
      */
     public boolean sendCurrentNetworkEapSimUmtsAuthResponse(String paramsStr) {
-        if (mCurrentNetworkRemoteHandle == null) return false;
-        return mCurrentNetworkRemoteHandle.sendNetworkEapSimUmtsAuthResponse(paramsStr);
+        synchronized (mLock) {
+            if (mCurrentNetworkRemoteHandle == null) return false;
+            return mCurrentNetworkRemoteHandle.sendNetworkEapSimUmtsAuthResponse(paramsStr);
+        }
     }
 
     /**
@@ -646,8 +681,10 @@
      * @return true if succeeds, false otherwise.
      */
     public boolean sendCurrentNetworkEapSimUmtsAutsResponse(String paramsStr) {
-        if (mCurrentNetworkRemoteHandle == null) return false;
-        return mCurrentNetworkRemoteHandle.sendNetworkEapSimUmtsAutsResponse(paramsStr);
+        synchronized (mLock) {
+            if (mCurrentNetworkRemoteHandle == null) return false;
+            return mCurrentNetworkRemoteHandle.sendNetworkEapSimUmtsAutsResponse(paramsStr);
+        }
     }
 
     /**
@@ -656,8 +693,10 @@
      * @return true if succeeds, false otherwise.
      */
     public boolean sendCurrentNetworkEapSimUmtsAuthFailure() {
-        if (mCurrentNetworkRemoteHandle == null) return false;
-        return mCurrentNetworkRemoteHandle.sendNetworkEapSimUmtsAuthFailure();
+        synchronized (mLock) {
+            if (mCurrentNetworkRemoteHandle == null) return false;
+            return mCurrentNetworkRemoteHandle.sendNetworkEapSimUmtsAuthFailure();
+        }
     }
 
     /**
@@ -717,13 +756,15 @@
      */
     protected SupplicantStaNetworkHal getStaNetworkMockable(
             ISupplicantStaNetwork iSupplicantStaNetwork) {
-        SupplicantStaNetworkHal network =
-                new SupplicantStaNetworkHal(iSupplicantStaNetwork, mIfaceName, mContext,
-                        mWifiMonitor);
-        if (network != null) {
-            network.enableVerboseLogging(mVerboseLoggingEnabled);
+        synchronized (mLock) {
+            SupplicantStaNetworkHal network =
+                    new SupplicantStaNetworkHal(iSupplicantStaNetwork, mIfaceName, mContext,
+                            mWifiMonitor);
+            if (network != null) {
+                network.enableVerboseLogging(mVerboseLoggingEnabled);
+            }
+            return network;
         }
-        return network;
     }
 
     /**
@@ -819,25 +860,27 @@
      * @return true if request is sent successfully, false otherwise.
      */
     public boolean setWpsDeviceType(String typeStr) {
-        try {
-            Matcher match = WPS_DEVICE_TYPE_PATTERN.matcher(typeStr);
-            if (!match.find() || match.groupCount() != 3) {
-                Log.e(TAG, "Malformed WPS device type " + typeStr);
+        synchronized (mLock) {
+            try {
+                Matcher match = WPS_DEVICE_TYPE_PATTERN.matcher(typeStr);
+                if (!match.find() || match.groupCount() != 3) {
+                    Log.e(TAG, "Malformed WPS device type " + typeStr);
+                    return false;
+                }
+                short categ = Short.parseShort(match.group(1));
+                byte[] oui = NativeUtil.hexStringToByteArray(match.group(2));
+                short subCateg = Short.parseShort(match.group(3));
+
+                byte[] bytes = new byte[8];
+                ByteBuffer byteBuffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
+                byteBuffer.putShort(categ);
+                byteBuffer.put(oui);
+                byteBuffer.putShort(subCateg);
+                return setWpsDeviceType(bytes);
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Illegal argument " + typeStr, e);
                 return false;
             }
-            short categ = Short.parseShort(match.group(1));
-            byte[] oui = NativeUtil.hexStringToByteArray(match.group(2));
-            short subCateg = Short.parseShort(match.group(3));
-
-            byte[] bytes = new byte[8];
-            ByteBuffer byteBuffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
-            byteBuffer.putShort(categ);
-            byteBuffer.put(oui);
-            byteBuffer.putShort(subCateg);
-            return setWpsDeviceType(bytes);
-        } catch (IllegalArgumentException e) {
-            Log.e(TAG, "Illegal argument " + typeStr, e);
-            return false;
         }
     }
 
@@ -942,12 +985,14 @@
      * @return true if request is sent successfully, false otherwise.
      */
     public boolean setWpsConfigMethods(String configMethodsStr) {
-        short configMethodsMask = 0;
-        String[] configMethodsStrArr = configMethodsStr.split("\\s+");
-        for (int i = 0; i < configMethodsStrArr.length; i++) {
-            configMethodsMask |= stringToWpsConfigMethod(configMethodsStrArr[i]);
+        synchronized (mLock) {
+            short configMethodsMask = 0;
+            String[] configMethodsStrArr = configMethodsStr.split("\\s+");
+            for (int i = 0; i < configMethodsStrArr.length; i++) {
+                configMethodsMask |= stringToWpsConfigMethod(configMethodsStrArr[i]);
+            }
+            return setWpsConfigMethods(configMethodsMask);
         }
-        return setWpsConfigMethods(configMethodsMask);
     }
 
     private boolean setWpsConfigMethods(short configMethods) {
@@ -1048,11 +1093,13 @@
      * @return true if request is sent successfully, false otherwise.
      */
     public boolean initiateTdlsDiscover(String macAddress) {
-        try {
-            return initiateTdlsDiscover(NativeUtil.macAddressToByteArray(macAddress));
-        } catch (IllegalArgumentException e) {
-            Log.e(TAG, "Illegal argument " + macAddress, e);
-            return false;
+        synchronized (mLock) {
+            try {
+                return initiateTdlsDiscover(NativeUtil.macAddressToByteArray(macAddress));
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Illegal argument " + macAddress, e);
+                return false;
+            }
         }
     }
     /** See ISupplicantStaIface.hal for documentation */
@@ -1077,11 +1124,13 @@
      * @return true if request is sent successfully, false otherwise.
      */
     public boolean initiateTdlsSetup(String macAddress) {
-        try {
-            return initiateTdlsSetup(NativeUtil.macAddressToByteArray(macAddress));
-        } catch (IllegalArgumentException e) {
-            Log.e(TAG, "Illegal argument " + macAddress, e);
-            return false;
+        synchronized (mLock) {
+            try {
+                return initiateTdlsSetup(NativeUtil.macAddressToByteArray(macAddress));
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Illegal argument " + macAddress, e);
+                return false;
+            }
         }
     }
     /** See ISupplicantStaIface.hal for documentation */
@@ -1105,11 +1154,13 @@
      * @return true if request is sent successfully, false otherwise.
      */
     public boolean initiateTdlsTeardown(String macAddress) {
-        try {
-            return initiateTdlsTeardown(NativeUtil.macAddressToByteArray(macAddress));
-        } catch (IllegalArgumentException e) {
-            Log.e(TAG, "Illegal argument " + macAddress, e);
-            return false;
+        synchronized (mLock) {
+            try {
+                return initiateTdlsTeardown(NativeUtil.macAddressToByteArray(macAddress));
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Illegal argument " + macAddress, e);
+                return false;
+            }
         }
     }
 
@@ -1138,12 +1189,14 @@
      */
     public boolean initiateAnqpQuery(String bssid, ArrayList<Short> infoElements,
                                      ArrayList<Integer> hs20SubTypes) {
-        try {
-            return initiateAnqpQuery(
-                    NativeUtil.macAddressToByteArray(bssid), infoElements, hs20SubTypes);
-        } catch (IllegalArgumentException e) {
-            Log.e(TAG, "Illegal argument " + bssid, e);
-            return false;
+        synchronized (mLock) {
+            try {
+                return initiateAnqpQuery(
+                        NativeUtil.macAddressToByteArray(bssid), infoElements, hs20SubTypes);
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Illegal argument " + bssid, e);
+                return false;
+            }
         }
     }
 
@@ -1172,11 +1225,13 @@
      * @return true if request is sent successfully, false otherwise.
      */
     public boolean initiateHs20IconQuery(String bssid, String fileName) {
-        try {
-            return initiateHs20IconQuery(NativeUtil.macAddressToByteArray(bssid), fileName);
-        } catch (IllegalArgumentException e) {
-            Log.e(TAG, "Illegal argument " + bssid, e);
-            return false;
+        synchronized (mLock) {
+            try {
+                return initiateHs20IconQuery(NativeUtil.macAddressToByteArray(bssid), fileName);
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Illegal argument " + bssid, e);
+                return false;
+            }
         }
     }
 
@@ -1266,19 +1321,21 @@
      * @return true if request is sent successfully, false otherwise.
      */
     public boolean addRxFilter(int type) {
-        byte halType;
-        switch (type) {
-            case WifiNative.RX_FILTER_TYPE_V4_MULTICAST:
-                halType = ISupplicantStaIface.RxFilterType.V4_MULTICAST;
-                break;
-            case WifiNative.RX_FILTER_TYPE_V6_MULTICAST:
-                halType = ISupplicantStaIface.RxFilterType.V6_MULTICAST;
-                break;
-            default:
-                Log.e(TAG, "Invalid Rx Filter type: " + type);
-                return false;
+        synchronized (mLock) {
+            byte halType;
+            switch (type) {
+                case WifiNative.RX_FILTER_TYPE_V4_MULTICAST:
+                    halType = ISupplicantStaIface.RxFilterType.V4_MULTICAST;
+                    break;
+                case WifiNative.RX_FILTER_TYPE_V6_MULTICAST:
+                    halType = ISupplicantStaIface.RxFilterType.V6_MULTICAST;
+                    break;
+                default:
+                    Log.e(TAG, "Invalid Rx Filter type: " + type);
+                    return false;
+            }
+            return addRxFilter(halType);
         }
-        return addRxFilter(halType);
     }
 
     public boolean addRxFilter(byte type) {
@@ -1303,19 +1360,21 @@
      * @return true if request is sent successfully, false otherwise.
      */
     public boolean removeRxFilter(int type) {
-        byte halType;
-        switch (type) {
-            case WifiNative.RX_FILTER_TYPE_V4_MULTICAST:
-                halType = ISupplicantStaIface.RxFilterType.V4_MULTICAST;
-                break;
-            case WifiNative.RX_FILTER_TYPE_V6_MULTICAST:
-                halType = ISupplicantStaIface.RxFilterType.V6_MULTICAST;
-                break;
-            default:
-                Log.e(TAG, "Invalid Rx Filter type: " + type);
-                return false;
+        synchronized (mLock) {
+            byte halType;
+            switch (type) {
+                case WifiNative.RX_FILTER_TYPE_V4_MULTICAST:
+                    halType = ISupplicantStaIface.RxFilterType.V4_MULTICAST;
+                    break;
+                case WifiNative.RX_FILTER_TYPE_V6_MULTICAST:
+                    halType = ISupplicantStaIface.RxFilterType.V6_MULTICAST;
+                    break;
+                default:
+                    Log.e(TAG, "Invalid Rx Filter type: " + type);
+                    return false;
+            }
+            return removeRxFilter(halType);
         }
-        return removeRxFilter(halType);
     }
 
     public boolean removeRxFilter(byte type) {
@@ -1341,22 +1400,24 @@
      * @return true if request is sent successfully, false otherwise.
      */
     public boolean setBtCoexistenceMode(int mode) {
-        byte halMode;
-        switch (mode) {
-            case WifiNative.BLUETOOTH_COEXISTENCE_MODE_ENABLED:
-                halMode = ISupplicantStaIface.BtCoexistenceMode.ENABLED;
-                break;
-            case WifiNative.BLUETOOTH_COEXISTENCE_MODE_DISABLED:
-                halMode = ISupplicantStaIface.BtCoexistenceMode.DISABLED;
-                break;
-            case WifiNative.BLUETOOTH_COEXISTENCE_MODE_SENSE:
-                halMode = ISupplicantStaIface.BtCoexistenceMode.SENSE;
-                break;
-            default:
-                Log.e(TAG, "Invalid Bt Coex mode: " + mode);
-                return false;
+        synchronized (mLock) {
+            byte halMode;
+            switch (mode) {
+                case WifiNative.BLUETOOTH_COEXISTENCE_MODE_ENABLED:
+                    halMode = ISupplicantStaIface.BtCoexistenceMode.ENABLED;
+                    break;
+                case WifiNative.BLUETOOTH_COEXISTENCE_MODE_DISABLED:
+                    halMode = ISupplicantStaIface.BtCoexistenceMode.DISABLED;
+                    break;
+                case WifiNative.BLUETOOTH_COEXISTENCE_MODE_SENSE:
+                    halMode = ISupplicantStaIface.BtCoexistenceMode.SENSE;
+                    break;
+                default:
+                    Log.e(TAG, "Invalid Bt Coex mode: " + mode);
+                    return false;
+            }
+            return setBtCoexistenceMode(halMode);
         }
-        return setBtCoexistenceMode(halMode);
     }
 
     private boolean setBtCoexistenceMode(byte mode) {
@@ -1420,8 +1481,10 @@
      * @return true if request is sent successfully, false otherwise.
      */
     public boolean setCountryCode(String codeStr) {
-        if (TextUtils.isEmpty(codeStr)) return false;
-        return setCountryCode(NativeUtil.stringToByteArray(codeStr));
+        synchronized (mLock) {
+            if (TextUtils.isEmpty(codeStr)) return false;
+            return setCountryCode(NativeUtil.stringToByteArray(codeStr));
+        }
     }
 
     /** See ISupplicantStaIface.hal for documentation */
@@ -1447,12 +1510,14 @@
      * @return true if request is sent successfully, false otherwise.
      */
     public boolean startWpsRegistrar(String bssidStr, String pin) {
-        if (TextUtils.isEmpty(bssidStr) || TextUtils.isEmpty(pin)) return false;
-        try {
-            return startWpsRegistrar(NativeUtil.macAddressToByteArray(bssidStr), pin);
-        } catch (IllegalArgumentException e) {
-            Log.e(TAG, "Illegal argument " + bssidStr, e);
-            return false;
+        synchronized (mLock) {
+            if (TextUtils.isEmpty(bssidStr) || TextUtils.isEmpty(pin)) return false;
+            try {
+                return startWpsRegistrar(NativeUtil.macAddressToByteArray(bssidStr), pin);
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Illegal argument " + bssidStr, e);
+                return false;
+            }
         }
     }
 
@@ -1478,11 +1543,13 @@
      * @return true if request is sent successfully, false otherwise.
      */
     public boolean startWpsPbc(String bssidStr) {
-        try {
-            return startWpsPbc(NativeUtil.macAddressToByteArray(bssidStr));
-        } catch (IllegalArgumentException e) {
-            Log.e(TAG, "Illegal argument " + bssidStr, e);
-            return false;
+        synchronized (mLock) {
+            try {
+                return startWpsPbc(NativeUtil.macAddressToByteArray(bssidStr));
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Illegal argument " + bssidStr, e);
+                return false;
+            }
         }
     }
 
@@ -1529,11 +1596,13 @@
      * @return new pin generated on success, null otherwise.
      */
     public String startWpsPinDisplay(String bssidStr) {
-        try {
-            return startWpsPinDisplay(NativeUtil.macAddressToByteArray(bssidStr));
-        } catch (IllegalArgumentException e) {
-            Log.e(TAG, "Illegal argument " + bssidStr, e);
-            return null;
+        synchronized (mLock) {
+            try {
+                return startWpsPinDisplay(NativeUtil.macAddressToByteArray(bssidStr));
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Illegal argument " + bssidStr, e);
+                return null;
+            }
         }
     }
 
@@ -1618,10 +1687,12 @@
      * @return true if request is sent successfully, false otherwise.
      */
     public boolean setLogLevel(boolean turnOnVerbose) {
-        int logLevel = turnOnVerbose
-                ? ISupplicant.DebugLevel.DEBUG
-                : ISupplicant.DebugLevel.INFO;
-        return setDebugParams(logLevel, false, false);
+        synchronized (mLock) {
+            int logLevel = turnOnVerbose
+                    ? ISupplicant.DebugLevel.DEBUG
+                    : ISupplicant.DebugLevel.INFO;
+            return setDebugParams(logLevel, false, false);
+        }
     }
 
     /** See ISupplicant.hal for documentation */
@@ -1648,10 +1719,12 @@
      * @return true if request is sent successfully, false otherwise.
      */
     public boolean setConcurrencyPriority(boolean isStaHigherPriority) {
-        if (isStaHigherPriority) {
-            return setConcurrencyPriority(IfaceType.STA);
-        } else {
-            return setConcurrencyPriority(IfaceType.P2P);
+        synchronized (mLock) {
+            if (isStaHigherPriority) {
+                return setConcurrencyPriority(IfaceType.STA);
+            } else {
+                return setConcurrencyPriority(IfaceType.P2P);
+            }
         }
     }
 
@@ -1674,22 +1747,26 @@
      * Returns false if Supplicant is null, and logs failure to call methodStr
      */
     private boolean checkSupplicantAndLogFailure(final String methodStr) {
-        if (mISupplicant == null) {
-            Log.e(TAG, "Can't call " + methodStr + ", ISupplicant is null");
-            return false;
+        synchronized (mLock) {
+            if (mISupplicant == null) {
+                Log.e(TAG, "Can't call " + methodStr + ", ISupplicant is null");
+                return false;
+            }
+            return true;
         }
-        return true;
     }
 
     /**
      * Returns false if SupplicantStaIface is null, and logs failure to call methodStr
      */
     private boolean checkSupplicantStaIfaceAndLogFailure(final String methodStr) {
-        if (mISupplicantStaIface == null) {
-            Log.e(TAG, "Can't call " + methodStr + ", ISupplicantStaIface is null");
-            return false;
+        synchronized (mLock) {
+            if (mISupplicantStaIface == null) {
+                Log.e(TAG, "Can't call " + methodStr + ", ISupplicantStaIface is null");
+                return false;
+            }
+            return true;
         }
-        return true;
     }
 
     /**
@@ -1698,15 +1775,17 @@
      */
     private boolean checkStatusAndLogFailure(SupplicantStatus status,
             final String methodStr) {
-        if (status.code != SupplicantStatusCode.SUCCESS) {
-            Log.e(TAG, "ISupplicantStaIface." + methodStr + " failed: "
-                    + supplicantStatusCodeToString(status.code) + ", " + status.debugMessage);
-            return false;
-        } else {
-            if (mVerboseLoggingEnabled) {
-                Log.d(TAG, "ISupplicantStaIface." + methodStr + " succeeded");
+        synchronized (mLock) {
+            if (status.code != SupplicantStatusCode.SUCCESS) {
+                Log.e(TAG, "ISupplicantStaIface." + methodStr + " failed: "
+                        + supplicantStatusCodeToString(status.code) + ", " + status.debugMessage);
+                return false;
+            } else {
+                if (mVerboseLoggingEnabled) {
+                    Log.d(TAG, "ISupplicantStaIface." + methodStr + " succeeded");
+                }
+                return true;
             }
-            return true;
         }
     }
 
@@ -1714,15 +1793,19 @@
      * Helper function to log callbacks.
      */
     private void logCallback(final String methodStr) {
-        if (mVerboseLoggingEnabled) {
-            Log.d(TAG, "ISupplicantStaIfaceCallback." + methodStr + " received");
+        synchronized (mLock) {
+            if (mVerboseLoggingEnabled) {
+                Log.d(TAG, "ISupplicantStaIfaceCallback." + methodStr + " received");
+            }
         }
     }
 
 
     private void handleRemoteException(RemoteException e, String methodStr) {
-        supplicantServiceDiedHandler();
-        Log.e(TAG, "ISupplicantStaIface." + methodStr + " failed with exception", e);
+        synchronized (mLock) {
+            supplicantServiceDiedHandler();
+            Log.e(TAG, "ISupplicantStaIface." + methodStr + " failed with exception", e);
+        }
     }
 
     /**
@@ -1851,15 +1934,17 @@
          */
         private ANQPElement parseAnqpElement(Constants.ANQPElementType infoID,
                                              ArrayList<Byte> payload) {
-            try {
-                return Constants.getANQPElementID(infoID) != null
-                        ? ANQPParser.parseElement(
-                        infoID, ByteBuffer.wrap(NativeUtil.byteArrayFromArrayList(payload)))
-                        : ANQPParser.parseHS20Element(
-                        infoID, ByteBuffer.wrap(NativeUtil.byteArrayFromArrayList(payload)));
-            } catch (IOException | BufferUnderflowException e) {
-                Log.e(TAG, "Failed parsing ANQP element payload: " + infoID, e);
-                return null;
+            synchronized (mLock) {
+                try {
+                    return Constants.getANQPElementID(infoID) != null
+                            ? ANQPParser.parseElement(
+                            infoID, ByteBuffer.wrap(NativeUtil.byteArrayFromArrayList(payload)))
+                            : ANQPParser.parseHS20Element(
+                            infoID, ByteBuffer.wrap(NativeUtil.byteArrayFromArrayList(payload)));
+                } catch (IOException | BufferUnderflowException e) {
+                    Log.e(TAG, "Failed parsing ANQP element payload: " + infoID, e);
+                    return null;
+                }
             }
         }
 
@@ -1873,28 +1958,34 @@
         private void addAnqpElementToMap(Map<Constants.ANQPElementType, ANQPElement> elementsMap,
                                          Constants.ANQPElementType infoID,
                                          ArrayList<Byte> payload) {
-            if (payload == null || payload.isEmpty()) return;
-            ANQPElement element = parseAnqpElement(infoID, payload);
-            if (element != null) {
-                elementsMap.put(infoID, element);
+            synchronized (mLock) {
+                if (payload == null || payload.isEmpty()) return;
+                ANQPElement element = parseAnqpElement(infoID, payload);
+                if (element != null) {
+                    elementsMap.put(infoID, element);
+                }
             }
         }
 
         @Override
         public void onNetworkAdded(int id) {
-            logCallback("onNetworkAdded");
+            synchronized (mLock) {
+                logCallback("onNetworkAdded");
+            }
         }
 
         @Override
         public void onNetworkRemoved(int id) {
-            logCallback("onNetworkRemoved");
+            synchronized (mLock) {
+                logCallback("onNetworkRemoved");
+            }
         }
 
         @Override
         public void onStateChanged(int newState, byte[/* 6 */] bssid, int id,
                                    ArrayList<Byte> ssid) {
-            logCallback("onStateChanged");
             synchronized (mLock) {
+                logCallback("onStateChanged");
                 SupplicantState newSupplicantState = supplicantHidlStateToFrameworkState(newState);
                 WifiSsid wifiSsid =
                         WifiSsid.createFromByteArray(NativeUtil.byteArrayFromArrayList(ssid));
@@ -1913,8 +2004,8 @@
         public void onAnqpQueryDone(byte[/* 6 */] bssid,
                                     ISupplicantStaIfaceCallback.AnqpData data,
                                     ISupplicantStaIfaceCallback.Hs20AnqpData hs20Data) {
-            logCallback("onAnqpQueryDone");
             synchronized (mLock) {
+                logCallback("onAnqpQueryDone");
                 Map<Constants.ANQPElementType, ANQPElement> elementsMap = new HashMap<>();
                 addAnqpElementToMap(elementsMap, ANQPVenueName, data.venueName);
                 addAnqpElementToMap(elementsMap, ANQPRoamingConsortium, data.roamingConsortium);
@@ -1935,8 +2026,8 @@
         @Override
         public void onHs20IconQueryDone(byte[/* 6 */] bssid, String fileName,
                                         ArrayList<Byte> data) {
-            logCallback("onHs20IconQueryDone");
             synchronized (mLock) {
+                logCallback("onHs20IconQueryDone");
                 mWifiMonitor.broadcastIconDoneEvent(
                         mIfaceName,
                         new IconEvent(NativeUtil.macAddressToLong(bssid), fileName, data.size(),
@@ -1946,8 +2037,8 @@
 
         @Override
         public void onHs20SubscriptionRemediation(byte[/* 6 */] bssid, byte osuMethod, String url) {
-            logCallback("onHs20SubscriptionRemediation");
             synchronized (mLock) {
+                logCallback("onHs20SubscriptionRemediation");
                 mWifiMonitor.broadcastWnmEvent(
                         mIfaceName,
                         new WnmData(NativeUtil.macAddressToLong(bssid), url, osuMethod));
@@ -1957,8 +2048,8 @@
         @Override
         public void onHs20DeauthImminentNotice(byte[/* 6 */] bssid, int reasonCode,
                                                int reAuthDelayInSec, String url) {
-            logCallback("onHs20DeauthImminentNotice");
             synchronized (mLock) {
+                logCallback("onHs20DeauthImminentNotice");
                 mWifiMonitor.broadcastWnmEvent(
                         mIfaceName,
                         new WnmData(NativeUtil.macAddressToLong(bssid), url,
@@ -1968,8 +2059,8 @@
 
         @Override
         public void onDisconnected(byte[/* 6 */] bssid, boolean locallyGenerated, int reasonCode) {
-            logCallback("onDisconnected");
             synchronized (mLock) {
+                logCallback("onDisconnected");
                 if (mVerboseLoggingEnabled) {
                     Log.e(TAG, "onDisconnected 4way=" + mStateIsFourway
                             + " locallyGenerated=" + locallyGenerated
@@ -1988,8 +2079,8 @@
 
         @Override
         public void onAssociationRejected(byte[/* 6 */] bssid, int statusCode, boolean timedOut) {
-            logCallback("onAssociationRejected");
             synchronized (mLock) {
+                logCallback("onAssociationRejected");
                 mWifiMonitor.broadcastAssociationRejectionEvent(mIfaceName, statusCode, timedOut,
                         NativeUtil.macAddressFromByteArray(bssid));
             }
@@ -1997,8 +2088,8 @@
 
         @Override
         public void onAuthenticationTimeout(byte[/* 6 */] bssid) {
-            logCallback("onAuthenticationTimeout");
             synchronized (mLock) {
+                logCallback("onAuthenticationTimeout");
                 mWifiMonitor.broadcastAuthenticationFailureEvent(
                         mIfaceName, WifiManager.ERROR_AUTH_FAILURE_TIMEOUT);
             }
@@ -2006,8 +2097,8 @@
 
         @Override
         public void onBssidChanged(byte reason, byte[/* 6 */] bssid) {
-            logCallback("onBssidChanged");
             synchronized (mLock) {
+                logCallback("onBssidChanged");
                 if (reason == BssidChangeReason.ASSOC_START) {
                     mWifiMonitor.broadcastTargetBssidEvent(
                             mIfaceName, NativeUtil.macAddressFromByteArray(bssid));
@@ -2020,8 +2111,8 @@
 
         @Override
         public void onEapFailure() {
-            logCallback("onEapFailure");
             synchronized (mLock) {
+                logCallback("onEapFailure");
                 mWifiMonitor.broadcastAuthenticationFailureEvent(
                         mIfaceName, WifiManager.ERROR_AUTH_FAILURE_EAP_FAILURE);
             }
@@ -2037,8 +2128,8 @@
 
         @Override
         public void onWpsEventFail(byte[/* 6 */] bssid, short configError, short errorInd) {
-            logCallback("onWpsEventFail");
             synchronized (mLock) {
+                logCallback("onWpsEventFail");
                 if (configError == WpsConfigError.MSG_TIMEOUT
                         && errorInd == WpsErrorIndication.NO_ERROR) {
                     mWifiMonitor.broadcastWpsTimeoutEvent(mIfaceName);
@@ -2050,32 +2141,36 @@
 
         @Override
         public void onWpsEventPbcOverlap() {
-            logCallback("onWpsEventPbcOverlap");
             synchronized (mLock) {
+                logCallback("onWpsEventPbcOverlap");
                 mWifiMonitor.broadcastWpsOverlapEvent(mIfaceName);
             }
         }
 
         @Override
         public void onExtRadioWorkStart(int id) {
-            logCallback("onExtRadioWorkStart");
+            synchronized (mLock) {
+                logCallback("onExtRadioWorkStart");
+            }
         }
 
         @Override
         public void onExtRadioWorkTimeout(int id) {
-            logCallback("onExtRadioWorkTimeout");
+            synchronized (mLock) {
+                logCallback("onExtRadioWorkTimeout");
+            }
         }
     }
 
-    private void logd(String s) {
+    private static void logd(String s) {
         Log.d(TAG, s);
     }
 
-    private void logi(String s) {
+    private static void logi(String s) {
         Log.i(TAG, s);
     }
 
-    private void loge(String s) {
+    private static void loge(String s) {
         Log.e(TAG, s);
     }
 }
diff --git a/service/java/com/android/server/wifi/SupplicantStaNetworkHal.java b/service/java/com/android/server/wifi/SupplicantStaNetworkHal.java
index 6e7d98c..61ec9b3 100644
--- a/service/java/com/android/server/wifi/SupplicantStaNetworkHal.java
+++ b/service/java/com/android/server/wifi/SupplicantStaNetworkHal.java
@@ -46,13 +46,18 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import javax.annotation.concurrent.ThreadSafe;
+
 
 /**
  * Wrapper class for ISupplicantStaNetwork HAL calls. Gets and sets supplicant sta network variables
  * and interacts with networks.
  * Public fields should be treated as invalid until their 'get' method is called, which will set the
  * value if it returns true
+ * To maintain thread-safety, the locking protocol is that every non-static method (regardless of
+ * access level) acquires mLock.
  */
+@ThreadSafe
 public class SupplicantStaNetworkHal {
     private static final String TAG = "SupplicantStaNetworkHal";
     @VisibleForTesting
@@ -137,7 +142,9 @@
      * @param enable true to enable, false to disable.
      */
     void enableVerboseLogging(boolean enable) {
-        mVerboseLoggingEnabled = enable;
+        synchronized (mLock) {
+            mVerboseLoggingEnabled = enable;
+        }
     }
 
     /**
@@ -150,90 +157,92 @@
      */
     public boolean loadWifiConfiguration(WifiConfiguration config,
                                          Map<String, String> networkExtras) {
-        if (config == null) return false;
-        /** SSID */
-        config.SSID = null;
-        if (getSsid() && !ArrayUtils.isEmpty(mSsid)) {
-            config.SSID = NativeUtil.encodeSsid(mSsid);
-        } else {
-            Log.e(TAG, "failed to read ssid");
-            return false;
-        }
-        /** Network Id */
-        config.networkId = -1;
-        if (getId()) {
-            config.networkId = mNetworkId;
-        } else {
-            Log.e(TAG, "getId failed");
-            return false;
-        }
-        /** BSSID */
-        config.getNetworkSelectionStatus().setNetworkSelectionBSSID(null);
-        if (getBssid() && !ArrayUtils.isEmpty(mBssid)) {
-            config.getNetworkSelectionStatus().setNetworkSelectionBSSID(
-                    NativeUtil.macAddressFromByteArray(mBssid));
-        }
-        /** Scan SSID (Is Hidden Network?) */
-        config.hiddenSSID = false;
-        if (getScanSsid()) {
-            config.hiddenSSID = mScanSsid;
-        }
-        /** Require PMF*/
-        config.requirePMF = false;
-        if (getRequirePmf()) {
-            config.requirePMF = mRequirePmf;
-        }
-        /** WEP keys **/
-        config.wepTxKeyIndex = -1;
-        if (getWepTxKeyIdx()) {
-            config.wepTxKeyIndex = mWepTxKeyIdx;
-        }
-        for (int i = 0; i < 4; i++) {
-            config.wepKeys[i] = null;
-            if (getWepKey(i) && !ArrayUtils.isEmpty(mWepKey)) {
-                config.wepKeys[i] = NativeUtil.bytesToHexOrQuotedAsciiString(mWepKey);
+        synchronized (mLock) {
+            if (config == null) return false;
+            /** SSID */
+            config.SSID = null;
+            if (getSsid() && !ArrayUtils.isEmpty(mSsid)) {
+                config.SSID = NativeUtil.encodeSsid(mSsid);
+            } else {
+                Log.e(TAG, "failed to read ssid");
+                return false;
             }
+            /** Network Id */
+            config.networkId = -1;
+            if (getId()) {
+                config.networkId = mNetworkId;
+            } else {
+                Log.e(TAG, "getId failed");
+                return false;
+            }
+            /** BSSID */
+            config.getNetworkSelectionStatus().setNetworkSelectionBSSID(null);
+            if (getBssid() && !ArrayUtils.isEmpty(mBssid)) {
+                config.getNetworkSelectionStatus().setNetworkSelectionBSSID(
+                        NativeUtil.macAddressFromByteArray(mBssid));
+            }
+            /** Scan SSID (Is Hidden Network?) */
+            config.hiddenSSID = false;
+            if (getScanSsid()) {
+                config.hiddenSSID = mScanSsid;
+            }
+            /** Require PMF*/
+            config.requirePMF = false;
+            if (getRequirePmf()) {
+                config.requirePMF = mRequirePmf;
+            }
+            /** WEP keys **/
+            config.wepTxKeyIndex = -1;
+            if (getWepTxKeyIdx()) {
+                config.wepTxKeyIndex = mWepTxKeyIdx;
+            }
+            for (int i = 0; i < 4; i++) {
+                config.wepKeys[i] = null;
+                if (getWepKey(i) && !ArrayUtils.isEmpty(mWepKey)) {
+                    config.wepKeys[i] = NativeUtil.bytesToHexOrQuotedAsciiString(mWepKey);
+                }
+            }
+            /** PSK pass phrase */
+            config.preSharedKey = null;
+            if (getPskPassphrase() && !TextUtils.isEmpty(mPskPassphrase)) {
+                config.preSharedKey = NativeUtil.addEnclosingQuotes(mPskPassphrase);
+            } else if (getPsk() && !ArrayUtils.isEmpty(mPsk)) {
+                config.preSharedKey = NativeUtil.hexStringFromByteArray(mPsk);
+            }
+            /** allowedKeyManagement */
+            if (getKeyMgmt()) {
+                BitSet keyMgmtMask = supplicantToWifiConfigurationKeyMgmtMask(mKeyMgmtMask);
+                config.allowedKeyManagement = removeFastTransitionFlags(keyMgmtMask);
+            }
+            /** allowedProtocols */
+            if (getProto()) {
+                config.allowedProtocols =
+                        supplicantToWifiConfigurationProtoMask(mProtoMask);
+            }
+            /** allowedAuthAlgorithms */
+            if (getAuthAlg()) {
+                config.allowedAuthAlgorithms =
+                        supplicantToWifiConfigurationAuthAlgMask(mAuthAlgMask);
+            }
+            /** allowedGroupCiphers */
+            if (getGroupCipher()) {
+                config.allowedGroupCiphers =
+                        supplicantToWifiConfigurationGroupCipherMask(mGroupCipherMask);
+            }
+            /** allowedPairwiseCiphers */
+            if (getPairwiseCipher()) {
+                config.allowedPairwiseCiphers =
+                        supplicantToWifiConfigurationPairwiseCipherMask(mPairwiseCipherMask);
+            }
+            /** metadata: idstr */
+            if (getIdStr() && !TextUtils.isEmpty(mIdStr)) {
+                Map<String, String> metadata = parseNetworkExtra(mIdStr);
+                networkExtras.putAll(metadata);
+            } else {
+                Log.w(TAG, "getIdStr failed or empty");
+            }
+            return loadWifiEnterpriseConfig(config.SSID, config.enterpriseConfig);
         }
-        /** PSK pass phrase */
-        config.preSharedKey = null;
-        if (getPskPassphrase() && !TextUtils.isEmpty(mPskPassphrase)) {
-            config.preSharedKey = NativeUtil.addEnclosingQuotes(mPskPassphrase);
-        } else if (getPsk() && !ArrayUtils.isEmpty(mPsk)) {
-            config.preSharedKey = NativeUtil.hexStringFromByteArray(mPsk);
-        }
-        /** allowedKeyManagement */
-        if (getKeyMgmt()) {
-            BitSet keyMgmtMask = supplicantToWifiConfigurationKeyMgmtMask(mKeyMgmtMask);
-            config.allowedKeyManagement = removeFastTransitionFlags(keyMgmtMask);
-        }
-        /** allowedProtocols */
-        if (getProto()) {
-            config.allowedProtocols =
-                    supplicantToWifiConfigurationProtoMask(mProtoMask);
-        }
-        /** allowedAuthAlgorithms */
-        if (getAuthAlg()) {
-            config.allowedAuthAlgorithms =
-                    supplicantToWifiConfigurationAuthAlgMask(mAuthAlgMask);
-        }
-        /** allowedGroupCiphers */
-        if (getGroupCipher()) {
-            config.allowedGroupCiphers =
-                    supplicantToWifiConfigurationGroupCipherMask(mGroupCipherMask);
-        }
-        /** allowedPairwiseCiphers */
-        if (getPairwiseCipher()) {
-            config.allowedPairwiseCiphers =
-                    supplicantToWifiConfigurationPairwiseCipherMask(mPairwiseCipherMask);
-        }
-        /** metadata: idstr */
-        if (getIdStr() && !TextUtils.isEmpty(mIdStr)) {
-            Map<String, String> metadata = parseNetworkExtra(mIdStr);
-            networkExtras.putAll(metadata);
-        } else {
-            Log.w(TAG, "getIdStr failed or empty");
-        }
-        return loadWifiEnterpriseConfig(config.SSID, config.enterpriseConfig);
     }
 
     /**
@@ -244,138 +253,141 @@
      * @throws IllegalArgumentException on malformed configuration params.
      */
     public boolean saveWifiConfiguration(WifiConfiguration config) {
-        if (config == null) return false;
-        /** SSID */
-        if (config.SSID != null) {
-            if (!setSsid(NativeUtil.decodeSsid(config.SSID))) {
-                Log.e(TAG, "failed to set SSID: " + config.SSID);
-                return false;
-            }
-        }
-        /** BSSID */
-        String bssidStr = config.getNetworkSelectionStatus().getNetworkSelectionBSSID();
-        if (bssidStr != null) {
-            byte[] bssid = NativeUtil.macAddressToByteArray(bssidStr);
-            if (!setBssid(bssid)) {
-                Log.e(TAG, "failed to set BSSID: " + bssidStr);
-                return false;
-            }
-        }
-        /** Pre Shared Key. This can either be quoted ASCII passphrase or hex string for raw psk */
-        if (config.preSharedKey != null) {
-            if (config.preSharedKey.startsWith("\"")) {
-                if (!setPskPassphrase(NativeUtil.removeEnclosingQuotes(config.preSharedKey))) {
-                    Log.e(TAG, "failed to set psk passphrase");
-                    return false;
-                }
-            } else {
-                if (!setPsk(NativeUtil.hexStringToByteArray(config.preSharedKey))) {
-                    Log.e(TAG, "failed to set psk");
+        synchronized (mLock) {
+            if (config == null) return false;
+            /** SSID */
+            if (config.SSID != null) {
+                if (!setSsid(NativeUtil.decodeSsid(config.SSID))) {
+                    Log.e(TAG, "failed to set SSID: " + config.SSID);
                     return false;
                 }
             }
-        }
-
-        /** Wep Keys */
-        boolean hasSetKey = false;
-        if (config.wepKeys != null) {
-            for (int i = 0; i < config.wepKeys.length; i++) {
-                if (config.wepKeys[i] != null) {
-                    if (!setWepKey(
-                            i, NativeUtil.hexOrQuotedAsciiStringToBytes(config.wepKeys[i]))) {
-                        Log.e(TAG, "failed to set wep_key " + i);
+            /** BSSID */
+            String bssidStr = config.getNetworkSelectionStatus().getNetworkSelectionBSSID();
+            if (bssidStr != null) {
+                byte[] bssid = NativeUtil.macAddressToByteArray(bssidStr);
+                if (!setBssid(bssid)) {
+                    Log.e(TAG, "failed to set BSSID: " + bssidStr);
+                    return false;
+                }
+            }
+            /** Pre Shared Key */
+            // This can either be quoted ASCII passphrase or hex string for raw psk.
+            if (config.preSharedKey != null) {
+                if (config.preSharedKey.startsWith("\"")) {
+                    if (!setPskPassphrase(NativeUtil.removeEnclosingQuotes(config.preSharedKey))) {
+                        Log.e(TAG, "failed to set psk passphrase");
                         return false;
                     }
-                    hasSetKey = true;
+                } else {
+                    if (!setPsk(NativeUtil.hexStringToByteArray(config.preSharedKey))) {
+                        Log.e(TAG, "failed to set psk");
+                        return false;
+                    }
                 }
             }
-        }
-        /** Wep Tx Key Idx */
-        if (hasSetKey) {
-            if (!setWepTxKeyIdx(config.wepTxKeyIndex)) {
-                Log.e(TAG, "failed to set wep_tx_keyidx: " + config.wepTxKeyIndex);
-                return false;
-            }
-        }
-        /** HiddenSSID */
-        if (!setScanSsid(config.hiddenSSID)) {
-            Log.e(TAG, config.SSID + ": failed to set hiddenSSID: " + config.hiddenSSID);
-            return false;
-        }
-        /** RequirePMF */
-        if (!setRequirePmf(config.requirePMF)) {
-            Log.e(TAG, config.SSID + ": failed to set requirePMF: " + config.requirePMF);
-            return false;
-        }
-        /** Key Management Scheme */
-        if (config.allowedKeyManagement.cardinality() != 0) {
-            // Add FT flags if supported.
-            BitSet keyMgmtMask = addFastTransitionFlags(config.allowedKeyManagement);
-            if (!setKeyMgmt(wifiConfigurationToSupplicantKeyMgmtMask(keyMgmtMask))) {
-                Log.e(TAG, "failed to set Key Management");
-                return false;
-            }
-        }
-        /** Security Protocol */
-        if (config.allowedProtocols.cardinality() != 0
-                && !setProto(wifiConfigurationToSupplicantProtoMask(config.allowedProtocols))) {
-            Log.e(TAG, "failed to set Security Protocol");
-            return false;
-        }
-        /** Auth Algorithm */
-        if (config.allowedAuthAlgorithms.cardinality() != 0
-                && !setAuthAlg(wifiConfigurationToSupplicantAuthAlgMask(
-                config.allowedAuthAlgorithms))) {
-            Log.e(TAG, "failed to set AuthAlgorithm");
-            return false;
-        }
-        /** Group Cipher */
-        if (config.allowedGroupCiphers.cardinality() != 0
-                && !setGroupCipher(wifiConfigurationToSupplicantGroupCipherMask(
-                config.allowedGroupCiphers))) {
-            Log.e(TAG, "failed to set Group Cipher");
-            return false;
-        }
-        /** Pairwise Cipher*/
-        if (config.allowedPairwiseCiphers.cardinality() != 0
-                && !setPairwiseCipher(wifiConfigurationToSupplicantPairwiseCipherMask(
-                        config.allowedPairwiseCiphers))) {
-            Log.e(TAG, "failed to set PairwiseCipher");
-            return false;
-        }
-        /** metadata: FQDN + ConfigKey + CreatorUid */
-        final Map<String, String> metadata = new HashMap<String, String>();
-        if (config.isPasspoint()) {
-            metadata.put(ID_STRING_KEY_FQDN, config.FQDN);
-        }
-        metadata.put(ID_STRING_KEY_CONFIG_KEY, config.configKey());
-        metadata.put(ID_STRING_KEY_CREATOR_UID, Integer.toString(config.creatorUid));
-        if (!setIdStr(createNetworkExtra(metadata))) {
-            Log.e(TAG, "failed to set id string");
-            return false;
-        }
-        /** UpdateIdentifier */
-        if (config.updateIdentifier != null
-                && !setUpdateIdentifier(Integer.parseInt(config.updateIdentifier))) {
-            Log.e(TAG, "failed to set update identifier");
-            return false;
-        }
-        // Finish here if no EAP config to set
-        if (config.enterpriseConfig != null
-                && config.enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE) {
-            if (!saveWifiEnterpriseConfig(config.SSID, config.enterpriseConfig)) {
-                return false;
-            }
-        }
 
-        // Now that the network is configured fully, start listening for callback events.
-        mISupplicantStaNetworkCallback =
-                new SupplicantStaNetworkHalCallback(config.networkId, config.SSID);
-        if (!registerCallback(mISupplicantStaNetworkCallback)) {
-            Log.e(TAG, "Failed to register callback");
-            return false;
+            /** Wep Keys */
+            boolean hasSetKey = false;
+            if (config.wepKeys != null) {
+                for (int i = 0; i < config.wepKeys.length; i++) {
+                    if (config.wepKeys[i] != null) {
+                        if (!setWepKey(
+                                i, NativeUtil.hexOrQuotedAsciiStringToBytes(config.wepKeys[i]))) {
+                            Log.e(TAG, "failed to set wep_key " + i);
+                            return false;
+                        }
+                        hasSetKey = true;
+                    }
+                }
+            }
+            /** Wep Tx Key Idx */
+            if (hasSetKey) {
+                if (!setWepTxKeyIdx(config.wepTxKeyIndex)) {
+                    Log.e(TAG, "failed to set wep_tx_keyidx: " + config.wepTxKeyIndex);
+                    return false;
+                }
+            }
+            /** HiddenSSID */
+            if (!setScanSsid(config.hiddenSSID)) {
+                Log.e(TAG, config.SSID + ": failed to set hiddenSSID: " + config.hiddenSSID);
+                return false;
+            }
+            /** RequirePMF */
+            if (!setRequirePmf(config.requirePMF)) {
+                Log.e(TAG, config.SSID + ": failed to set requirePMF: " + config.requirePMF);
+                return false;
+            }
+            /** Key Management Scheme */
+            if (config.allowedKeyManagement.cardinality() != 0) {
+                // Add FT flags if supported.
+                BitSet keyMgmtMask = addFastTransitionFlags(config.allowedKeyManagement);
+                if (!setKeyMgmt(wifiConfigurationToSupplicantKeyMgmtMask(keyMgmtMask))) {
+                    Log.e(TAG, "failed to set Key Management");
+                    return false;
+                }
+            }
+            /** Security Protocol */
+            if (config.allowedProtocols.cardinality() != 0
+                    && !setProto(wifiConfigurationToSupplicantProtoMask(config.allowedProtocols))) {
+                Log.e(TAG, "failed to set Security Protocol");
+                return false;
+            }
+            /** Auth Algorithm */
+            if (config.allowedAuthAlgorithms.cardinality() != 0
+                    && !setAuthAlg(wifiConfigurationToSupplicantAuthAlgMask(
+                    config.allowedAuthAlgorithms))) {
+                Log.e(TAG, "failed to set AuthAlgorithm");
+                return false;
+            }
+            /** Group Cipher */
+            if (config.allowedGroupCiphers.cardinality() != 0
+                    && !setGroupCipher(wifiConfigurationToSupplicantGroupCipherMask(
+                    config.allowedGroupCiphers))) {
+                Log.e(TAG, "failed to set Group Cipher");
+                return false;
+            }
+            /** Pairwise Cipher*/
+            if (config.allowedPairwiseCiphers.cardinality() != 0
+                    && !setPairwiseCipher(wifiConfigurationToSupplicantPairwiseCipherMask(
+                    config.allowedPairwiseCiphers))) {
+                Log.e(TAG, "failed to set PairwiseCipher");
+                return false;
+            }
+            /** metadata: FQDN + ConfigKey + CreatorUid */
+            final Map<String, String> metadata = new HashMap<String, String>();
+            if (config.isPasspoint()) {
+                metadata.put(ID_STRING_KEY_FQDN, config.FQDN);
+            }
+            metadata.put(ID_STRING_KEY_CONFIG_KEY, config.configKey());
+            metadata.put(ID_STRING_KEY_CREATOR_UID, Integer.toString(config.creatorUid));
+            if (!setIdStr(createNetworkExtra(metadata))) {
+                Log.e(TAG, "failed to set id string");
+                return false;
+            }
+            /** UpdateIdentifier */
+            if (config.updateIdentifier != null
+                    && !setUpdateIdentifier(Integer.parseInt(config.updateIdentifier))) {
+                Log.e(TAG, "failed to set update identifier");
+                return false;
+            }
+            // Finish here if no EAP config to set
+            if (config.enterpriseConfig != null
+                    && config.enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE) {
+                if (!saveWifiEnterpriseConfig(config.SSID, config.enterpriseConfig)) {
+                    return false;
+                }
+            }
+
+            // Now that the network is configured fully, start listening for callback events.
+            mISupplicantStaNetworkCallback =
+                    new SupplicantStaNetworkHalCallback(config.networkId, config.SSID);
+            if (!registerCallback(mISupplicantStaNetworkCallback)) {
+                Log.e(TAG, "Failed to register callback");
+                return false;
+            }
+            return true;
         }
-        return true;
     }
 
     /**
@@ -386,84 +398,87 @@
      * @return true if succeeds, false otherwise.
      */
     private boolean loadWifiEnterpriseConfig(String ssid, WifiEnterpriseConfig eapConfig) {
-        if (eapConfig == null) return false;
-        /** EAP method */
-        if (getEapMethod()) {
-            eapConfig.setEapMethod(supplicantToWifiConfigurationEapMethod(mEapMethod));
-        } else {
-            // Invalid eap method could be because it's not an enterprise config.
-            Log.e(TAG, "failed to get eap method. Assumimg not an enterprise network");
+        synchronized (mLock) {
+            if (eapConfig == null) return false;
+            /** EAP method */
+            if (getEapMethod()) {
+                eapConfig.setEapMethod(supplicantToWifiConfigurationEapMethod(mEapMethod));
+            } else {
+                // Invalid eap method could be because it's not an enterprise config.
+                Log.e(TAG, "failed to get eap method. Assumimg not an enterprise network");
+                return true;
+            }
+            /** EAP Phase 2 method */
+            if (getEapPhase2Method()) {
+                eapConfig.setPhase2Method(
+                        supplicantToWifiConfigurationEapPhase2Method(mEapPhase2Method));
+            } else {
+                // We cannot have an invalid eap phase 2 method. Return failure.
+                Log.e(TAG, "failed to get eap phase2 method");
+                return false;
+            }
+            /** EAP Identity */
+            if (getEapIdentity() && !ArrayUtils.isEmpty(mEapIdentity)) {
+                eapConfig.setFieldValue(
+                        WifiEnterpriseConfig.IDENTITY_KEY,
+                        NativeUtil.stringFromByteArrayList(mEapIdentity));
+            }
+            /** EAP Anonymous Identity */
+            if (getEapAnonymousIdentity() && !ArrayUtils.isEmpty(mEapAnonymousIdentity)) {
+                eapConfig.setFieldValue(
+                        WifiEnterpriseConfig.ANON_IDENTITY_KEY,
+                        NativeUtil.stringFromByteArrayList(mEapAnonymousIdentity));
+            }
+            /** EAP Password */
+            if (getEapPassword() && !ArrayUtils.isEmpty(mEapPassword)) {
+                eapConfig.setFieldValue(
+                        WifiEnterpriseConfig.PASSWORD_KEY,
+                        NativeUtil.stringFromByteArrayList(mEapPassword));
+            }
+            /** EAP Client Cert */
+            if (getEapClientCert() && !TextUtils.isEmpty(mEapClientCert)) {
+                eapConfig.setFieldValue(WifiEnterpriseConfig.CLIENT_CERT_KEY, mEapClientCert);
+            }
+            /** EAP CA Cert */
+            if (getEapCACert() && !TextUtils.isEmpty(mEapCACert)) {
+                eapConfig.setFieldValue(WifiEnterpriseConfig.CA_CERT_KEY, mEapCACert);
+            }
+            /** EAP Subject Match */
+            if (getEapSubjectMatch() && !TextUtils.isEmpty(mEapSubjectMatch)) {
+                eapConfig.setFieldValue(WifiEnterpriseConfig.SUBJECT_MATCH_KEY, mEapSubjectMatch);
+            }
+            /** EAP Engine ID */
+            if (getEapEngineID() && !TextUtils.isEmpty(mEapEngineID)) {
+                eapConfig.setFieldValue(WifiEnterpriseConfig.ENGINE_ID_KEY, mEapEngineID);
+            }
+            /** EAP Engine. Set this only if the engine id is non null. */
+            if (getEapEngine() && !TextUtils.isEmpty(mEapEngineID)) {
+                eapConfig.setFieldValue(
+                        WifiEnterpriseConfig.ENGINE_KEY,
+                        mEapEngine
+                                ? WifiEnterpriseConfig.ENGINE_ENABLE
+                                : WifiEnterpriseConfig.ENGINE_DISABLE);
+            }
+            /** EAP Private Key */
+            if (getEapPrivateKeyId() && !TextUtils.isEmpty(mEapPrivateKeyId)) {
+                eapConfig.setFieldValue(WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY, mEapPrivateKeyId);
+            }
+            /** EAP Alt Subject Match */
+            if (getEapAltSubjectMatch() && !TextUtils.isEmpty(mEapAltSubjectMatch)) {
+                eapConfig.setFieldValue(
+                        WifiEnterpriseConfig.ALTSUBJECT_MATCH_KEY, mEapAltSubjectMatch);
+            }
+            /** EAP Domain Suffix Match */
+            if (getEapDomainSuffixMatch() && !TextUtils.isEmpty(mEapDomainSuffixMatch)) {
+                eapConfig.setFieldValue(
+                        WifiEnterpriseConfig.DOM_SUFFIX_MATCH_KEY, mEapDomainSuffixMatch);
+            }
+            /** EAP CA Path*/
+            if (getEapCAPath() && !TextUtils.isEmpty(mEapCAPath)) {
+                eapConfig.setFieldValue(WifiEnterpriseConfig.CA_PATH_KEY, mEapCAPath);
+            }
             return true;
         }
-        /** EAP Phase 2 method */
-        if (getEapPhase2Method()) {
-            eapConfig.setPhase2Method(
-                    supplicantToWifiConfigurationEapPhase2Method(mEapPhase2Method));
-        } else {
-            // We cannot have an invalid eap phase 2 method. Return failure.
-            Log.e(TAG, "failed to get eap phase2 method");
-            return false;
-        }
-        /** EAP Identity */
-        if (getEapIdentity() && !ArrayUtils.isEmpty(mEapIdentity)) {
-            eapConfig.setFieldValue(
-                    WifiEnterpriseConfig.IDENTITY_KEY,
-                    NativeUtil.stringFromByteArrayList(mEapIdentity));
-        }
-        /** EAP Anonymous Identity */
-        if (getEapAnonymousIdentity() && !ArrayUtils.isEmpty(mEapAnonymousIdentity)) {
-            eapConfig.setFieldValue(
-                    WifiEnterpriseConfig.ANON_IDENTITY_KEY,
-                    NativeUtil.stringFromByteArrayList(mEapAnonymousIdentity));
-        }
-        /** EAP Password */
-        if (getEapPassword() && !ArrayUtils.isEmpty(mEapPassword)) {
-            eapConfig.setFieldValue(
-                    WifiEnterpriseConfig.PASSWORD_KEY,
-                    NativeUtil.stringFromByteArrayList(mEapPassword));
-        }
-        /** EAP Client Cert */
-        if (getEapClientCert() && !TextUtils.isEmpty(mEapClientCert)) {
-            eapConfig.setFieldValue(WifiEnterpriseConfig.CLIENT_CERT_KEY, mEapClientCert);
-        }
-        /** EAP CA Cert */
-        if (getEapCACert() && !TextUtils.isEmpty(mEapCACert)) {
-            eapConfig.setFieldValue(WifiEnterpriseConfig.CA_CERT_KEY, mEapCACert);
-        }
-        /** EAP Subject Match */
-        if (getEapSubjectMatch() && !TextUtils.isEmpty(mEapSubjectMatch)) {
-            eapConfig.setFieldValue(WifiEnterpriseConfig.SUBJECT_MATCH_KEY, mEapSubjectMatch);
-        }
-        /** EAP Engine ID */
-        if (getEapEngineID() && !TextUtils.isEmpty(mEapEngineID)) {
-            eapConfig.setFieldValue(WifiEnterpriseConfig.ENGINE_ID_KEY, mEapEngineID);
-        }
-        /** EAP Engine. Set this only if the engine id is non null. */
-        if (getEapEngine() && !TextUtils.isEmpty(mEapEngineID)) {
-            eapConfig.setFieldValue(
-                    WifiEnterpriseConfig.ENGINE_KEY,
-                    mEapEngine
-                            ? WifiEnterpriseConfig.ENGINE_ENABLE
-                            : WifiEnterpriseConfig.ENGINE_DISABLE);
-        }
-        /** EAP Private Key */
-        if (getEapPrivateKeyId() && !TextUtils.isEmpty(mEapPrivateKeyId)) {
-            eapConfig.setFieldValue(WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY, mEapPrivateKeyId);
-        }
-        /** EAP Alt Subject Match */
-        if (getEapAltSubjectMatch() && !TextUtils.isEmpty(mEapAltSubjectMatch)) {
-            eapConfig.setFieldValue(WifiEnterpriseConfig.ALTSUBJECT_MATCH_KEY, mEapAltSubjectMatch);
-        }
-        /** EAP Domain Suffix Match */
-        if (getEapDomainSuffixMatch() && !TextUtils.isEmpty(mEapDomainSuffixMatch)) {
-            eapConfig.setFieldValue(
-                    WifiEnterpriseConfig.DOM_SUFFIX_MATCH_KEY, mEapDomainSuffixMatch);
-        }
-        /** EAP CA Path*/
-        if (getEapCAPath() && !TextUtils.isEmpty(mEapCAPath)) {
-            eapConfig.setFieldValue(WifiEnterpriseConfig.CA_PATH_KEY, mEapCAPath);
-        }
-        return true;
     }
 
     /**
@@ -474,104 +489,107 @@
      * @return true if succeeds, false otherwise.
      */
     private boolean saveWifiEnterpriseConfig(String ssid, WifiEnterpriseConfig eapConfig) {
-        if (eapConfig == null) return false;
-        /** EAP method */
-        if (!setEapMethod(wifiConfigurationToSupplicantEapMethod(eapConfig.getEapMethod()))) {
-            Log.e(TAG, ssid + ": failed to set eap method: " + eapConfig.getEapMethod());
-            return false;
-        }
-        /** EAP Phase 2 method */
-        if (!setEapPhase2Method(wifiConfigurationToSupplicantEapPhase2Method(
-                eapConfig.getPhase2Method()))) {
-            Log.e(TAG, ssid + ": failed to set eap phase 2 method: " + eapConfig.getPhase2Method());
-            return false;
-        }
-        String eapParam = null;
-        /** EAP Identity */
-        eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.IDENTITY_KEY);
-        if (!TextUtils.isEmpty(eapParam)
-                && !setEapIdentity(NativeUtil.stringToByteArrayList(eapParam))) {
-            Log.e(TAG, ssid + ": failed to set eap identity: " + eapParam);
-            return false;
-        }
-        /** EAP Anonymous Identity */
-        eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.ANON_IDENTITY_KEY);
-        if (!TextUtils.isEmpty(eapParam)
-                && !setEapAnonymousIdentity(NativeUtil.stringToByteArrayList(eapParam))) {
-            Log.e(TAG, ssid + ": failed to set eap anonymous identity: " + eapParam);
-            return false;
-        }
-        /** EAP Password */
-        eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.PASSWORD_KEY);
-        if (!TextUtils.isEmpty(eapParam)
-                && !setEapPassword(NativeUtil.stringToByteArrayList(eapParam))) {
-            Log.e(TAG, ssid + ": failed to set eap password");
-            return false;
-        }
-        /** EAP Client Cert */
-        eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.CLIENT_CERT_KEY);
-        if (!TextUtils.isEmpty(eapParam) && !setEapClientCert(eapParam)) {
-            Log.e(TAG, ssid + ": failed to set eap client cert: " + eapParam);
-            return false;
-        }
-        /** EAP CA Cert */
-        eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.CA_CERT_KEY);
-        if (!TextUtils.isEmpty(eapParam) && !setEapCACert(eapParam)) {
-            Log.e(TAG, ssid + ": failed to set eap ca cert: " + eapParam);
-            return false;
-        }
-        /** EAP Subject Match */
-        eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.SUBJECT_MATCH_KEY);
-        if (!TextUtils.isEmpty(eapParam) && !setEapSubjectMatch(eapParam)) {
-            Log.e(TAG, ssid + ": failed to set eap subject match: " + eapParam);
-            return false;
-        }
-        /** EAP Engine ID */
-        eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.ENGINE_ID_KEY);
-        if (!TextUtils.isEmpty(eapParam) && !setEapEngineID(eapParam)) {
-            Log.e(TAG, ssid + ": failed to set eap engine id: " + eapParam);
-            return false;
-        }
-        /** EAP Engine */
-        eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.ENGINE_KEY);
-        if (!TextUtils.isEmpty(eapParam) && !setEapEngine(
-                eapParam.equals(WifiEnterpriseConfig.ENGINE_ENABLE) ? true : false)) {
-            Log.e(TAG, ssid + ": failed to set eap engine: " + eapParam);
-            return false;
-        }
-        /** EAP Private Key */
-        eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY);
-        if (!TextUtils.isEmpty(eapParam) && !setEapPrivateKeyId(eapParam)) {
-            Log.e(TAG, ssid + ": failed to set eap private key: " + eapParam);
-            return false;
-        }
-        /** EAP Alt Subject Match */
-        eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.ALTSUBJECT_MATCH_KEY);
-        if (!TextUtils.isEmpty(eapParam) && !setEapAltSubjectMatch(eapParam)) {
-            Log.e(TAG, ssid + ": failed to set eap alt subject match: " + eapParam);
-            return false;
-        }
-        /** EAP Domain Suffix Match */
-        eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.DOM_SUFFIX_MATCH_KEY);
-        if (!TextUtils.isEmpty(eapParam) && !setEapDomainSuffixMatch(eapParam)) {
-            Log.e(TAG, ssid + ": failed to set eap domain suffix match: " + eapParam);
-            return false;
-        }
-        /** EAP CA Path*/
-        eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.CA_PATH_KEY);
-        if (!TextUtils.isEmpty(eapParam) && !setEapCAPath(eapParam)) {
-            Log.e(TAG, ssid + ": failed to set eap ca path: " + eapParam);
-            return false;
-        }
+        synchronized (mLock) {
+            if (eapConfig == null) return false;
+            /** EAP method */
+            if (!setEapMethod(wifiConfigurationToSupplicantEapMethod(eapConfig.getEapMethod()))) {
+                Log.e(TAG, ssid + ": failed to set eap method: " + eapConfig.getEapMethod());
+                return false;
+            }
+            /** EAP Phase 2 method */
+            if (!setEapPhase2Method(wifiConfigurationToSupplicantEapPhase2Method(
+                    eapConfig.getPhase2Method()))) {
+                Log.e(TAG, ssid + ": failed to set eap phase 2 method: "
+                        + eapConfig.getPhase2Method());
+                return false;
+            }
+            String eapParam = null;
+            /** EAP Identity */
+            eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.IDENTITY_KEY);
+            if (!TextUtils.isEmpty(eapParam)
+                    && !setEapIdentity(NativeUtil.stringToByteArrayList(eapParam))) {
+                Log.e(TAG, ssid + ": failed to set eap identity: " + eapParam);
+                return false;
+            }
+            /** EAP Anonymous Identity */
+            eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.ANON_IDENTITY_KEY);
+            if (!TextUtils.isEmpty(eapParam)
+                    && !setEapAnonymousIdentity(NativeUtil.stringToByteArrayList(eapParam))) {
+                Log.e(TAG, ssid + ": failed to set eap anonymous identity: " + eapParam);
+                return false;
+            }
+            /** EAP Password */
+            eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.PASSWORD_KEY);
+            if (!TextUtils.isEmpty(eapParam)
+                    && !setEapPassword(NativeUtil.stringToByteArrayList(eapParam))) {
+                Log.e(TAG, ssid + ": failed to set eap password");
+                return false;
+            }
+            /** EAP Client Cert */
+            eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.CLIENT_CERT_KEY);
+            if (!TextUtils.isEmpty(eapParam) && !setEapClientCert(eapParam)) {
+                Log.e(TAG, ssid + ": failed to set eap client cert: " + eapParam);
+                return false;
+            }
+            /** EAP CA Cert */
+            eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.CA_CERT_KEY);
+            if (!TextUtils.isEmpty(eapParam) && !setEapCACert(eapParam)) {
+                Log.e(TAG, ssid + ": failed to set eap ca cert: " + eapParam);
+                return false;
+            }
+            /** EAP Subject Match */
+            eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.SUBJECT_MATCH_KEY);
+            if (!TextUtils.isEmpty(eapParam) && !setEapSubjectMatch(eapParam)) {
+                Log.e(TAG, ssid + ": failed to set eap subject match: " + eapParam);
+                return false;
+            }
+            /** EAP Engine ID */
+            eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.ENGINE_ID_KEY);
+            if (!TextUtils.isEmpty(eapParam) && !setEapEngineID(eapParam)) {
+                Log.e(TAG, ssid + ": failed to set eap engine id: " + eapParam);
+                return false;
+            }
+            /** EAP Engine */
+            eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.ENGINE_KEY);
+            if (!TextUtils.isEmpty(eapParam) && !setEapEngine(
+                    eapParam.equals(WifiEnterpriseConfig.ENGINE_ENABLE) ? true : false)) {
+                Log.e(TAG, ssid + ": failed to set eap engine: " + eapParam);
+                return false;
+            }
+            /** EAP Private Key */
+            eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY);
+            if (!TextUtils.isEmpty(eapParam) && !setEapPrivateKeyId(eapParam)) {
+                Log.e(TAG, ssid + ": failed to set eap private key: " + eapParam);
+                return false;
+            }
+            /** EAP Alt Subject Match */
+            eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.ALTSUBJECT_MATCH_KEY);
+            if (!TextUtils.isEmpty(eapParam) && !setEapAltSubjectMatch(eapParam)) {
+                Log.e(TAG, ssid + ": failed to set eap alt subject match: " + eapParam);
+                return false;
+            }
+            /** EAP Domain Suffix Match */
+            eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.DOM_SUFFIX_MATCH_KEY);
+            if (!TextUtils.isEmpty(eapParam) && !setEapDomainSuffixMatch(eapParam)) {
+                Log.e(TAG, ssid + ": failed to set eap domain suffix match: " + eapParam);
+                return false;
+            }
+            /** EAP CA Path*/
+            eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.CA_PATH_KEY);
+            if (!TextUtils.isEmpty(eapParam) && !setEapCAPath(eapParam)) {
+                Log.e(TAG, ssid + ": failed to set eap ca path: " + eapParam);
+                return false;
+            }
 
-        /** EAP Proactive Key Caching */
-        eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.OPP_KEY_CACHING);
-        if (!TextUtils.isEmpty(eapParam)
-                && !setEapProactiveKeyCaching(eapParam.equals("1") ? true : false)) {
-            Log.e(TAG, ssid + ": failed to set proactive key caching: " + eapParam);
-            return false;
+            /** EAP Proactive Key Caching */
+            eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.OPP_KEY_CACHING);
+            if (!TextUtils.isEmpty(eapParam)
+                    && !setEapProactiveKeyCaching(eapParam.equals("1") ? true : false)) {
+                Log.e(TAG, ssid + ": failed to set proactive key caching: " + eapParam);
+                return false;
+            }
+            return true;
         }
-        return true;
     }
 
     /**
@@ -981,11 +999,13 @@
      * @return true if it succeeds, false otherwise.
      */
     public boolean setBssid(String bssidStr) {
-        try {
-            return setBssid(NativeUtil.macAddressToByteArray(bssidStr));
-        } catch (IllegalArgumentException e) {
-            Log.e(TAG, "Illegal argument " + bssidStr, e);
-            return false;
+        synchronized (mLock) {
+            try {
+                return setBssid(NativeUtil.macAddressToByteArray(bssidStr));
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Illegal argument " + bssidStr, e);
+                return false;
+            }
         }
     }
 
@@ -1793,10 +1813,12 @@
      * @return anonymous identity string if succeeds, null otherwise.
      */
     public String fetchEapAnonymousIdentity() {
-        if (!getEapAnonymousIdentity()) {
-            return null;
+        synchronized (mLock) {
+            if (!getEapAnonymousIdentity()) {
+                return null;
+            }
+            return NativeUtil.stringFromByteArrayList(mEapAnonymousIdentity);
         }
-        return NativeUtil.stringFromByteArrayList(mEapAnonymousIdentity);
     }
 
     /** See ISupplicantStaNetwork.hal for documentation */
@@ -2103,40 +2125,42 @@
      * @return true if succeeds, false otherwise.
      */
     public boolean sendNetworkEapSimGsmAuthResponse(String paramsStr) {
-        try {
-            Matcher match = GSM_AUTH_RESPONSE_PARAMS_PATTERN.matcher(paramsStr);
-            ArrayList<ISupplicantStaNetwork.NetworkResponseEapSimGsmAuthParams> params =
-                    new ArrayList<>();
-            while (match.find()) {
-                if (match.groupCount() != 2) {
+        synchronized (mLock) {
+            try {
+                Matcher match = GSM_AUTH_RESPONSE_PARAMS_PATTERN.matcher(paramsStr);
+                ArrayList<ISupplicantStaNetwork.NetworkResponseEapSimGsmAuthParams> params =
+                        new ArrayList<>();
+                while (match.find()) {
+                    if (match.groupCount() != 2) {
+                        Log.e(TAG, "Malformed gsm auth response params: " + paramsStr);
+                        return false;
+                    }
+                    ISupplicantStaNetwork.NetworkResponseEapSimGsmAuthParams param =
+                            new ISupplicantStaNetwork.NetworkResponseEapSimGsmAuthParams();
+                    byte[] kc = NativeUtil.hexStringToByteArray(match.group(1));
+                    if (kc == null || kc.length != param.kc.length) {
+                        Log.e(TAG, "Invalid kc value: " + match.group(1));
+                        return false;
+                    }
+                    byte[] sres = NativeUtil.hexStringToByteArray(match.group(2));
+                    if (sres == null || sres.length != param.sres.length) {
+                        Log.e(TAG, "Invalid sres value: " + match.group(2));
+                        return false;
+                    }
+                    System.arraycopy(kc, 0, param.kc, 0, param.kc.length);
+                    System.arraycopy(sres, 0, param.sres, 0, param.sres.length);
+                    params.add(param);
+                }
+                // The number of kc/sres pairs can either be 2 or 3 depending on the request.
+                if (params.size() > 3 || params.size() < 2) {
                     Log.e(TAG, "Malformed gsm auth response params: " + paramsStr);
                     return false;
                 }
-                ISupplicantStaNetwork.NetworkResponseEapSimGsmAuthParams param =
-                        new ISupplicantStaNetwork.NetworkResponseEapSimGsmAuthParams();
-                byte[] kc = NativeUtil.hexStringToByteArray(match.group(1));
-                if (kc == null || kc.length != param.kc.length) {
-                    Log.e(TAG, "Invalid kc value: " + match.group(1));
-                    return false;
-                }
-                byte[] sres = NativeUtil.hexStringToByteArray(match.group(2));
-                if (sres == null || sres.length != param.sres.length) {
-                    Log.e(TAG, "Invalid sres value: " + match.group(2));
-                    return false;
-                }
-                System.arraycopy(kc, 0, param.kc, 0, param.kc.length);
-                System.arraycopy(sres, 0, param.sres, 0, param.sres.length);
-                params.add(param);
-            }
-            // The number of kc/sres pairs can either be 2 or 3 depending on the request.
-            if (params.size() > 3 || params.size() < 2) {
-                Log.e(TAG, "Malformed gsm auth response params: " + paramsStr);
+                return sendNetworkEapSimGsmAuthResponse(params);
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Illegal argument " + paramsStr, e);
                 return false;
             }
-            return sendNetworkEapSimGsmAuthResponse(params);
-        } catch (IllegalArgumentException e) {
-            Log.e(TAG, "Illegal argument " + paramsStr, e);
-            return false;
         }
     }
 
@@ -2177,38 +2201,40 @@
      * @return true if succeeds, false otherwise.
      */
     public boolean sendNetworkEapSimUmtsAuthResponse(String paramsStr) {
-        try {
-            Matcher match = UMTS_AUTH_RESPONSE_PARAMS_PATTERN.matcher(paramsStr);
-            if (!match.find() || match.groupCount() != 3) {
-                Log.e(TAG, "Malformed umts auth response params: " + paramsStr);
+        synchronized (mLock) {
+            try {
+                Matcher match = UMTS_AUTH_RESPONSE_PARAMS_PATTERN.matcher(paramsStr);
+                if (!match.find() || match.groupCount() != 3) {
+                    Log.e(TAG, "Malformed umts auth response params: " + paramsStr);
+                    return false;
+                }
+                ISupplicantStaNetwork.NetworkResponseEapSimUmtsAuthParams params =
+                        new ISupplicantStaNetwork.NetworkResponseEapSimUmtsAuthParams();
+                byte[] ik = NativeUtil.hexStringToByteArray(match.group(1));
+                if (ik == null || ik.length != params.ik.length) {
+                    Log.e(TAG, "Invalid ik value: " + match.group(1));
+                    return false;
+                }
+                byte[] ck = NativeUtil.hexStringToByteArray(match.group(2));
+                if (ck == null || ck.length != params.ck.length) {
+                    Log.e(TAG, "Invalid ck value: " + match.group(2));
+                    return false;
+                }
+                byte[] res = NativeUtil.hexStringToByteArray(match.group(3));
+                if (res == null || res.length == 0) {
+                    Log.e(TAG, "Invalid res value: " + match.group(3));
+                    return false;
+                }
+                System.arraycopy(ik, 0, params.ik, 0, params.ik.length);
+                System.arraycopy(ck, 0, params.ck, 0, params.ck.length);
+                for (byte b : res) {
+                    params.res.add(b);
+                }
+                return sendNetworkEapSimUmtsAuthResponse(params);
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Illegal argument " + paramsStr, e);
                 return false;
             }
-            ISupplicantStaNetwork.NetworkResponseEapSimUmtsAuthParams params =
-                    new ISupplicantStaNetwork.NetworkResponseEapSimUmtsAuthParams();
-            byte[] ik = NativeUtil.hexStringToByteArray(match.group(1));
-            if (ik == null || ik.length != params.ik.length) {
-                Log.e(TAG, "Invalid ik value: " + match.group(1));
-                return false;
-            }
-            byte[] ck = NativeUtil.hexStringToByteArray(match.group(2));
-            if (ck == null || ck.length != params.ck.length) {
-                Log.e(TAG, "Invalid ck value: " + match.group(2));
-                return false;
-            }
-            byte[] res = NativeUtil.hexStringToByteArray(match.group(3));
-            if (res == null || res.length == 0) {
-                Log.e(TAG, "Invalid res value: " + match.group(3));
-                return false;
-            }
-            System.arraycopy(ik, 0, params.ik, 0, params.ik.length);
-            System.arraycopy(ck, 0, params.ck, 0, params.ck.length);
-            for (byte b : res) {
-                params.res.add(b);
-            }
-            return sendNetworkEapSimUmtsAuthResponse(params);
-        } catch (IllegalArgumentException e) {
-            Log.e(TAG, "Illegal argument " + paramsStr, e);
-            return false;
         }
     }
 
@@ -2235,21 +2261,23 @@
      * @return true if succeeds, false otherwise.
      */
     public boolean sendNetworkEapSimUmtsAutsResponse(String paramsStr) {
-        try {
-            Matcher match = UMTS_AUTS_RESPONSE_PARAMS_PATTERN.matcher(paramsStr);
-            if (!match.find() || match.groupCount() != 1) {
-                Log.e(TAG, "Malformed umts auts response params: " + paramsStr);
+        synchronized (mLock) {
+            try {
+                Matcher match = UMTS_AUTS_RESPONSE_PARAMS_PATTERN.matcher(paramsStr);
+                if (!match.find() || match.groupCount() != 1) {
+                    Log.e(TAG, "Malformed umts auts response params: " + paramsStr);
+                    return false;
+                }
+                byte[] auts = NativeUtil.hexStringToByteArray(match.group(1));
+                if (auts == null || auts.length != 14) {
+                    Log.e(TAG, "Invalid auts value: " + match.group(1));
+                    return false;
+                }
+                return sendNetworkEapSimUmtsAutsResponse(auts);
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Illegal argument " + paramsStr, e);
                 return false;
             }
-            byte[] auts = NativeUtil.hexStringToByteArray(match.group(1));
-            if (auts == null || auts.length != 14) {
-                Log.e(TAG, "Invalid auts value: " + match.group(1));
-                return false;
-            }
-            return sendNetworkEapSimUmtsAutsResponse(auts);
-        } catch (IllegalArgumentException e) {
-            Log.e(TAG, "Illegal argument " + paramsStr, e);
-            return false;
         }
     }
     /** See ISupplicantStaNetwork.hal for documentation */
@@ -2288,12 +2316,14 @@
      * @return true if succeeds, false otherwise.
      */
     public boolean sendNetworkEapIdentityResponse(String identityStr) {
-        try {
-            ArrayList<Byte> identity = NativeUtil.stringToByteArrayList(identityStr);
-            return sendNetworkEapIdentityResponse(identity);
-        } catch (IllegalArgumentException e) {
-            Log.e(TAG, "Illegal argument " + identityStr, e);
-            return false;
+        synchronized (mLock) {
+            try {
+                ArrayList<Byte> identity = NativeUtil.stringToByteArrayList(identityStr);
+                return sendNetworkEapIdentityResponse(identity);
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Illegal argument " + identityStr, e);
+                return false;
+            }
         }
     }
     /** See ISupplicantStaNetwork.hal for documentation */
@@ -2318,11 +2348,13 @@
      * @return Hex string corresponding to the NFC token or null for failure.
      */
     public String getWpsNfcConfigurationToken() {
-        ArrayList<Byte> token = getWpsNfcConfigurationTokenInternal();
-        if (token == null) {
-            return null;
+        synchronized (mLock) {
+            ArrayList<Byte> token = getWpsNfcConfigurationTokenInternal();
+            if (token == null) {
+                return null;
+            }
+            return NativeUtil.hexStringFromByteArray(NativeUtil.byteArrayFromArrayList(token));
         }
-        return NativeUtil.hexStringFromByteArray(NativeUtil.byteArrayFromArrayList(token));
     }
 
     /** See ISupplicantStaNetwork.hal for documentation */
@@ -2350,16 +2382,18 @@
      * otherwise
      */
     private boolean checkStatusAndLogFailure(SupplicantStatus status, final String methodStr) {
-        if (status.code != SupplicantStatusCode.SUCCESS) {
-            Log.e(TAG, "ISupplicantStaNetwork." + methodStr + " failed: "
-                    + SupplicantStaIfaceHal.supplicantStatusCodeToString(status.code) + ", "
-                    + status.debugMessage);
-            return false;
-        } else {
-            if (mVerboseLoggingEnabled) {
-                Log.d(TAG, "ISupplicantStaNetwork." + methodStr + " succeeded");
+        synchronized (mLock) {
+            if (status.code != SupplicantStatusCode.SUCCESS) {
+                Log.e(TAG, "ISupplicantStaNetwork." + methodStr + " failed: "
+                        + SupplicantStaIfaceHal.supplicantStatusCodeToString(status.code) + ", "
+                        + status.debugMessage);
+                return false;
+            } else {
+                if (mVerboseLoggingEnabled) {
+                    Log.d(TAG, "ISupplicantStaNetwork." + methodStr + " succeeded");
+                }
+                return true;
             }
-            return true;
         }
     }
 
@@ -2367,8 +2401,10 @@
      * Helper function to log callbacks.
      */
     private void logCallback(final String methodStr) {
-        if (mVerboseLoggingEnabled) {
-            Log.d(TAG, "ISupplicantStaNetworkCallback." + methodStr + " received");
+        synchronized (mLock) {
+            if (mVerboseLoggingEnabled) {
+                Log.d(TAG, "ISupplicantStaNetworkCallback." + methodStr + " received");
+            }
         }
     }
 
@@ -2376,43 +2412,51 @@
      * Returns false if ISupplicantStaNetwork is null, and logs failure of methodStr
      */
     private boolean checkISupplicantStaNetworkAndLogFailure(final String methodStr) {
-        if (mISupplicantStaNetwork == null) {
-            Log.e(TAG, "Can't call " + methodStr + ", ISupplicantStaNetwork is null");
-            return false;
+        synchronized (mLock) {
+            if (mISupplicantStaNetwork == null) {
+                Log.e(TAG, "Can't call " + methodStr + ", ISupplicantStaNetwork is null");
+                return false;
+            }
+            return true;
         }
-        return true;
     }
 
     private void handleRemoteException(RemoteException e, String methodStr) {
-        mISupplicantStaNetwork = null;
-        Log.e(TAG, "ISupplicantStaNetwork." + methodStr + " failed with exception", e);
+        synchronized (mLock) {
+            mISupplicantStaNetwork = null;
+            Log.e(TAG, "ISupplicantStaNetwork." + methodStr + " failed with exception", e);
+        }
     }
 
     /**
      * Adds FT flags for networks if the device supports it.
      */
     private BitSet addFastTransitionFlags(BitSet keyManagementFlags) {
-        if (!mSystemSupportsFastBssTransition) {
-            return keyManagementFlags;
+        synchronized (mLock) {
+            if (!mSystemSupportsFastBssTransition) {
+                return keyManagementFlags;
+            }
+            BitSet modifiedFlags = (BitSet) keyManagementFlags.clone();
+            if (keyManagementFlags.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
+                modifiedFlags.set(WifiConfiguration.KeyMgmt.FT_PSK);
+            }
+            if (keyManagementFlags.get(WifiConfiguration.KeyMgmt.WPA_EAP)) {
+                modifiedFlags.set(WifiConfiguration.KeyMgmt.FT_EAP);
+            }
+            return modifiedFlags;
         }
-        BitSet modifiedFlags = (BitSet) keyManagementFlags.clone();
-        if (keyManagementFlags.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
-            modifiedFlags.set(WifiConfiguration.KeyMgmt.FT_PSK);
-        }
-        if (keyManagementFlags.get(WifiConfiguration.KeyMgmt.WPA_EAP)) {
-            modifiedFlags.set(WifiConfiguration.KeyMgmt.FT_EAP);
-        }
-        return modifiedFlags;
     }
 
     /**
      * Removes FT flags for networks if the device supports it.
      */
     private BitSet removeFastTransitionFlags(BitSet keyManagementFlags) {
-        BitSet modifiedFlags = (BitSet) keyManagementFlags.clone();
-        modifiedFlags.clear(WifiConfiguration.KeyMgmt.FT_PSK);
-        modifiedFlags.clear(WifiConfiguration.KeyMgmt.FT_EAP);
-        return modifiedFlags;
+        synchronized (mLock) {
+            BitSet modifiedFlags = (BitSet) keyManagementFlags.clone();
+            modifiedFlags.clear(WifiConfiguration.KeyMgmt.FT_PSK);
+            modifiedFlags.clear(WifiConfiguration.KeyMgmt.FT_EAP);
+            return modifiedFlags;
+        }
     }
 
     /**
@@ -2496,8 +2540,8 @@
         @Override
         public void onNetworkEapSimGsmAuthRequest(
                 ISupplicantStaNetworkCallback.NetworkRequestEapSimGsmAuthParams params) {
-            logCallback("onNetworkEapSimGsmAuthRequest");
             synchronized (mLock) {
+                logCallback("onNetworkEapSimGsmAuthRequest");
                 String[] data = new String[params.rands.size()];
                 int i = 0;
                 for (byte[] rand : params.rands) {
@@ -2511,8 +2555,8 @@
         @Override
         public void onNetworkEapSimUmtsAuthRequest(
                 ISupplicantStaNetworkCallback.NetworkRequestEapSimUmtsAuthParams params) {
-            logCallback("onNetworkEapSimUmtsAuthRequest");
             synchronized (mLock) {
+                logCallback("onNetworkEapSimUmtsAuthRequest");
                 String randHex = NativeUtil.hexStringFromByteArray(params.rand);
                 String autnHex = NativeUtil.hexStringFromByteArray(params.autn);
                 String[] data = {randHex, autnHex};
@@ -2523,8 +2567,8 @@
 
         @Override
         public void onNetworkEapIdentityRequest() {
-            logCallback("onNetworkEapIdentityRequest");
             synchronized (mLock) {
+                logCallback("onNetworkEapIdentityRequest");
                 mWifiMonitor.broadcastNetworkIdentityRequestEvent(
                         mIfaceName, mFramewokNetworkId, mSsid);
             }
diff --git a/service/java/com/android/server/wifi/WifiApConfigStore.java b/service/java/com/android/server/wifi/WifiApConfigStore.java
index 9c90bcf..98a5932 100644
--- a/service/java/com/android/server/wifi/WifiApConfigStore.java
+++ b/service/java/com/android/server/wifi/WifiApConfigStore.java
@@ -16,13 +16,16 @@
 
 package com.android.server.wifi;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiConfiguration.KeyMgmt;
 import android.os.Environment;
+import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
@@ -31,6 +34,7 @@
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Random;
 import java.util.UUID;
@@ -50,6 +54,15 @@
     private static final int RAND_SSID_INT_MIN = 1000;
     private static final int RAND_SSID_INT_MAX = 9999;
 
+    @VisibleForTesting
+    static final int SSID_MIN_LEN = 1;
+    @VisibleForTesting
+    static final int SSID_MAX_LEN = 32;
+    @VisibleForTesting
+    static final int PSK_MIN_LEN = 8;
+    @VisibleForTesting
+    static final int PSK_MAX_LEN = 63;
+
     private WifiConfiguration mWifiApConfig = null;
 
     private ArrayList<Integer> mAllowed2GChannel = null;
@@ -224,4 +237,110 @@
         config.preSharedKey = randomUUID.substring(0, 8) + randomUUID.substring(9, 13);
         return config;
     }
+
+    /**
+     * Verify provided SSID for existence, length and conversion to bytes
+     *
+     * @param ssid String ssid name
+     * @return boolean indicating ssid met requirements
+     */
+    private static boolean validateApConfigSsid(String ssid) {
+        if (TextUtils.isEmpty(ssid)) {
+            Log.d(TAG, "SSID for softap configuration must be set.");
+            return false;
+        }
+
+        if (ssid.length() < SSID_MIN_LEN || ssid.length() > SSID_MAX_LEN) {
+            Log.d(TAG, "SSID for softap configuration string size must be at least "
+                    + SSID_MIN_LEN + " and not more than " + SSID_MAX_LEN);
+            return false;
+        }
+
+        try {
+            ssid.getBytes(StandardCharsets.UTF_8);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "softap config SSID verification failed: malformed string " + ssid);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Verify provided preSharedKey in ap config for WPA2_PSK network meets requirements.
+     */
+    private static boolean validateApConfigPreSharedKey(String preSharedKey) {
+        if (preSharedKey.length() < PSK_MIN_LEN || preSharedKey.length() > PSK_MAX_LEN) {
+            Log.d(TAG, "softap network password string size must be at least " + PSK_MIN_LEN
+                    + " and no more than " + PSK_MAX_LEN);
+            return false;
+        }
+
+        try {
+            preSharedKey.getBytes(StandardCharsets.UTF_8);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "softap network password verification failed: malformed string");
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Validate a WifiConfiguration is properly configured for use by SoftApManager.
+     *
+     * This method checks the length of the SSID and for sanity between security settings (if it
+     * requires a password, was one provided?).
+     *
+     * @param apConfig {@link WifiConfiguration} to use for softap mode
+     * @return boolean true if the provided config meets the minimum set of details, false
+     * otherwise.
+     */
+    static boolean validateApWifiConfiguration(@NonNull WifiConfiguration apConfig) {
+        // first check the SSID
+        if (!validateApConfigSsid(apConfig.SSID)) {
+            // failed SSID verificiation checks
+            return false;
+        }
+
+        // now check security settings: settings app allows open and WPA2 PSK
+        if (apConfig.allowedKeyManagement == null) {
+            Log.d(TAG, "softap config key management bitset was null");
+            return false;
+        }
+
+        String preSharedKey = apConfig.preSharedKey;
+        boolean hasPreSharedKey = !TextUtils.isEmpty(preSharedKey);
+        int authType;
+
+        try {
+            authType = apConfig.getAuthType();
+        } catch (IllegalStateException e) {
+            Log.d(TAG, "Unable to get AuthType for softap config: " + e.getMessage());
+            return false;
+        }
+
+        if (authType == KeyMgmt.NONE) {
+            // open networks should not have a password
+            if (hasPreSharedKey) {
+                Log.d(TAG, "open softap network should not have a password");
+                return false;
+            }
+        } else if (authType == KeyMgmt.WPA2_PSK) {
+            // this is a config that should have a password - check that first
+            if (!hasPreSharedKey) {
+                Log.d(TAG, "softap network password must be set");
+                return false;
+            }
+
+            if (!validateApConfigPreSharedKey(preSharedKey)) {
+                // failed preSharedKey checks
+                return false;
+            }
+        } else {
+            // this is not a supported security type
+            Log.d(TAG, "softap configs must either be open or WPA2 PSK networks");
+            return false;
+        }
+
+        return true;
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiConfigManager.java b/service/java/com/android/server/wifi/WifiConfigManager.java
index 25a5a20..d8b4237 100644
--- a/service/java/com/android/server/wifi/WifiConfigManager.java
+++ b/service/java/com/android/server/wifi/WifiConfigManager.java
@@ -49,7 +49,6 @@
 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.ScanResultUtil;
 import com.android.server.wifi.util.TelephonyUtil;
 import com.android.server.wifi.util.WifiPermissionsUtil;
 import com.android.server.wifi.util.WifiPermissionsWrapper;
@@ -123,7 +122,8 @@
             1,  //  threshold for DISABLED_AUTHENTICATION_NO_CREDENTIALS
             1,  //  threshold for DISABLED_NO_INTERNET
             1,  //  threshold for DISABLED_BY_WIFI_MANAGER
-            1   //  threshold for DISABLED_BY_USER_SWITCH
+            1,  //  threshold for DISABLED_BY_USER_SWITCH
+            1   //  threshold for DISABLED_BY_WRONG_PASSWORD
     };
     /**
      * Network Selection disable timeout for each kind of error. After the timeout milliseconds,
@@ -144,7 +144,8 @@
             Integer.MAX_VALUE,  // threshold for DISABLED_AUTHENTICATION_NO_CREDENTIALS
             Integer.MAX_VALUE,  // threshold for DISABLED_NO_INTERNET
             Integer.MAX_VALUE,  // threshold for DISABLED_BY_WIFI_MANAGER
-            Integer.MAX_VALUE   // threshold for DISABLED_BY_USER_SWITCH
+            Integer.MAX_VALUE,  // threshold for DISABLED_BY_USER_SWITCH
+            Integer.MAX_VALUE   // threshold for DISABLED_BY_WRONG_PASSWORD
     };
     /**
      * Interface for other modules to listen to the saved network updated
@@ -304,6 +305,10 @@
      */
     private boolean mDeferredUserUnlockRead = false;
     /**
+     * Flag to indicate if SIM is present.
+     */
+    private boolean mSimPresent = false;
+    /**
      * This is keeping track of the next network ID to be assigned. Any new networks will be
      * assigned |mNextNetworkId| as network ID.
      */
@@ -677,10 +682,10 @@
 
         final boolean isCreator = (config.creatorUid == uid);
 
-        // Check if the |uid| holds the |OVERRIDE_CONFIG_WIFI| permission if the caller asks us to
+        // Check if the |uid| holds the |NETWORK_SETTINGS| permission if the caller asks us to
         // bypass the lockdown checks.
         if (ignoreLockdown) {
-            return mWifiPermissionsUtil.checkConfigOverridePermission(uid);
+            return mWifiPermissionsUtil.checkNetworkSettingsPermission(uid);
         }
 
         // Check if device has DPM capability. If it has and |dpmi| is still null, then we
@@ -695,13 +700,14 @@
         final boolean isConfigEligibleForLockdown = dpmi != null && dpmi.isActiveAdminWithPolicy(
                 config.creatorUid, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
         if (!isConfigEligibleForLockdown) {
-            return isCreator || mWifiPermissionsUtil.checkConfigOverridePermission(uid);
+            return isCreator || mWifiPermissionsUtil.checkNetworkSettingsPermission(uid);
         }
 
         final ContentResolver resolver = mContext.getContentResolver();
         final boolean isLockdownFeatureEnabled = Settings.Global.getInt(resolver,
                 Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) != 0;
-        return !isLockdownFeatureEnabled && mWifiPermissionsUtil.checkConfigOverridePermission(uid);
+        return !isLockdownFeatureEnabled
+                && mWifiPermissionsUtil.checkNetworkSettingsPermission(uid);
     }
 
     /**
@@ -942,6 +948,10 @@
         WifiConfiguration existingInternalConfig = getInternalConfiguredNetwork(config);
         // No existing network found. So, potentially a network add.
         if (existingInternalConfig == null) {
+            if (!WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD)) {
+                Log.e(TAG, "Cannot add network with invalid config");
+                return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
+            }
             newInternalConfig = createNewInternalWifiConfigurationFromExternal(config, uid);
             // Since the original config provided may have had an empty
             // {@link WifiConfiguration#allowedKeyMgmt} field, check again if we already have a
@@ -950,6 +960,11 @@
         }
         // Existing network found. So, a network update.
         if (existingInternalConfig != null) {
+            if (!WifiConfigurationUtil.validate(
+                    config, WifiConfigurationUtil.VALIDATE_FOR_UPDATE)) {
+                Log.e(TAG, "Cannot update network with invalid config");
+                return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
+            }
             // Check for the app's permission before we let it update this network.
             if (!canModifyNetwork(existingInternalConfig, uid, DISALLOW_LOCKDOWN_CHECK_BYPASS)) {
                 Log.e(TAG, "UID " + uid + " does not have permission to update configuration "
@@ -965,7 +980,7 @@
         if (WifiConfigurationUtil.hasProxyChanged(existingInternalConfig, newInternalConfig)
                 && !canModifyProxySettings(uid)) {
             Log.e(TAG, "UID " + uid + " does not have permission to modify proxy Settings "
-                    + config.configKey() + ". Must have OVERRIDE_WIFI_CONFIG,"
+                    + config.configKey() + ". Must have NETWORK_SETTINGS,"
                     + " or be device or profile owner.");
             return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
         }
@@ -998,7 +1013,12 @@
 
         // Add it to our internal map. This will replace any existing network configuration for
         // updates.
-        mConfiguredNetworks.put(newInternalConfig);
+        try {
+            mConfiguredNetworks.put(newInternalConfig);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Failed to add network to config map", e);
+            return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
+        }
 
         if (mDeletedEphemeralSSIDs.remove(config.SSID)) {
             if (mVerboseLoggingEnabled) {
@@ -1188,6 +1208,33 @@
     }
 
     /**
+     * Iterates through the internal list of configured networks and removes any ephemeral or
+     * passpoint network configurations which are transient in nature.
+     *
+     * @return true if a network was removed, false otherwise.
+     */
+    public boolean removeAllEphemeralOrPasspointConfiguredNetworks() {
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "Removing all passpoint or ephemeral configured networks");
+        }
+        boolean didRemove = false;
+        WifiConfiguration[] copiedConfigs =
+                mConfiguredNetworks.valuesForAllUsers().toArray(new WifiConfiguration[0]);
+        for (WifiConfiguration config : copiedConfigs) {
+            if (config.isPasspoint()) {
+                Log.d(TAG, "Removing passpoint network config " + config.configKey());
+                removeNetwork(config.networkId, mSystemUiUid);
+                didRemove = true;
+            } else if (config.ephemeral) {
+                Log.d(TAG, "Removing ephemeral network config " + config.configKey());
+                removeNetwork(config.networkId, mSystemUiUid);
+                didRemove = true;
+            }
+        }
+        return didRemove;
+    }
+
+    /**
      * Helper method to mark a network enabled for network selection.
      */
     private void setNetworkSelectionEnabled(WifiConfiguration config) {
@@ -1479,7 +1526,7 @@
      * @return true if |uid| has the necessary permission to trigger explicit connection to the
      * network, false otherwise.
      * Note: This returns true only for the system settings/sysui app which holds the
-     * {@link android.Manifest.permission#OVERRIDE_WIFI_CONFIG} permission. We don't want to let
+     * {@link android.Manifest.permission#NETWORK_SETTINGS} permission. We don't want to let
      * any other app force connection to a network.
      */
     public boolean checkAndUpdateLastConnectUid(int networkId, int uid) {
@@ -1885,41 +1932,44 @@
     }
 
     /**
-     * Retrieves a saved network corresponding to the provided scan detail if one exists.
+     * Retrieves a configured network corresponding to the provided scan detail if one exists.
      *
      * @param scanDetail ScanDetail instance  to use for looking up the network.
      * @return WifiConfiguration object representing the network corresponding to the scanDetail,
      * null if none exists.
      */
-    private WifiConfiguration getSavedNetworkForScanDetail(ScanDetail scanDetail) {
+    public WifiConfiguration getConfiguredNetworkForScanDetail(ScanDetail scanDetail) {
         ScanResult scanResult = scanDetail.getScanResult();
         if (scanResult == null) {
             Log.e(TAG, "No scan result found in scan detail");
             return null;
         }
-        for (WifiConfiguration config : getInternalConfiguredNetworks()) {
-            if (ScanResultUtil.doesScanResultMatchWithNetwork(scanResult, config)) {
-                if (mVerboseLoggingEnabled) {
-                    Log.v(TAG, "getSavedNetworkFromScanDetail Found " + config.configKey()
-                            + " for " + scanResult.SSID + "[" + scanResult.capabilities + "]");
-                }
-                return config;
+        WifiConfiguration config = null;
+        try {
+            config = mConfiguredNetworks.getByScanResultForCurrentUser(scanResult);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Failed to lookup network from config map", e);
+        }
+        if (config != null) {
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "getSavedNetworkFromScanDetail Found " + config.configKey()
+                        + " for " + scanResult.SSID + "[" + scanResult.capabilities + "]");
             }
         }
-        return null;
+        return config;
     }
 
     /**
-     * Retrieves a saved network corresponding to the provided scan detail if one exists and caches
-     * the provided |scanDetail| into the corresponding scan detail cache entry
+     * Retrieves a configured network corresponding to the provided scan detail if one exists and
+     * caches the provided |scanDetail| into the corresponding scan detail cache entry
      * {@link #mScanDetailCaches} for the retrieved network.
      *
      * @param scanDetail input a scanDetail from the scan result
      * @return WifiConfiguration object representing the network corresponding to the scanDetail,
      * null if none exists.
      */
-    public WifiConfiguration getSavedNetworkForScanDetailAndCache(ScanDetail scanDetail) {
-        WifiConfiguration network = getSavedNetworkForScanDetail(scanDetail);
+    public WifiConfiguration getConfiguredNetworkForScanDetailAndCache(ScanDetail scanDetail) {
+        WifiConfiguration network = getConfiguredNetworkForScanDetail(scanDetail);
         if (network == null) {
             return null;
         }
@@ -2270,7 +2320,8 @@
         Iterator<WifiConfiguration> iter = networks.iterator();
         while (iter.hasNext()) {
             WifiConfiguration config = iter.next();
-            if (config.getNetworkSelectionStatus().isNetworkPermanentlyDisabled()) {
+            if (config.ephemeral || config.isPasspoint()
+                    || config.getNetworkSelectionStatus().isNetworkPermanentlyDisabled()) {
                 iter.remove();
             }
         }
@@ -2365,11 +2416,14 @@
     /**
      * Resets all sim networks state.
      */
-    public void resetSimNetworks() {
+    public void resetSimNetworks(boolean simPresent) {
         if (mVerboseLoggingEnabled) localLog("resetSimNetworks");
         for (WifiConfiguration config : getInternalConfiguredNetworks()) {
             if (TelephonyUtil.isSimConfig(config)) {
-                String currentIdentity = TelephonyUtil.getSimIdentity(mTelephonyManager, config);
+                String currentIdentity = null;
+                if (simPresent) {
+                    currentIdentity = TelephonyUtil.getSimIdentity(mTelephonyManager, config);
+                }
                 // Update the loaded config
                 config.enterpriseConfig.setIdentity(currentIdentity);
                 if (config.enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.PEAP) {
@@ -2377,6 +2431,16 @@
                 }
             }
         }
+        mSimPresent = simPresent;
+    }
+
+    /**
+     * Check if SIM is present.
+     *
+     * @return True if SIM is present, otherwise false.
+     */
+    public boolean isSimPresent() {
+        return mSimPresent;
     }
 
     /**
@@ -2554,7 +2618,11 @@
             if (mVerboseLoggingEnabled) {
                 Log.v(TAG, "Adding network from shared store " + configuration.configKey());
             }
-            mConfiguredNetworks.put(configuration);
+            try {
+                mConfiguredNetworks.put(configuration);
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Failed to add network to config map", e);
+            }
         }
     }
 
@@ -2573,7 +2641,11 @@
             if (mVerboseLoggingEnabled) {
                 Log.v(TAG, "Adding network from user store " + configuration.configKey());
             }
-            mConfiguredNetworks.put(configuration);
+            try {
+                mConfiguredNetworks.put(configuration);
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Failed to add network to config map", e);
+            }
         }
         for (String ssid : deletedEphemeralSSIDs) {
             mDeletedEphemeralSSIDs.add(ssid);
@@ -2815,15 +2887,15 @@
                 DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
         final boolean isUidDeviceOwner = dpmi != null && dpmi.isActiveAdminWithPolicy(uid,
                 DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
-        final boolean hasConfigOverridePermission =
-                mWifiPermissionsUtil.checkConfigOverridePermission(uid);
+        final boolean hasNetworkSettingsPermission =
+                mWifiPermissionsUtil.checkNetworkSettingsPermission(uid);
         // If |uid| corresponds to the device owner, allow all modifications.
-        if (isUidDeviceOwner || isUidProfileOwner || hasConfigOverridePermission) {
+        if (isUidDeviceOwner || isUidProfileOwner || hasNetworkSettingsPermission) {
             return true;
         }
         if (mVerboseLoggingEnabled) {
             Log.v(TAG, "UID: " + uid + " cannot modify WifiConfiguration proxy settings."
-                    + " ConfigOverride=" + hasConfigOverridePermission
+                    + " ConfigOverride=" + hasNetworkSettingsPermission
                     + " DeviceOwner=" + isUidDeviceOwner
                     + " ProfileOwner=" + isUidProfileOwner);
         }
diff --git a/service/java/com/android/server/wifi/WifiConfigurationUtil.java b/service/java/com/android/server/wifi/WifiConfigurationUtil.java
index bbfde46..fef78aa 100644
--- a/service/java/com/android/server/wifi/WifiConfigurationUtil.java
+++ b/service/java/com/android/server/wifi/WifiConfigurationUtil.java
@@ -18,15 +18,20 @@
 
 import android.content.pm.UserInfo;
 import android.net.IpConfiguration;
+import android.net.StaticIpConfiguration;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiEnterpriseConfig;
 import android.net.wifi.WifiScanner;
 import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wifi.util.NativeUtil;
 
 import java.security.cert.X509Certificate;
 import java.util.Arrays;
+import java.util.BitSet;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Objects;
@@ -38,6 +43,22 @@
  *   > Helper methods to identify the encryption of a WifiConfiguration object.
  */
 public class WifiConfigurationUtil {
+    private static final String TAG = "WifiConfigurationUtil";
+
+    /**
+     * Constants used for validating external config objects.
+     */
+    private static final int ENCLOSING_QUTOES_LEN = 2;
+    private static final int SSID_ASCII_MIN_LEN = 1 + ENCLOSING_QUTOES_LEN;
+    private static final int SSID_ASCII_MAX_LEN = 32 + ENCLOSING_QUTOES_LEN;
+    private static final int SSID_HEX_MIN_LEN = 2;
+    private static final int SSID_HEX_MAX_LEN = 64;
+    private static final int PSK_ASCII_MIN_LEN = 8 + ENCLOSING_QUTOES_LEN;
+    private static final int PSK_ASCII_MAX_LEN = 63 + ENCLOSING_QUTOES_LEN;
+    private static final int PSK_HEX_LEN = 64;
+    @VisibleForTesting
+    public static final String PASSWORD_MASK = "*";
+
     /**
      * Check whether a network configuration is visible to a user or any of its managed profiles.
      *
@@ -172,6 +193,12 @@
                     != newEnterpriseConfig.getPhase2Method()) {
                 return true;
             }
+            if (!TextUtils.equals(existingEnterpriseConfig.getIdentity(),
+                                  newEnterpriseConfig.getIdentity())
+                    || !TextUtils.equals(existingEnterpriseConfig.getAnonymousIdentity(),
+                                         newEnterpriseConfig.getAnonymousIdentity())) {
+                return true;
+            }
             X509Certificate[] existingCaCerts = existingEnterpriseConfig.getCaCertificates();
             X509Certificate[] newCaCerts = newEnterpriseConfig.getCaCertificates();
             if (!Arrays.equals(existingCaCerts, newCaCerts)) {
@@ -234,12 +261,185 @@
         return false;
     }
 
+    private static boolean validateSsid(String ssid, boolean isAdd) {
+        if (isAdd) {
+            if (ssid == null) {
+                Log.e(TAG, "validateSsid : null string");
+                return false;
+            }
+        } else {
+            if (ssid == null) {
+                // This is an update, so the SSID can be null if that is not being changed.
+                return true;
+            }
+        }
+        if (ssid.isEmpty()) {
+            Log.e(TAG, "validateSsid failed: empty string");
+            return false;
+        }
+        if (ssid.startsWith("\"")) {
+            // ASCII SSID string
+            if (ssid.length() < SSID_ASCII_MIN_LEN) {
+                Log.e(TAG, "validateSsid failed: ascii string size too small: " + ssid.length());
+                return false;
+            }
+            if (ssid.length() > SSID_ASCII_MAX_LEN) {
+                Log.e(TAG, "validateSsid failed: ascii string size too large: " + ssid.length());
+                return false;
+            }
+        } else {
+            // HEX SSID string
+            if (ssid.length() < SSID_HEX_MIN_LEN) {
+                Log.e(TAG, "validateSsid failed: hex string size too small: " + ssid.length());
+                return false;
+            }
+            if (ssid.length() > SSID_HEX_MAX_LEN) {
+                Log.e(TAG, "validateSsid failed: hex string size too large: " + ssid.length());
+                return false;
+            }
+        }
+        try {
+            NativeUtil.decodeSsid(ssid);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "validateSsid failed: malformed string: " + ssid);
+            return false;
+        }
+        return true;
+    }
+
+    private static boolean validatePsk(String psk, boolean isAdd) {
+        if (isAdd) {
+            if (psk == null) {
+                Log.e(TAG, "validatePsk: null string");
+                return false;
+            }
+        } else {
+            if (psk == null) {
+                // This is an update, so the psk can be null if that is not being changed.
+                return true;
+            } else if (psk.equals(PASSWORD_MASK)) {
+                // This is an update, so the app might have returned back the masked password, let
+                // it thru. WifiConfigManager will handle it.
+                return true;
+            }
+        }
+        if (psk.isEmpty()) {
+            Log.e(TAG, "validatePsk failed: empty string");
+            return false;
+        }
+        if (psk.startsWith("\"")) {
+            // ASCII PSK string
+            if (psk.length() < PSK_ASCII_MIN_LEN) {
+                Log.e(TAG, "validatePsk failed: ascii string size too small: " + psk.length());
+                return false;
+            }
+            if (psk.length() > PSK_ASCII_MAX_LEN) {
+                Log.e(TAG, "validatePsk failed: ascii string size too large: " + psk.length());
+                return false;
+            }
+        } else {
+            // HEX PSK string
+            if (psk.length() != PSK_HEX_LEN) {
+                Log.e(TAG, "validatePsk failed: hex string size mismatch: " + psk.length());
+                return false;
+            }
+        }
+        try {
+            NativeUtil.hexOrQuotedAsciiStringToBytes(psk);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "validatePsk failed: malformed string: " + psk);
+            return false;
+        }
+        return true;
+    }
+
+    private static boolean validateKeyMgmt(BitSet keyMgmnt) {
+        if (keyMgmnt == null) {
+            Log.e(TAG, "validateKeyMgmt failed: null bitset");
+            return false;
+        }
+        if (keyMgmnt.cardinality() > 1) {
+            if (keyMgmnt.cardinality() != 2) {
+                Log.e(TAG, "validateKeyMgmt failed: cardinality != 2");
+                return false;
+            }
+            if (!keyMgmnt.get(WifiConfiguration.KeyMgmt.WPA_EAP)) {
+                Log.e(TAG, "validateKeyMgmt failed: not WPA_EAP");
+                return false;
+            }
+            if (!keyMgmnt.get(WifiConfiguration.KeyMgmt.IEEE8021X)
+                    && !keyMgmnt.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
+                Log.e(TAG, "validateKeyMgmt failed: not PSK or 8021X");
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static boolean validateIpConfiguration(IpConfiguration ipConfig) {
+        if (ipConfig == null) {
+            Log.e(TAG, "validateIpConfiguration failed: null IpConfiguration");
+            return false;
+        }
+        if (ipConfig.getIpAssignment() == IpConfiguration.IpAssignment.STATIC) {
+            StaticIpConfiguration staticIpConfig = ipConfig.getStaticIpConfiguration();
+            if (staticIpConfig == null) {
+                Log.e(TAG, "validateIpConfiguration failed: null StaticIpConfiguration");
+                return false;
+            }
+            if (staticIpConfig.ipAddress == null) {
+                Log.e(TAG, "validateIpConfiguration failed: null static ip Address");
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Enums to specify if the provided config is being validated for add or update.
+     */
+    public static final boolean VALIDATE_FOR_ADD = true;
+    public static final boolean VALIDATE_FOR_UPDATE = false;
+
+    /**
+     * Validate the configuration received from an external application.
+     *
+     * This method checks for the following parameters:
+     * 1. {@link WifiConfiguration#SSID}
+     * 2. {@link WifiConfiguration#preSharedKey}
+     * 3. {@link WifiConfiguration#allowedKeyManagement}
+     * 4. {@link WifiConfiguration#getIpConfiguration()}
+     *
+     * @param config {@link WifiConfiguration} received from an external application.
+     * @param isAdd {@link #VALIDATE_FOR_ADD} to indicate a network config received for an add,
+     *              {@link #VALIDATE_FOR_UPDATE} for a network config received for an update.
+     *              These 2 cases need to be handled differently because the config received for an
+     *              update could contain only the fields that are being changed.
+     * @return true if the parameters are valid, false otherwise.
+     */
+    public static boolean validate(WifiConfiguration config, boolean isAdd) {
+        if (!validateSsid(config.SSID, isAdd)) {
+            return false;
+        }
+        if (!validateKeyMgmt(config.allowedKeyManagement)) {
+            return false;
+        }
+        if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)
+                && !validatePsk(config.preSharedKey, isAdd)) {
+            return false;
+        }
+        if (!validateIpConfiguration(config.getIpConfiguration())) {
+            return false;
+        }
+        // TBD: Validate some enterprise params as well in the future here.
+        return true;
+    }
+
     /**
      * Check if the provided two networks are the same.
-     * Note: This does not check if network selection BSSID's are the same.
      *
-     * @param config  Configuration corresponding to a network.
-     * @param config1 Configuration corresponding to another network.
+     * @param config      Configuration corresponding to a network.
+     * @param config1      Configuration corresponding to another network.
      *
      * @return true if |config| and |config1| are the same network.
      *         false otherwise.
@@ -257,6 +457,13 @@
         if (!Objects.equals(config.SSID, config1.SSID)) {
             return false;
         }
+        String networkSelectionBSSID = config.getNetworkSelectionStatus()
+                .getNetworkSelectionBSSID();
+        String networkSelectionBSSID1 = config1.getNetworkSelectionStatus()
+                .getNetworkSelectionBSSID();
+        if (!Objects.equals(networkSelectionBSSID, networkSelectionBSSID1)) {
+            return false;
+        }
         if (WifiConfigurationUtil.hasCredentialChanged(config, config1)) {
             return false;
         }
diff --git a/service/java/com/android/server/wifi/WifiConnectivityManager.java b/service/java/com/android/server/wifi/WifiConnectivityManager.java
index f45d17b..344242a 100644
--- a/service/java/com/android/server/wifi/WifiConnectivityManager.java
+++ b/service/java/com/android/server/wifi/WifiConnectivityManager.java
@@ -20,6 +20,7 @@
 
 import android.app.AlarmManager;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.net.wifi.ScanResult;
 import android.net.wifi.SupplicantState;
 import android.net.wifi.WifiConfiguration;
@@ -107,10 +108,6 @@
     public static final int MAX_CONNECTION_ATTEMPTS_TIME_INTERVAL_MS = 4 * 60 * 1000; // 4 mins
     // Max number of connection attempts in the above time interval.
     public static final int MAX_CONNECTION_ATTEMPTS_RATE = 6;
-    // Packet tx/rx rates to determine if we want to do partial vs full scans.
-    // TODO(b/31180330): Make these device configs.
-    public static final int MAX_TX_PACKET_FOR_FULL_SCANS = 8;
-    public static final int MAX_RX_PACKET_FOR_FULL_SCANS = 16;
 
     // WifiStateMachine has a bunch of states. From the
     // WifiConnectivityManager's perspective it only cares
@@ -136,6 +133,7 @@
     private final WifiConnectivityHelper mConnectivityHelper;
     private final WifiNetworkSelector mNetworkSelector;
     private final WifiLastResortWatchdog mWifiLastResortWatchdog;
+    private final WifiNotificationController mWifiNotificationController;
     private final WifiMetrics mWifiMetrics;
     private final AlarmManager mAlarmManager;
     private final Handler mEventHandler;
@@ -160,6 +158,8 @@
     // Device configs
     private boolean mEnableAutoJoinWhenAssociated;
     private boolean mWaitForFullBandScanResults = false;
+    private int mFullScanMaxTxRate;
+    private int mFullScanMaxRxRate;
 
     // PNO settings
     private int mMin5GHzRssi;
@@ -262,13 +262,17 @@
                 mStateMachine.isConnected(), mStateMachine.isDisconnected(),
                 mUntrustedConnectionAllowed);
         mWifiLastResortWatchdog.updateAvailableNetworks(
-                mNetworkSelector.getFilteredScanDetails());
+                mNetworkSelector.getConnectableScanDetails());
         mWifiMetrics.countScanResults(scanDetails);
         if (candidate != null) {
             localLog(listenerName + ":  WNS candidate-" + candidate.SSID);
             connectToNetwork(candidate);
             return true;
         } else {
+            if (mWifiState == WIFI_STATE_DISCONNECTED) {
+                mWifiNotificationController.handleScanResults(
+                        mNetworkSelector.getFilteredScanDetailsForOpenUnsavedNetworks());
+            }
             return false;
         }
     }
@@ -317,7 +321,10 @@
                     mWaitForFullBandScanResults = false;
                 }
             }
-
+            if (results.length > 0) {
+                mWifiMetrics.incrementAvailableNetworksHistograms(mScanDetails,
+                        results[0].isAllChannelsScanned());
+            }
             boolean wasConnectAttempted = handleScanResults(mScanDetails, "AllSingleScanListener");
             clearScanDetails();
 
@@ -528,7 +535,8 @@
     WifiConnectivityManager(Context context, WifiStateMachine stateMachine,
             WifiScanner scanner, WifiConfigManager configManager, WifiInfo wifiInfo,
             WifiNetworkSelector networkSelector, WifiConnectivityHelper connectivityHelper,
-            WifiLastResortWatchdog wifiLastResortWatchdog, WifiMetrics wifiMetrics,
+            WifiLastResortWatchdog wifiLastResortWatchdog,
+            WifiNotificationController wifiNotificationController, WifiMetrics wifiMetrics,
             Looper looper, Clock clock, LocalLog localLog, boolean enable,
             FrameworkFacade frameworkFacade,
             SavedNetworkEvaluator savedNetworkEvaluator,
@@ -542,6 +550,7 @@
         mConnectivityHelper = connectivityHelper;
         mLocalLog = localLog;
         mWifiLastResortWatchdog = wifiLastResortWatchdog;
+        mWifiNotificationController = wifiNotificationController;
         mWifiMetrics = wifiMetrics;
         mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
         mEventHandler = new Handler(looper);
@@ -570,6 +579,10 @@
                             R.integer.config_wifi_framework_RSSI_SCORE_OFFSET))
                 * context.getResources().getInteger(
                         R.integer.config_wifi_framework_RSSI_SCORE_SLOPE);
+        mFullScanMaxTxRate = context.getResources().getInteger(
+                R.integer.config_wifi_framework_max_tx_rate_for_full_scan);
+        mFullScanMaxRxRate = context.getResources().getInteger(
+                R.integer.config_wifi_framework_max_rx_rate_for_full_scan);
 
         localLog("PNO settings:" + " min5GHzRssi " + mMin5GHzRssi
                 + " min24GHzRssi " + mMin24GHzRssi
@@ -578,8 +591,8 @@
                 + " secureNetworkBonus " + mSecureBonus
                 + " initialScoreMax " + mInitialScoreMax);
 
-        boolean hs2Enabled = context.getResources().getBoolean(
-                R.bool.config_wifi_hotspot2_enabled);
+        boolean hs2Enabled = context.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_WIFI_PASSPOINT);
         localLog("Passpoint is: " + (hs2Enabled ? "enabled" : "disabled"));
 
         // Register the network evaluators
@@ -679,7 +692,7 @@
             return;
         }
 
-        Long elapsedTimeMillis = mClock.getElapsedSinceBootMillis();
+        long elapsedTimeMillis = mClock.getElapsedSinceBootMillis();
         if (!mScreenOn && shouldSkipConnectionAttempt(elapsedTimeMillis)) {
             localLog("connectToNetwork: Too many connection attempts. Skipping this attempt!");
             mTotalConnectivityAttemptsRateLimited++;
@@ -797,8 +810,8 @@
 
         // If the WiFi traffic is heavy, only partial scan is initiated.
         if (mWifiState == WIFI_STATE_CONNECTED
-                && (mWifiInfo.txSuccessRate > MAX_TX_PACKET_FOR_FULL_SCANS
-                    || mWifiInfo.rxSuccessRate > MAX_RX_PACKET_FOR_FULL_SCANS)) {
+                && (mWifiInfo.txSuccessRate > mFullScanMaxTxRate
+                    || mWifiInfo.rxSuccessRate > mFullScanMaxRxRate)) {
             localLog("No full band scan due to ongoing traffic");
             isFullBandScan = false;
         }
@@ -1024,6 +1037,8 @@
 
         mScreenOn = screenOn;
 
+        mWifiNotificationController.handleScreenStateChanged(screenOn);
+
         startConnectivityScan(SCAN_ON_SCHEDULE);
     }
 
@@ -1051,6 +1066,10 @@
 
         mWifiState = state;
 
+        if (mWifiState == WIFI_STATE_CONNECTED) {
+            mWifiNotificationController.clearPendingNotification(false /* resetRepeatDelay */);
+        }
+
         // Reset BSSID of last connection attempt and kick off
         // the watchdog timer if entering disconnected state.
         if (mWifiState == WIFI_STATE_DISCONNECTED) {
@@ -1284,6 +1303,7 @@
         stopConnectivityScan();
         clearBssidBlacklist();
         resetLastPeriodicSingleScanTimeStamp();
+        mWifiNotificationController.clearPendingNotification(true /* resetRepeatDelay */);
         mLastConnectionAttemptBssid = null;
         mWaitForFullBandScanResults = false;
     }
@@ -1343,5 +1363,6 @@
         pw.println("WifiConnectivityManager - Log Begin ----");
         mLocalLog.dump(fd, pw, args);
         pw.println("WifiConnectivityManager - Log End ----");
+        mWifiNotificationController.dump(fd, pw, args);
     }
 }
diff --git a/service/java/com/android/server/wifi/WifiDiagnostics.java b/service/java/com/android/server/wifi/WifiDiagnostics.java
index b2acb2a..42078ef 100644
--- a/service/java/com/android/server/wifi/WifiDiagnostics.java
+++ b/service/java/com/android/server/wifi/WifiDiagnostics.java
@@ -497,7 +497,8 @@
     }
 
     private boolean enableVerboseLoggingForDogfood() {
-        return false;
+        return true;
+
     }
 
     private BugReport captureBugreport(int errorCode, boolean captureFWDump) {
diff --git a/service/java/com/android/server/wifi/WifiInjector.java b/service/java/com/android/server/wifi/WifiInjector.java
index c517785..80daaea 100644
--- a/service/java/com/android/server/wifi/WifiInjector.java
+++ b/service/java/com/android/server/wifi/WifiInjector.java
@@ -44,6 +44,7 @@
 import com.android.server.am.BatteryStatsService;
 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;
@@ -160,7 +161,8 @@
         mWifiStateMachineHandlerThread = new HandlerThread("WifiStateMachine");
         mWifiStateMachineHandlerThread.start();
         Looper wifiStateMachineLooper = mWifiStateMachineHandlerThread.getLooper();
-        mWifiMetrics = new WifiMetrics(mClock, wifiStateMachineLooper);
+        WifiAwareMetrics awareMetrics = new WifiAwareMetrics(mClock);
+        mWifiMetrics = new WifiMetrics(mClock, wifiStateMachineLooper, awareMetrics);
         // Modules interacting with Native.
         mWifiMonitor = new WifiMonitor(this);
         mHalDeviceManager = new HalDeviceManager();
@@ -203,10 +205,12 @@
                 mWifiKeyStore, mWifiConfigStore, mWifiConfigStoreLegacy, mWifiPermissionsUtil,
                 mWifiPermissionsWrapper, new NetworkListStoreData(),
                 new DeletedEphemeralSsidsStoreData());
+        mWifiMetrics.setWifiConfigManager(mWifiConfigManager);
         mWifiConnectivityHelper = new WifiConnectivityHelper(mWifiNative);
         mConnectivityLocalLog = new LocalLog(ActivityManager.isLowRamDeviceStatic() ? 256 : 512);
         mWifiNetworkSelector = new WifiNetworkSelector(mContext, mWifiConfigManager, mClock,
                 mConnectivityLocalLog);
+        mWifiMetrics.setWifiNetworkSelector(mWifiNetworkSelector);
         mSavedNetworkEvaluator = new SavedNetworkEvaluator(mContext,
                 mWifiConfigManager, mClock, mConnectivityLocalLog, mWifiConnectivityHelper);
         mScoredNetworkEvaluator = new ScoredNetworkEvaluator(context, wifiStateMachineLooper,
@@ -214,21 +218,24 @@
                 mWifiNetworkScoreCache);
         mSimAccessor = new SIMAccessor(mContext);
         mPasspointManager = new PasspointManager(mContext, mWifiNative, mWifiKeyStore, mClock,
-                mSimAccessor, new PasspointObjectFactory(), mWifiConfigManager, mWifiConfigStore);
+                mSimAccessor, new PasspointObjectFactory(), mWifiConfigManager, mWifiConfigStore,
+                mWifiMetrics);
         mPasspointNetworkEvaluator = new PasspointNetworkEvaluator(
                 mPasspointManager, mWifiConfigManager, mConnectivityLocalLog);
+        mWifiMetrics.setPasspointManager(mPasspointManager);
         // mWifiStateMachine has an implicit dependency on mJavaRuntime due to WifiDiagnostics.
         mJavaRuntime = Runtime.getRuntime();
         mWifiStateMachine = new WifiStateMachine(mContext, mFrameworkFacade,
                 wifiStateMachineLooper, UserManager.get(mContext),
-                this, mBackupManagerProxy, mCountryCode, mWifiNative);
+                this, mBackupManagerProxy, mCountryCode, mWifiNative,
+                new WrongPasswordNotifier(mContext, mFrameworkFacade));
         mCertManager = new WifiCertManager(mContext);
         mNotificationController = new WifiNotificationController(mContext,
-                mWifiServiceHandlerThread.getLooper(), mFrameworkFacade, null, this);
+                mWifiStateMachineHandlerThread.getLooper(), mFrameworkFacade, null);
         mLockManager = new WifiLockManager(mContext, BatteryStatsService.getService());
         mWifiController = new WifiController(mContext, mWifiStateMachine, mSettingsStore,
                 mLockManager, mWifiServiceHandlerThread.getLooper(), mFrameworkFacade);
-        mSelfRecovery = new SelfRecovery(mWifiController);
+        mSelfRecovery = new SelfRecovery(mWifiController, mClock);
         mWifiLastResortWatchdog = new WifiLastResortWatchdog(mSelfRecovery, mWifiMetrics);
         mWifiMulticastLockManager = new WifiMulticastLockManager(mWifiStateMachine,
                 BatteryStatsService.getService());
@@ -300,10 +307,6 @@
         return mCertManager;
     }
 
-    public WifiNotificationController getWifiNotificationController() {
-        return mNotificationController;
-    }
-
     public WifiLockManager getWifiLockManager() {
         return mLockManager;
     }
@@ -427,9 +430,10 @@
                                                                boolean hasConnectionRequests) {
         return new WifiConnectivityManager(mContext, mWifiStateMachine, getWifiScanner(),
                 mWifiConfigManager, wifiInfo, mWifiNetworkSelector, mWifiConnectivityHelper,
-                mWifiLastResortWatchdog, mWifiMetrics, mWifiStateMachineHandlerThread.getLooper(),
-                mClock, mConnectivityLocalLog, hasConnectionRequests, mFrameworkFacade,
-                mSavedNetworkEvaluator, mScoredNetworkEvaluator, mPasspointNetworkEvaluator);
+                mWifiLastResortWatchdog, mNotificationController, mWifiMetrics,
+                mWifiStateMachineHandlerThread.getLooper(), mClock, mConnectivityLocalLog,
+                hasConnectionRequests, mFrameworkFacade, mSavedNetworkEvaluator,
+                mScoredNetworkEvaluator, mPasspointNetworkEvaluator);
     }
 
     public WifiPermissionsUtil getWifiPermissionsUtil() {
diff --git a/service/java/com/android/server/wifi/WifiLastResortWatchdog.java b/service/java/com/android/server/wifi/WifiLastResortWatchdog.java
index 4cc8a20..0c1050c 100644
--- a/service/java/com/android/server/wifi/WifiLastResortWatchdog.java
+++ b/service/java/com/android/server/wifi/WifiLastResortWatchdog.java
@@ -365,7 +365,7 @@
     /**
      * Clear failure counts for each network in recentAvailableNetworks
      */
-    private void clearAllFailureCounts() {
+    public void clearAllFailureCounts() {
         if (mVerboseLoggingEnabled) Log.v(TAG, "clearAllFailureCounts.");
         for (Map.Entry<String, AvailableNetworkFailureCount> entry
                 : mRecentAvailableNetworks.entrySet()) {
diff --git a/service/java/com/android/server/wifi/WifiMetrics.java b/service/java/com/android/server/wifi/WifiMetrics.java
index 92a6fd6..7dfdb86 100644
--- a/service/java/com/android/server/wifi/WifiMetrics.java
+++ b/service/java/com/android/server/wifi/WifiMetrics.java
@@ -28,9 +28,14 @@
 import android.os.Message;
 import android.util.Base64;
 import android.util.Log;
+import android.util.Pair;
 import android.util.SparseIntArray;
 
+import com.android.server.wifi.aware.WifiAwareMetrics;
 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.nano.WifiMetricsProto;
 import com.android.server.wifi.nano.WifiMetricsProto.StaEvent;
 import com.android.server.wifi.nano.WifiMetricsProto.StaEvent.ConfigInfo;
@@ -42,8 +47,10 @@
 import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.Calendar;
+import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Provides storage for wireless connectivity metrics, as they are generated.
@@ -69,10 +76,20 @@
     private static final int MAX_WIFI_SCORE = NetworkAgent.WIFI_BASE_SCORE;
     private final Object mLock = new Object();
     private static final int MAX_CONNECTION_EVENTS = 256;
+    // Largest bucket in the NumConnectableNetworkCount histogram,
+    // anything large will be stored in this bucket
+    public static final int MAX_CONNECTABLE_SSID_NETWORK_BUCKET = 20;
+    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;
     private Clock mClock;
     private boolean mScreenOn;
     private int mWifiState;
+    private WifiAwareMetrics mWifiAwareMetrics;
     private Handler mHandler;
+    private WifiConfigManager mWifiConfigManager;
+    private WifiNetworkSelector mWifiNetworkSelector;
+    private PasspointManager mPasspointManager;
     /**
      * Metrics are stored within an instance of the WifiLog proto during runtime,
      * The ConnectionEvent, SystemStateEntries & ScanReturnEntries metrics are stored during
@@ -117,6 +134,20 @@
     private final SparseIntArray mWifiScoreCounts = new SparseIntArray();
     /** Mapping of SoftApManager start SoftAp return codes to counts */
     private final SparseIntArray mSoftApManagerReturnCodeCounts = new SparseIntArray();
+
+    private final SparseIntArray mTotalSsidsInScanHistogram = new SparseIntArray();
+    private final SparseIntArray mTotalBssidsInScanHistogram = new SparseIntArray();
+    private final SparseIntArray mAvailableOpenSsidsInScanHistogram = new SparseIntArray();
+    private final SparseIntArray mAvailableOpenBssidsInScanHistogram = new SparseIntArray();
+    private final SparseIntArray mAvailableSavedSsidsInScanHistogram = new SparseIntArray();
+    private final SparseIntArray mAvailableSavedBssidsInScanHistogram = new SparseIntArray();
+    private final SparseIntArray mAvailableOpenOrSavedSsidsInScanHistogram = new SparseIntArray();
+    private final SparseIntArray mAvailableOpenOrSavedBssidsInScanHistogram = new SparseIntArray();
+    private final SparseIntArray mAvailableSavedPasspointProviderProfilesInScanHistogram =
+            new SparseIntArray();
+    private final SparseIntArray mAvailableSavedPasspointProviderBssidsInScanHistogram =
+            new SparseIntArray();
+
     class RouterFingerPrint {
         private WifiMetricsProto.RouterFingerPrint mRouterFingerPrintProto;
         RouterFingerPrint() {
@@ -344,12 +375,13 @@
         }
     }
 
-    public WifiMetrics(Clock clock, Looper looper) {
+    public WifiMetrics(Clock clock, Looper looper, WifiAwareMetrics awareMetrics) {
         mClock = clock;
         mCurrentConnectionEvent = null;
         mScreenOn = true;
         mWifiState = WifiMetricsProto.WifiLog.WIFI_DISABLED;
         mRecordStartTimeSec = mClock.getElapsedSinceBootMillis() / 1000;
+        mWifiAwareMetrics = awareMetrics;
 
         mHandler = new Handler(looper) {
             public void handleMessage(Message msg) {
@@ -360,6 +392,21 @@
         };
     }
 
+    /** Sets internal WifiConfigManager member */
+    public void setWifiConfigManager(WifiConfigManager wifiConfigManager) {
+        mWifiConfigManager = wifiConfigManager;
+    }
+
+    /** Sets internal WifiNetworkSelector member */
+    public void setWifiNetworkSelector(WifiNetworkSelector wifiNetworkSelector) {
+        mWifiNetworkSelector = wifiNetworkSelector;
+    }
+
+    /** Sets internal PasspointManager member */
+    public void setPasspointManager(PasspointManager passpointManager) {
+        mPasspointManager = passpointManager;
+    }
+
     // Values used for indexing SystemStateEntries
     private static final int SCREEN_ON = 1;
     private static final int SCREEN_OFF = 0;
@@ -1022,6 +1069,126 @@
         }
     }
 
+    /**
+     * Increment number of times Passpoint provider being installed.
+     */
+    public void incrementNumPasspointProviderInstallation() {
+        synchronized (mLock) {
+            mWifiLogProto.numPasspointProviderInstallation++;
+        }
+    }
+
+    /**
+     * Increment number of times Passpoint provider is installed successfully.
+     */
+    public void incrementNumPasspointProviderInstallSuccess() {
+        synchronized (mLock) {
+            mWifiLogProto.numPasspointProviderInstallSuccess++;
+        }
+    }
+
+    /**
+     * Increment number of times Passpoint provider being uninstalled.
+     */
+    public void incrementNumPasspointProviderUninstallation() {
+        synchronized (mLock) {
+            mWifiLogProto.numPasspointProviderUninstallation++;
+        }
+    }
+
+    /**
+     * Increment number of times Passpoint provider is uninstalled successfully.
+     */
+    public void incrementNumPasspointProviderUninstallSuccess() {
+        synchronized (mLock) {
+            mWifiLogProto.numPasspointProviderUninstallSuccess++;
+        }
+    }
+
+    /**
+     * Increment N-Way network selection decision histograms:
+     * Counts the size of various sets of scanDetails within a scan, and increment the occurrence
+     * of that size for the associated histogram. There are ten histograms generated for each
+     * combination of: {SSID, BSSID} *{Total, Saved, Open, Saved_or_Open, Passpoint}
+     * Only performs this count if isFullBand is true, otherwise, increments the partial scan count
+     */
+    public void incrementAvailableNetworksHistograms(List<ScanDetail> scanDetails,
+            boolean isFullBand) {
+        synchronized (mLock) {
+            if (mWifiConfigManager == null || mWifiNetworkSelector == null
+                    || mPasspointManager == null) {
+                return;
+            }
+            if (!isFullBand) {
+                mWifiLogProto.partialAllSingleScanListenerResults++;
+                return;
+            }
+            Set<ScanResultMatchInfo> ssids = new HashSet<ScanResultMatchInfo>();
+            int bssids = 0;
+            Set<ScanResultMatchInfo> openSsids = new HashSet<ScanResultMatchInfo>();
+            int openBssids = 0;
+            Set<ScanResultMatchInfo> savedSsids = new HashSet<ScanResultMatchInfo>();
+            int savedBssids = 0;
+            // openOrSavedSsids calculated from union of savedSsids & openSsids
+            int openOrSavedBssids = 0;
+            Set<PasspointProvider> savedPasspointProviderProfiles =
+                    new HashSet<PasspointProvider>();
+            int savedPasspointProviderBssids = 0;
+            for (ScanDetail scanDetail : scanDetails) {
+                NetworkDetail networkDetail = scanDetail.getNetworkDetail();
+                ScanResult scanResult = scanDetail.getScanResult();
+                if (mWifiNetworkSelector.isSignalTooWeak(scanResult)) {
+                    continue;
+                }
+                ScanResultMatchInfo matchInfo = ScanResultMatchInfo.fromScanResult(scanResult);
+                Pair<PasspointProvider, PasspointMatch> providerMatch = null;
+                PasspointProvider passpointProvider = null;
+                if (networkDetail.isInterworking()) {
+                    providerMatch =
+                            mPasspointManager.matchProvider(scanResult);
+                    passpointProvider = providerMatch != null ? providerMatch.first : null;
+                }
+                ssids.add(matchInfo);
+                bssids++;
+                boolean isOpen = matchInfo.networkType == ScanResultMatchInfo.NETWORK_TYPE_OPEN;
+                WifiConfiguration config =
+                        mWifiConfigManager.getConfiguredNetworkForScanDetail(scanDetail);
+                boolean isSaved = (config != null) && !config.isEphemeral()
+                        && !config.isPasspoint();
+                boolean isSavedPasspoint = passpointProvider != null;
+                if (isOpen) {
+                    openSsids.add(matchInfo);
+                    openBssids++;
+                }
+                if (isSaved) {
+                    savedSsids.add(matchInfo);
+                    savedBssids++;
+                }
+                if (isOpen || isSaved) {
+                    openOrSavedBssids++;
+                    // Calculate openOrSavedSsids union later
+                }
+                if (isSavedPasspoint) {
+                    savedPasspointProviderProfiles.add(passpointProvider);
+                    savedPasspointProviderBssids++;
+                }
+            }
+            mWifiLogProto.fullBandAllSingleScanListenerResults++;
+            incrementTotalScanSsids(mTotalSsidsInScanHistogram, ssids.size());
+            incrementTotalScanResults(mTotalBssidsInScanHistogram, bssids);
+            incrementSsid(mAvailableOpenSsidsInScanHistogram, openSsids.size());
+            incrementBssid(mAvailableOpenBssidsInScanHistogram, openBssids);
+            incrementSsid(mAvailableSavedSsidsInScanHistogram, savedSsids.size());
+            incrementBssid(mAvailableSavedBssidsInScanHistogram, savedBssids);
+            openSsids.addAll(savedSsids); // openSsids = Union(openSsids, savedSsids)
+            incrementSsid(mAvailableOpenOrSavedSsidsInScanHistogram, openSsids.size());
+            incrementBssid(mAvailableOpenOrSavedBssidsInScanHistogram, openOrSavedBssids);
+            incrementSsid(mAvailableSavedPasspointProviderProfilesInScanHistogram,
+                    savedPasspointProviderProfiles.size());
+            incrementBssid(mAvailableSavedPasspointProviderBssidsInScanHistogram,
+                    savedPasspointProviderBssids);
+        }
+    }
 
     public static final String PROTO_DUMP_ARG = "wifiMetricsProto";
     public static final String CLEAN_DUMP_ARG = "clean";
@@ -1224,11 +1391,49 @@
                 for (StaEvent event : mStaEventList) {
                     pw.println(staEventToString(event));
                 }
+
+                pw.println("mWifiLogProto.numPasspointProviders="
+                        + mWifiLogProto.numPasspointProviders);
+                pw.println("mWifiLogProto.numPasspointProviderInstallation="
+                        + mWifiLogProto.numPasspointProviderInstallation);
+                pw.println("mWifiLogProto.numPasspointProviderInstallSuccess="
+                        + mWifiLogProto.numPasspointProviderInstallSuccess);
+                pw.println("mWifiLogProto.numPasspointProviderUninstallation="
+                        + mWifiLogProto.numPasspointProviderUninstallation);
+                pw.println("mWifiLogProto.numPasspointProviderUninstallSuccess="
+                        + mWifiLogProto.numPasspointProviderUninstallSuccess);
+                pw.println("mWifiLogProto.numPasspointProvidersSuccessfullyConnected="
+                        + mWifiLogProto.numPasspointProvidersSuccessfullyConnected);
+                pw.println("mTotalSsidsInScanHistogram:"
+                        + mTotalSsidsInScanHistogram.toString());
+                pw.println("mTotalBssidsInScanHistogram:"
+                        + mTotalBssidsInScanHistogram.toString());
+                pw.println("mAvailableOpenSsidsInScanHistogram:"
+                        + mAvailableOpenSsidsInScanHistogram.toString());
+                pw.println("mAvailableOpenBssidsInScanHistogram:"
+                        + mAvailableOpenBssidsInScanHistogram.toString());
+                pw.println("mAvailableSavedSsidsInScanHistogram:"
+                        + mAvailableSavedSsidsInScanHistogram.toString());
+                pw.println("mAvailableSavedBssidsInScanHistogram:"
+                        + mAvailableSavedBssidsInScanHistogram.toString());
+                pw.println("mAvailableOpenOrSavedSsidsInScanHistogram:"
+                        + mAvailableOpenOrSavedSsidsInScanHistogram.toString());
+                pw.println("mAvailableOpenOrSavedBssidsInScanHistogram:"
+                        + mAvailableOpenOrSavedBssidsInScanHistogram.toString());
+                pw.println("mAvailableSavedPasspointProviderProfilesInScanHistogram:"
+                        + mAvailableSavedPasspointProviderProfilesInScanHistogram.toString());
+                pw.println("mAvailableSavedPasspointProviderBssidsInScanHistogram:"
+                        + mAvailableSavedPasspointProviderBssidsInScanHistogram.toString());
+                pw.println("mWifiLogProto.partialAllSingleScanListenerResults="
+                        + mWifiLogProto.partialAllSingleScanListenerResults);
+                pw.println("mWifiLogProto.fullBandAllSingleScanListenerResults="
+                        + mWifiLogProto.fullBandAllSingleScanListenerResults);
+                pw.println("mWifiAwareMetrics:");
+                mWifiAwareMetrics.dump(fd, pw, args);
             }
         }
     }
 
-
     /**
      * Update various counts of saved network types
      * @param networks List of WifiConfigurations representing all saved networks, must not be null
@@ -1267,6 +1472,20 @@
     }
 
     /**
+     * Update metrics for saved Passpoint profiles.
+     *
+     * @param numSavedProfiles The number of saved Passpoint profiles
+     * @param numConnectedProfiles The number of saved Passpoint profiles that have ever resulted
+     *                             in a successful network connection
+     */
+    public void updateSavedPasspointProfiles(int numSavedProfiles, int numConnectedProfiles) {
+        synchronized (mLock) {
+            mWifiLogProto.numPasspointProviders = numSavedProfiles;
+            mWifiLogProto.numPasspointProvidersSuccessfullyConnected = numConnectedProfiles;
+        }
+    }
+
+    /**
      * append the separate ConnectionEvent, SystemStateEntry and ScanReturnCode collections to their
      * respective lists within mWifiLogProto
      *
@@ -1386,11 +1605,49 @@
                 mWifiLogProto.softApReturnCode[sapCode].count =
                         mSoftApManagerReturnCodeCounts.valueAt(sapCode);
             }
-
+            mWifiLogProto.totalSsidsInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(mTotalSsidsInScanHistogram);
+            mWifiLogProto.totalBssidsInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(mTotalBssidsInScanHistogram);
+            mWifiLogProto.availableOpenSsidsInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(mAvailableOpenSsidsInScanHistogram);
+            mWifiLogProto.availableOpenBssidsInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(mAvailableOpenBssidsInScanHistogram);
+            mWifiLogProto.availableSavedSsidsInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(mAvailableSavedSsidsInScanHistogram);
+            mWifiLogProto.availableSavedBssidsInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(mAvailableSavedBssidsInScanHistogram);
+            mWifiLogProto.availableOpenOrSavedSsidsInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(
+                    mAvailableOpenOrSavedSsidsInScanHistogram);
+            mWifiLogProto.availableOpenOrSavedBssidsInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(
+                    mAvailableOpenOrSavedBssidsInScanHistogram);
+            mWifiLogProto.availableSavedPasspointProviderProfilesInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(
+                    mAvailableSavedPasspointProviderProfilesInScanHistogram);
+            mWifiLogProto.availableSavedPasspointProviderBssidsInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(
+                    mAvailableSavedPasspointProviderBssidsInScanHistogram);
             mWifiLogProto.staEventList = mStaEventList.toArray(mWifiLogProto.staEventList);
+            mWifiLogProto.wifiAwareLog = mWifiAwareMetrics.consolidateProto();
         }
     }
 
+    private WifiMetricsProto.NumConnectableNetworksBucket[] makeNumConnectableNetworksBucketArray(
+            SparseIntArray sia) {
+        WifiMetricsProto.NumConnectableNetworksBucket[] array =
+                new WifiMetricsProto.NumConnectableNetworksBucket[sia.size()];
+        for (int i = 0; i < sia.size(); i++) {
+            WifiMetricsProto.NumConnectableNetworksBucket keyVal =
+                    new WifiMetricsProto.NumConnectableNetworksBucket();
+            keyVal.numConnectableNetworks = sia.keyAt(i);
+            keyVal.count = sia.valueAt(i);
+            array[i] = keyVal;
+        }
+        return array;
+    }
+
     /**
      * Clear all WifiMetrics, except for currentConnectionEvent.
      */
@@ -1411,6 +1668,17 @@
             mScanResultRssiTimestampMillis = -1;
             mSoftApManagerReturnCodeCounts.clear();
             mStaEventList.clear();
+            mWifiAwareMetrics.clear();
+            mTotalSsidsInScanHistogram.clear();
+            mTotalBssidsInScanHistogram.clear();
+            mAvailableOpenSsidsInScanHistogram.clear();
+            mAvailableOpenBssidsInScanHistogram.clear();
+            mAvailableSavedSsidsInScanHistogram.clear();
+            mAvailableSavedBssidsInScanHistogram.clear();
+            mAvailableOpenOrSavedSsidsInScanHistogram.clear();
+            mAvailableOpenOrSavedBssidsInScanHistogram.clear();
+            mAvailableSavedPasspointProviderProfilesInScanHistogram.clear();
+            mAvailableSavedPasspointProviderBssidsInScanHistogram.clear();
         }
     }
 
@@ -1587,6 +1855,10 @@
         return mHandler;
     }
 
+    public WifiAwareMetrics getWifiAwareMetrics() {
+        return mWifiAwareMetrics;
+    }
+
     // Rather than generate a StaEvent for each SUPPLICANT_STATE_CHANGE, cache these in a bitmask
     // and attach it to the next event which is generated.
     private int mSupplicantStateChangeBitmask = 0;
@@ -1797,4 +2069,20 @@
         }
         return value;
     }
+    private void incrementSsid(SparseIntArray sia, int element) {
+        increment(sia, Math.min(element, MAX_CONNECTABLE_SSID_NETWORK_BUCKET));
+    }
+    private void incrementBssid(SparseIntArray sia, int element) {
+        increment(sia, Math.min(element, MAX_CONNECTABLE_BSSID_NETWORK_BUCKET));
+    }
+    private void incrementTotalScanResults(SparseIntArray sia, int element) {
+        increment(sia, Math.min(element, MAX_TOTAL_SCAN_RESULTS_BUCKET));
+    }
+    private void incrementTotalScanSsids(SparseIntArray sia, int element) {
+        increment(sia, Math.min(element, MAX_TOTAL_SCAN_RESULT_SSIDS_BUCKET));
+    }
+    private void increment(SparseIntArray sia, int element) {
+        int count = sia.get(element);
+        sia.put(element, count + 1);
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiNative.java b/service/java/com/android/server/wifi/WifiNative.java
index 90f6ac1..973b659 100644
--- a/service/java/com/android/server/wifi/WifiNative.java
+++ b/service/java/com/android/server/wifi/WifiNative.java
@@ -29,6 +29,7 @@
 import android.net.wifi.WifiWakeReasonAndCounts;
 import android.os.SystemClock;
 import android.util.Log;
+import android.util.Pair;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.Immutable;
@@ -90,6 +91,9 @@
    /********************************************************
     * Native Initialization/Deinitialization
     ********************************************************/
+    public static final int SETUP_SUCCESS = 0;
+    public static final int SETUP_FAILURE_HAL = 1;
+    public static final int SETUP_FAILURE_WIFICOND = 2;
 
    /**
     * Setup wifi native for Client mode operations.
@@ -98,15 +102,19 @@
     * 2. Setup Wificond to operate in client mode and retrieve the handle to use for client
     * operations.
     *
-    * @return An IClientInterface as wificond client interface binder handler.
-    * Returns null on failure.
+    * @return Pair of <Integer, IClientInterface> to indicate the status and the associated wificond
+    * client interface binder handler (will be null on failure).
     */
-    public IClientInterface setupForClientMode() {
+    public Pair<Integer, IClientInterface> setupForClientMode() {
         if (!startHalIfNecessary(true)) {
             Log.e(mTAG, "Failed to start HAL for client mode");
-            return null;
+            return Pair.create(SETUP_FAILURE_HAL, null);
         }
-        return mWificondControl.setupDriverForClientMode();
+        IClientInterface iClientInterface = mWificondControl.setupDriverForClientMode();
+        if (iClientInterface == null) {
+            return Pair.create(SETUP_FAILURE_WIFICOND, null);
+        }
+        return Pair.create(SETUP_SUCCESS, iClientInterface);
     }
 
     /**
@@ -115,33 +123,33 @@
      * 1. Starts the Wifi HAL and configures it in AP mode.
      * 2. Setup Wificond to operate in AP mode and retrieve the handle to use for ap operations.
      *
-     * @return An IApInterface as wificond Ap interface binder handler.
-     * Returns null on failure.
+     * @return Pair of <Integer, IApInterface> to indicate the status and the associated wificond
+     * AP interface binder handler (will be null on failure).
      */
-    public IApInterface setupForSoftApMode() {
+    public Pair<Integer, IApInterface> setupForSoftApMode() {
         if (!startHalIfNecessary(false)) {
             Log.e(mTAG, "Failed to start HAL for AP mode");
-            return null;
+            return Pair.create(SETUP_FAILURE_HAL, null);
         }
-        return mWificondControl.setupDriverForSoftApMode();
+        IApInterface iApInterface = mWificondControl.setupDriverForSoftApMode();
+        if (iApInterface == null) {
+            return Pair.create(SETUP_FAILURE_WIFICOND, null);
+        }
+        return Pair.create(SETUP_SUCCESS, iApInterface);
     }
 
     /**
      * Teardown all mode configurations in wifi native.
      *
-     * 1. Tears down all the interfaces from Wificond.
-     * 2. Stops the Wifi HAL.
-     *
-     * @return Returns true on success.
+     * 1. Stops the Wifi HAL.
+     * 2. Tears down all the interfaces from Wificond.
      */
-    public boolean tearDown() {
+    public void tearDown() {
+        stopHalIfNecessary();
         if (!mWificondControl.tearDownInterfaces()) {
             // TODO(b/34859006): Handle failures.
             Log.e(mTAG, "Failed to teardown interfaces from Wificond");
-            return false;
         }
-        stopHalIfNecessary();
-        return true;
     }
 
     /********************************************************
@@ -1675,6 +1683,24 @@
         return mWifiVendorHal.configureRoaming(new RoamingConfig());
     }
 
+    /**
+     * Tx power level scenarios that can be selected.
+     */
+    public static final int TX_POWER_SCENARIO_NORMAL = 0;
+    public static final int TX_POWER_SCENARIO_VOICE_CALL = 1;
+
+    /**
+     * Select one of the pre-configured TX power level scenarios or reset it back to normal.
+     * Primarily used for meeting SAR requirements during voice calls.
+     *
+     * @param scenario Should be one {@link #TX_POWER_SCENARIO_NORMAL} or
+     *        {@link #TX_POWER_SCENARIO_VOICE_CALL}.
+     * @return true for success; false for failure or if the HAL version does not support this API.
+     */
+    public boolean selectTxPowerScenario(int scenario) {
+        return mWifiVendorHal.selectTxPowerScenario(scenario);
+    }
+
     /********************************************************
      * JNI operations
      ********************************************************/
diff --git a/service/java/com/android/server/wifi/WifiNetworkSelector.java b/service/java/com/android/server/wifi/WifiNetworkSelector.java
index d24b5cc..89068a8 100644
--- a/service/java/com/android/server/wifi/WifiNetworkSelector.java
+++ b/service/java/com/android/server/wifi/WifiNetworkSelector.java
@@ -25,11 +25,11 @@
 import android.net.wifi.WifiInfo;
 import android.text.TextUtils;
 import android.util.LocalLog;
-import android.util.Log;
 import android.util.Pair;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wifi.util.ScanResultUtil;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -56,10 +56,13 @@
     // WifiConfiguration (if any).
     private volatile List<Pair<ScanDetail, WifiConfiguration>> mConnectableNetworks =
             new ArrayList<>();
+    private List<ScanDetail> mFilteredNetworks = new ArrayList<>();
     private final int mThresholdQualifiedRssi24;
     private final int mThresholdQualifiedRssi5;
     private final int mThresholdMinimumRssi24;
     private final int mThresholdMinimumRssi5;
+    private final int mStayOnNetworkMinimumTxRate;
+    private final int mStayOnNetworkMinimumRxRate;
     private final boolean mEnableAutoJoinWhenAssociated;
 
     /**
@@ -145,6 +148,18 @@
                     + " , ID: " + network.networkId);
         }
 
+        int currentRssi = wifiInfo.getRssi();
+        boolean hasQualifiedRssi =
+                (wifiInfo.is24GHz() && (currentRssi > mThresholdQualifiedRssi24))
+                        || (wifiInfo.is5GHz() && (currentRssi > mThresholdQualifiedRssi5));
+        // getTxSuccessRate() and getRxSuccessRate() returns the packet rate in per 5 seconds unit.
+        boolean hasActiveStream = (wifiInfo.getTxSuccessRatePps() > mStayOnNetworkMinimumTxRate)
+                || (wifiInfo.getRxSuccessRatePps() > mStayOnNetworkMinimumRxRate);
+        if (hasQualifiedRssi && hasActiveStream) {
+            localLog("Stay on current network because of good RSSI and ongoing traffic");
+            return true;
+        }
+
         // Ephemeral network is not qualified.
         if (network.ephemeral) {
             localLog("Current network is an ephemeral one.");
@@ -157,28 +172,15 @@
             return false;
         }
 
-        int currentRssi = wifiInfo.getRssi();
         if (wifiInfo.is24GHz()) {
             // 2.4GHz networks is not qualified whenever 5GHz is available
             if (is5GHzNetworkAvailable(scanDetails)) {
                 localLog("Current network is 2.4GHz. 5GHz networks available.");
                 return false;
             }
-            // When 5GHz is not available, we go through normal 2.4GHz qualification
-            if (currentRssi < mThresholdQualifiedRssi24) {
-                localLog("Current network band=2.4GHz, RSSI["
-                        + currentRssi + "]-acceptable but not qualified.");
-                return false;
-            }
-        } else if (wifiInfo.is5GHz()) {
-            // Must be 5GHz, so we always apply qualification checks
-            if (currentRssi < mThresholdQualifiedRssi5) {
-                localLog("Current network band=5GHz, RSSI["
-                        + currentRssi + "]-acceptable but not qualified.");
-                return false;
-            }
-        } else {
-            Log.e(TAG, "We're on a wifi network that's neither 2.4 or 5GHz... aliens!");
+        }
+        if (!hasQualifiedRssi) {
+            localLog("Current network RSSI[" + currentRssi + "]-acceptable but not qualified.");
             return false;
         }
 
@@ -257,6 +259,14 @@
         return (network.SSID + ":" + network.networkId);
     }
 
+    /**
+     * Compares ScanResult level against the minimum threshold for its band, returns true if lower
+     */
+    public boolean isSignalTooWeak(ScanResult scanResult) {
+        return ((scanResult.is24GHz() && scanResult.level < mThresholdMinimumRssi24)
+                || (scanResult.is5GHz() && scanResult.level < mThresholdMinimumRssi5));
+    }
+
     private List<ScanDetail> filterScanResults(List<ScanDetail> scanDetails,
                 HashSet<String> bssidBlacklist, boolean isConnected, String currentBssid) {
         ArrayList<NetworkKey> unscoredNetworks = new ArrayList<NetworkKey>();
@@ -287,10 +297,7 @@
             }
 
             // Skip network with too weak signals.
-            if ((scanResult.is24GHz() && scanResult.level
-                    < mThresholdMinimumRssi24)
-                    || (scanResult.is5GHz() && scanResult.level
-                    < mThresholdMinimumRssi5)) {
+            if (isSignalTooWeak(scanResult)) {
                 lowRssi.append(scanId).append("(")
                     .append(scanResult.is24GHz() ? "2.4GHz" : "5GHz")
                     .append(")").append(scanResult.level).append(" / ");
@@ -327,12 +334,39 @@
     }
 
     /**
+     * This returns a list of ScanDetails that were filtered in the process of network selection.
+     * The list is further filtered for only open unsaved networks.
+     *
+     * @return the list of ScanDetails for open unsaved networks that do not have invalid SSIDS,
+     * blacklisted BSSIDS, or low signal strength. This will return an empty list when there are
+     * no open unsaved networks, or when network selection has not been run.
+     */
+    public List<ScanDetail> getFilteredScanDetailsForOpenUnsavedNetworks() {
+        List<ScanDetail> openUnsavedNetworks = new ArrayList<>();
+        for (ScanDetail scanDetail : mFilteredNetworks) {
+            ScanResult scanResult = scanDetail.getScanResult();
+
+            if (!ScanResultUtil.isScanResultForOpenNetwork(scanResult)) {
+                continue;
+            }
+
+            // Skip saved networks
+            if (mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(scanDetail) != null) {
+                continue;
+            }
+
+            openUnsavedNetworks.add(scanDetail);
+        }
+        return openUnsavedNetworks;
+    }
+
+    /**
      * @return the list of ScanDetails scored as potential candidates by the last run of
      * selectNetwork, this will be empty if Network selector determined no selection was
      * needed on last run. This includes scan details of sufficient signal strength, and
      * had an associated WifiConfiguration.
      */
-    public List<Pair<ScanDetail, WifiConfiguration>> getFilteredScanDetails() {
+    public List<Pair<ScanDetail, WifiConfiguration>> getConnectableScanDetails() {
         return mConnectableNetworks;
     }
 
@@ -450,6 +484,7 @@
     public WifiConfiguration selectNetwork(List<ScanDetail> scanDetails,
             HashSet<String> bssidBlacklist, WifiInfo wifiInfo,
             boolean connected, boolean disconnected, boolean untrustedNetworkAllowed) {
+        mFilteredNetworks.clear();
         mConnectableNetworks.clear();
         if (scanDetails.size() == 0) {
             localLog("Empty connectivity scan result");
@@ -476,9 +511,9 @@
         }
 
         // Filter out unwanted networks.
-        List<ScanDetail> filteredScanDetails = filterScanResults(scanDetails, bssidBlacklist,
+        mFilteredNetworks = filterScanResults(scanDetails, bssidBlacklist,
                 connected, currentBssid);
-        if (filteredScanDetails.size() == 0) {
+        if (mFilteredNetworks.size() == 0) {
             return null;
         }
 
@@ -488,9 +523,9 @@
         for (NetworkEvaluator registeredEvaluator : mEvaluators) {
             if (registeredEvaluator != null) {
                 localLog("About to run " + registeredEvaluator.getName() + " :");
-                selectedNetwork = registeredEvaluator.evaluateNetworks(filteredScanDetails,
-                            currentNetwork, currentBssid, connected,
-                            untrustedNetworkAllowed, mConnectableNetworks);
+                selectedNetwork = registeredEvaluator.evaluateNetworks(
+                        new ArrayList<>(mFilteredNetworks), currentNetwork, currentBssid, connected,
+                        untrustedNetworkAllowed, mConnectableNetworks);
                 if (selectedNetwork != null) {
                     localLog(registeredEvaluator.getName() + " selects "
                             + WifiNetworkSelector.toNetworkString(selectedNetwork) + " : "
@@ -549,5 +584,9 @@
                             R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz);
         mEnableAutoJoinWhenAssociated = context.getResources().getBoolean(
                             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(
+                R.integer.config_wifi_framework_min_rx_rate_for_staying_on_network);
     }
 }
diff --git a/service/java/com/android/server/wifi/WifiNotificationController.java b/service/java/com/android/server/wifi/WifiNotificationController.java
index c8e5e90..797fd43 100644
--- a/service/java/com/android/server/wifi/WifiNotificationController.java
+++ b/service/java/com/android/server/wifi/WifiNotificationController.java
@@ -16,18 +16,14 @@
 
 package com.android.server.wifi;
 
+import android.annotation.NonNull;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.TaskStackBuilder;
-import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.database.ContentObserver;
-import android.net.NetworkInfo;
-import android.net.wifi.ScanResult;
 import android.net.wifi.WifiManager;
-import android.net.wifi.WifiScanner;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -57,12 +53,11 @@
      */
     private final long NOTIFICATION_REPEAT_DELAY_MS;
 
+    /** Whether the user has set the setting to show the 'available networks' notification. */
+    private boolean mSettingEnabled;
+
     /**
-     * Whether the user has set the setting to show the 'available networks' notification.
-     */
-    private boolean mNotificationEnabled;
-    /**
-     * Observes the user setting to keep {@link #mNotificationEnabled} in sync.
+     * Observes the user setting to keep {@link #mSettingEnabled} in sync.
      */
     private NotificationEnabledSettingObserver mNotificationEnabledSettingObserver;
 
@@ -82,93 +77,21 @@
      * notification is not showing.
      */
     private boolean mNotificationShown;
-    /**
-     * The number of continuous scans that must occur before consider the
-     * supplicant in a scanning state. This allows supplicant to associate with
-     * remembered networks that are in the scan results.
-     */
-    private static final int NUM_SCANS_BEFORE_ACTUALLY_SCANNING = 3;
-    /**
-     * The number of scans since the last network state change. When this
-     * exceeds {@link #NUM_SCANS_BEFORE_ACTUALLY_SCANNING}, we consider the
-     * supplicant to actually be scanning. When the network state changes to
-     * something other than scanning, we reset this to 0.
-     */
-    private int mNumScansSinceNetworkStateChange;
+    /** Whether the screen is on or not. */
+    private boolean mScreenOn;
 
     private final Context mContext;
-    private NetworkInfo mNetworkInfo;
-    private NetworkInfo.DetailedState mDetailedState;
-    private volatile int mWifiState;
     private FrameworkFacade mFrameworkFacade;
-    private WifiInjector mWifiInjector;
-    private WifiScanner mWifiScanner;
 
     WifiNotificationController(Context context,
                                Looper looper,
                                FrameworkFacade framework,
-                               Notification.Builder builder,
-                               WifiInjector wifiInjector) {
+                               Notification.Builder builder) {
         mContext = context;
         mFrameworkFacade = framework;
         mNotificationBuilder = builder;
-        mWifiInjector = wifiInjector;
-        mWifiState = WifiManager.WIFI_STATE_UNKNOWN;
-        mDetailedState = NetworkInfo.DetailedState.IDLE;
 
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
-        filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
-        filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
-
-        mContext.registerReceiver(
-                new BroadcastReceiver() {
-                    @Override
-                    public void onReceive(Context context, Intent intent) {
-                        if (intent.getAction()
-                                .equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
-                            mWifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
-                                    WifiManager.WIFI_STATE_UNKNOWN);
-                            resetNotification();
-                        } else if (intent.getAction().equals(
-                                WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
-                            mNetworkInfo = (NetworkInfo) intent.getParcelableExtra(
-                                    WifiManager.EXTRA_NETWORK_INFO);
-                            NetworkInfo.DetailedState detailedState =
-                                    mNetworkInfo.getDetailedState();
-                            if (detailedState != NetworkInfo.DetailedState.SCANNING
-                                    && detailedState != mDetailedState) {
-                                mDetailedState = detailedState;
-                                // reset & clear notification on a network connect & disconnect
-                                switch(mDetailedState) {
-                                    case CONNECTED:
-                                    case DISCONNECTED:
-                                    case CAPTIVE_PORTAL_CHECK:
-                                        resetNotification();
-                                        break;
-
-                                    case IDLE:
-                                    case SCANNING:
-                                    case CONNECTING:
-                                    case AUTHENTICATING:
-                                    case OBTAINING_IPADDR:
-                                    case SUSPENDED:
-                                    case FAILED:
-                                    case BLOCKED:
-                                    case VERIFYING_POOR_LINK:
-                                        break;
-                                }
-                            }
-                        } else if (intent.getAction().equals(
-                                WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
-                            if (mWifiScanner == null) {
-                                mWifiScanner = mWifiInjector.getWifiScanner();
-                            }
-                            checkAndSetNotification(mNetworkInfo,
-                                    mWifiScanner.getSingleScanResults());
-                        }
-                    }
-                }, filter);
+        mScreenOn = false;
 
         // Setting is in seconds
         NOTIFICATION_REPEAT_DELAY_MS = mFrameworkFacade.getIntegerSetting(context,
@@ -178,66 +101,53 @@
         mNotificationEnabledSettingObserver.register();
     }
 
-    private synchronized void checkAndSetNotification(NetworkInfo networkInfo,
-            List<ScanResult> scanResults) {
-
-        // TODO: unregister broadcast so we do not have to check here
-        // If we shouldn't place a notification on available networks, then
-        // don't bother doing any of the following
-        if (!mNotificationEnabled) return;
-        if (mWifiState != WifiManager.WIFI_STATE_ENABLED) return;
-        if (UserManager.get(mContext)
-                .hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, UserHandle.CURRENT)) {
-            return;
+    /**
+     * 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;
         }
-
-        NetworkInfo.State state = NetworkInfo.State.DISCONNECTED;
-        if (networkInfo != null)
-            state = networkInfo.getState();
-
-        if ((state == NetworkInfo.State.DISCONNECTED)
-                || (state == NetworkInfo.State.UNKNOWN)) {
-            if (scanResults != null) {
-                int numOpenNetworks = 0;
-                for (int i = scanResults.size() - 1; i >= 0; i--) {
-                    ScanResult scanResult = scanResults.get(i);
-
-                    //A capability of [ESS] represents an open access point
-                    //that is available for an STA to connect
-                    if (scanResult.capabilities != null &&
-                            scanResult.capabilities.equals("[ESS]")) {
-                        numOpenNetworks++;
-                    }
-                }
-
-                if (numOpenNetworks > 0) {
-                    if (++mNumScansSinceNetworkStateChange >= NUM_SCANS_BEFORE_ACTUALLY_SCANNING) {
-                        /*
-                         * We've scanned continuously at least
-                         * NUM_SCANS_BEFORE_NOTIFICATION times. The user
-                         * probably does not have a remembered network in range,
-                         * since otherwise supplicant would have tried to
-                         * associate and thus resetting this counter.
-                         */
-                        setNotificationVisible(true, numOpenNetworks, false, 0);
-                    }
-                    return;
-                }
-            }
-        }
-
-        // No open networks in range, remove the notification
         setNotificationVisible(false, 0, false, 0);
     }
 
+    private boolean isControllerEnabled() {
+        return mSettingEnabled && !UserManager.get(mContext)
+                .hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, UserHandle.CURRENT);
+    }
+
     /**
-     * Clears variables related to tracking whether a notification has been
-     * shown recently and clears the current notification.
+     * If there are open networks, attempt to post an open network notification.
+     *
+     * @param availableNetworks Available networks from
+     * {@link WifiNetworkSelector.NetworkEvaluator#getFilteredScanDetailsForOpenUnsavedNetworks()}.
      */
-    private synchronized void resetNotification() {
-        mNotificationRepeatTime = 0;
-        mNumScansSinceNetworkStateChange = 0;
-        setNotificationVisible(false, 0, false, 0);
+    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;
     }
 
     /**
@@ -308,34 +218,30 @@
         mNotificationShown = visible;
     }
 
-    void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("mNotificationEnabled " + mNotificationEnabled);
+    /** 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);
-        pw.println("mNumScansSinceNetworkStateChange " + mNumScansSinceNetworkStateChange);
     }
 
     private class NotificationEnabledSettingObserver extends ContentObserver {
-        public NotificationEnabledSettingObserver(Handler handler) {
+        NotificationEnabledSettingObserver(Handler handler) {
             super(handler);
         }
 
         public void register() {
             mFrameworkFacade.registerContentObserver(mContext, Settings.Global.getUriFor(
                     Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON), true, this);
-            synchronized (WifiNotificationController.this) {
-                mNotificationEnabled = getValue();
-            }
+            mSettingEnabled = getValue();
         }
 
         @Override
         public void onChange(boolean selfChange) {
             super.onChange(selfChange);
-
-            synchronized (WifiNotificationController.this) {
-                mNotificationEnabled = getValue();
-                resetNotification();
-            }
+            mSettingEnabled = getValue();
+            clearPendingNotification(true /* resetRepeatDelay */);
         }
 
         private boolean getValue() {
diff --git a/service/java/com/android/server/wifi/WifiScoreReport.java b/service/java/com/android/server/wifi/WifiScoreReport.java
index 8bc8bf3..894d57c 100644
--- a/service/java/com/android/server/wifi/WifiScoreReport.java
+++ b/service/java/com/android/server/wifi/WifiScoreReport.java
@@ -18,11 +18,15 @@
 
 import android.content.Context;
 import android.net.NetworkAgent;
-import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiInfo;
 import android.util.Log;
 
-import com.android.internal.R;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.Locale;
 
 /**
  * Class used to calculate scores for connected wifi networks and report it to the associated
@@ -31,70 +35,25 @@
 public class WifiScoreReport {
     private static final String TAG = "WifiScoreReport";
 
-    private static final int STARTING_SCORE = 56;
+    private static final int DUMPSYS_ENTRY_COUNT_LIMIT = 14400; // 12 hours on 3 second poll
 
-    private static final int SCAN_CACHE_VISIBILITY_MS = 12000;
-    private static final int HOME_VISIBLE_NETWORK_MAX_COUNT = 6;
-    private static final int SCAN_CACHE_COUNT_PENALTY = 2;
-    private static final int AGGRESSIVE_HANDOVER_PENALTY = 6;
-    private static final int MAX_SUCCESS_RATE_OF_STUCK_LINK = 3; // proportional to packets per sec
-    private static final int MAX_STUCK_LINK_COUNT = 5;
-    private static final int MAX_BAD_RSSI_COUNT = 7;
-    private static final int BAD_RSSI_COUNT_PENALTY = 2;
-    private static final int MAX_LOW_RSSI_COUNT = 1;
-    private static final double MIN_TX_FAILURE_RATE_FOR_WORKING_LINK = 0.3;
-    private static final int MIN_SUSTAINED_LINK_STUCK_COUNT = 1;
-    private static final int LINK_STUCK_PENALTY = 2;
-    private static final int BAD_LINKSPEED_PENALTY = 4;
-    private static final int GOOD_LINKSPEED_BONUS = 4;
-
-    // Device configs. The values are examples.
-    private final int mThresholdMinimumRssi5;      // -82
-    private final int mThresholdQualifiedRssi5;    // -70
-    private final int mThresholdSaturatedRssi5;    // -57
-    private final int mThresholdMinimumRssi24;     // -85
-    private final int mThresholdQualifiedRssi24;   // -73
-    private final int mThresholdSaturatedRssi24;   // -60
-    private final int mBadLinkSpeed24;             //  6 Mbps
-    private final int mBadLinkSpeed5;              // 12 Mbps
-    private final int mGoodLinkSpeed24;            // 24 Mbps
-    private final int mGoodLinkSpeed5;             // 36 Mbps
-
-    private final WifiConfigManager mWifiConfigManager;
     private boolean mVerboseLoggingEnabled = false;
+    private static final long FIRST_REASONABLE_WALL_CLOCK = 1490000000000L; // mid-December 2016
 
     // Cache of the last score report.
     private String mReport;
     private boolean mReportValid = false;
 
-    // State set by updateScoringState
-    private boolean mMultiBandScanResults;
-    private boolean mIsHomeNetwork;
+    private final Clock mClock;
+    private int mSessionNumber = 0;
 
-    WifiScoreReport(Context context, WifiConfigManager wifiConfigManager) {
-        // Fetch all the device configs.
-        mThresholdMinimumRssi5 = context.getResources().getInteger(
-                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz);
-        mThresholdQualifiedRssi5 = context.getResources().getInteger(
-                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_5GHz);
-        mThresholdSaturatedRssi5 = context.getResources().getInteger(
-                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_5GHz);
-        mThresholdMinimumRssi24 = context.getResources().getInteger(
-                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz);
-        mThresholdQualifiedRssi24 = context.getResources().getInteger(
-                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_24GHz);
-        mThresholdSaturatedRssi24 = context.getResources().getInteger(
-                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz);
-        mBadLinkSpeed24 = context.getResources().getInteger(
-                R.integer.config_wifi_framework_wifi_score_bad_link_speed_24);
-        mBadLinkSpeed5 = context.getResources().getInteger(
-                R.integer.config_wifi_framework_wifi_score_bad_link_speed_5);
-        mGoodLinkSpeed24 = context.getResources().getInteger(
-                R.integer.config_wifi_framework_wifi_score_good_link_speed_24);
-        mGoodLinkSpeed5 = context.getResources().getInteger(
-                R.integer.config_wifi_framework_wifi_score_good_link_speed_5);
+    ConnectedScore mConnectedScore;
+    ConnectedScore mAggressiveConnectedScore;
 
-        mWifiConfigManager = wifiConfigManager;
+    WifiScoreReport(Context context, WifiConfigManager wifiConfigManager, Clock clock) {
+        mClock = clock;
+        mConnectedScore = new LegacyConnectedScore(context, wifiConfigManager, clock);
+        mAggressiveConnectedScore = new AggressiveConnectedScore(context, clock);
     }
 
     /**
@@ -111,7 +70,13 @@
      */
     public void reset() {
         mReport = "";
-        mReportValid = false;
+        if (mReportValid) {
+            mSessionNumber++;
+            mReportValid = false;
+        }
+        mConnectedScore.reset();
+        mAggressiveConnectedScore.reset();
+        if (mVerboseLoggingEnabled) Log.d(TAG, "reset");
     }
 
     /**
@@ -148,8 +113,19 @@
                                         int aggressiveHandover, WifiMetrics wifiMetrics) {
         int score;
 
-        updateScoringState(wifiInfo, aggressiveHandover);
-        score = calculateScore(wifiInfo, aggressiveHandover);
+        long millis = mConnectedScore.getMillis();
+
+        mConnectedScore.updateUsingWifiInfo(wifiInfo, millis);
+        mAggressiveConnectedScore.updateUsingWifiInfo(wifiInfo, millis);
+
+        int s0 = mConnectedScore.generateScore();
+        int s1 = mAggressiveConnectedScore.generateScore();
+
+        if (aggressiveHandover == 0) {
+            score = s0;
+        } else {
+            score = s1;
+        }
 
         //sanitize boundaries
         if (score > NetworkAgent.WIFI_BASE_SCORE) {
@@ -159,6 +135,8 @@
             score = 0;
         }
 
+        logLinkMetrics(wifiInfo, s0, s1);
+
         //report score
         if (score != wifiInfo.score) {
             if (mVerboseLoggingEnabled) {
@@ -170,154 +148,62 @@
             }
         }
 
-        mReport = String.format(" score=%d", score);
+        mReport = String.format(Locale.US, " score=%d", score);
         mReportValid = true;
         wifiMetrics.incrementWifiScoreCount(score);
     }
 
     /**
-     * Updates the state.
+     * Data for dumpsys
+     *
+     * These are stored as csv formatted lines
      */
-    private void updateScoringState(WifiInfo wifiInfo, int aggressiveHandover) {
-        mMultiBandScanResults = multiBandScanResults(wifiInfo);
-        mIsHomeNetwork = isHomeNetwork(wifiInfo);
-
-        int rssiThreshBad = mThresholdMinimumRssi24;
-        int rssiThreshLow = mThresholdQualifiedRssi24;
-
-        if (wifiInfo.is5GHz() && !mMultiBandScanResults) {
-            rssiThreshBad = mThresholdMinimumRssi5;
-            rssiThreshLow = mThresholdQualifiedRssi5;
-        }
-
-        int rssi =  wifiInfo.getRssi();
-        if (aggressiveHandover != 0) {
-            rssi -= AGGRESSIVE_HANDOVER_PENALTY * aggressiveHandover;
-        }
-        if (mIsHomeNetwork) {
-            rssi += WifiConfiguration.HOME_NETWORK_RSSI_BOOST;
-        }
-
-        if ((wifiInfo.txBadRate >= 1)
-                && (wifiInfo.txSuccessRate < MAX_SUCCESS_RATE_OF_STUCK_LINK)
-                && rssi < rssiThreshLow) {
-            // Link is stuck
-            if (wifiInfo.linkStuckCount < MAX_STUCK_LINK_COUNT) {
-                wifiInfo.linkStuckCount += 1;
-            }
-        } else if (wifiInfo.txBadRate < MIN_TX_FAILURE_RATE_FOR_WORKING_LINK) {
-            if (wifiInfo.linkStuckCount > 0) {
-                wifiInfo.linkStuckCount -= 1;
-            }
-        }
-
-        if (rssi < rssiThreshBad) {
-            if (wifiInfo.badRssiCount < MAX_BAD_RSSI_COUNT) {
-                wifiInfo.badRssiCount += 1;
-            }
-        } else if (rssi < rssiThreshLow) {
-            wifiInfo.lowRssiCount = MAX_LOW_RSSI_COUNT; // Dont increment the lowRssi count above 1
-            if (wifiInfo.badRssiCount > 0) {
-                // Decrement bad Rssi count
-                wifiInfo.badRssiCount -= 1;
-            }
-        } else {
-            wifiInfo.badRssiCount = 0;
-            wifiInfo.lowRssiCount = 0;
-        }
-
-    }
+    private LinkedList<String> mLinkMetricsHistory = new LinkedList<String>();
 
     /**
-     * Calculates the score, without all the cruft.
+     * Data logging for dumpsys
      */
-    private int calculateScore(WifiInfo wifiInfo, int aggressiveHandover) {
-        int score = STARTING_SCORE;
-
-        int rssiThreshSaturated = mThresholdSaturatedRssi24;
-        int linkspeedThreshBad = mBadLinkSpeed24;
-        int linkspeedThreshGood = mGoodLinkSpeed24;
-
-        if (wifiInfo.is5GHz()) {
-            if (!mMultiBandScanResults) {
-                rssiThreshSaturated = mThresholdSaturatedRssi5;
-            }
-            linkspeedThreshBad = mBadLinkSpeed5;
-            linkspeedThreshGood = mGoodLinkSpeed5;
-        }
-
-        int rssi =  wifiInfo.getRssi();
-        if (aggressiveHandover != 0) {
-            rssi -= AGGRESSIVE_HANDOVER_PENALTY * aggressiveHandover;
-        }
-        if (mIsHomeNetwork) {
-            rssi += WifiConfiguration.HOME_NETWORK_RSSI_BOOST;
-        }
-
+    private void logLinkMetrics(WifiInfo wifiInfo, int s0, int s1) {
+        long now = mClock.getWallClockMillis();
+        if (now < FIRST_REASONABLE_WALL_CLOCK) return;
+        double rssi = wifiInfo.getRssi();
+        int freq = wifiInfo.getFrequency();
         int linkSpeed = wifiInfo.getLinkSpeed();
-
-        if (wifiInfo.linkStuckCount > MIN_SUSTAINED_LINK_STUCK_COUNT) {
-            // Once link gets stuck for more than 3 seconds, start reducing the score
-            score = score - LINK_STUCK_PENALTY * (wifiInfo.linkStuckCount - 1);
+        double txSuccessRate = wifiInfo.txSuccessRate;
+        double txRetriesRate = wifiInfo.txRetriesRate;
+        double txBadRate = wifiInfo.txBadRate;
+        double rxSuccessRate = wifiInfo.rxSuccessRate;
+        try {
+            String timestamp = new SimpleDateFormat("MM-dd HH:mm:ss.SSS").format(new Date(now));
+            String s = String.format(Locale.US, // Use US to avoid comma/decimal confusion
+                    "%s,%d,%.1f,%d,%d,%.2f,%.2f,%.2f,%.2f,%d,%d",
+                    timestamp, mSessionNumber, rssi, freq, linkSpeed,
+                    txSuccessRate, txRetriesRate, txBadRate, rxSuccessRate,
+                    s0, s1);
+            mLinkMetricsHistory.add(s);
+        } catch (Exception e) {
+            Log.e(TAG, "format problem", e);
         }
-
-        if (linkSpeed < linkspeedThreshBad) {
-            score -= BAD_LINKSPEED_PENALTY;
-        } else if ((linkSpeed >= linkspeedThreshGood) && (wifiInfo.txSuccessRate > 5)) {
-            score += GOOD_LINKSPEED_BONUS; // So as bad rssi alone doesn't kill us
+        while (mLinkMetricsHistory.size() > DUMPSYS_ENTRY_COUNT_LIMIT) {
+            mLinkMetricsHistory.removeFirst();
         }
-
-        score -= wifiInfo.badRssiCount * BAD_RSSI_COUNT_PENALTY + wifiInfo.lowRssiCount;
-
-        if (rssi >= rssiThreshSaturated) score += 5;
-
-        if (score > NetworkAgent.WIFI_BASE_SCORE) score = NetworkAgent.WIFI_BASE_SCORE;
-        if (score < 0) score = 0;
-
-        return score;
     }
 
     /**
-     * Determines if we can see both 2.4GHz and 5GHz for current config
+     * Tag to be used in dumpsys request
      */
-    private boolean multiBandScanResults(WifiInfo wifiInfo) {
-        WifiConfiguration currentConfiguration =
-                mWifiConfigManager.getConfiguredNetwork(wifiInfo.getNetworkId());
-        if (currentConfiguration == null) return false;
-        ScanDetailCache scanDetailCache =
-                mWifiConfigManager.getScanDetailCacheForNetwork(wifiInfo.getNetworkId());
-        if (scanDetailCache == null) return false;
-        // Nasty that we change state here...
-        currentConfiguration.setVisibility(scanDetailCache.getVisibility(SCAN_CACHE_VISIBILITY_MS));
-        if (currentConfiguration.visibility == null) return false;
-        if (currentConfiguration.visibility.rssi24 == WifiConfiguration.INVALID_RSSI) return false;
-        if (currentConfiguration.visibility.rssi5 == WifiConfiguration.INVALID_RSSI) return false;
-        // N.B. this does not do exactly what is claimed!
-        if (currentConfiguration.visibility.rssi24
-                >= currentConfiguration.visibility.rssi5 - SCAN_CACHE_COUNT_PENALTY) {
-            return true;
-        }
-        return false;
-    }
+    public static final String DUMP_ARG = "WifiScoreReport";
 
     /**
-     * Decides whether the current network is a "home" network
+     * Dump logged signal strength and traffic measurements.
+     * @param fd unused
+     * @param pw PrintWriter for writing dump to
+     * @param args unused
      */
-    private boolean isHomeNetwork(WifiInfo wifiInfo) {
-        WifiConfiguration currentConfiguration =
-                mWifiConfigManager.getConfiguredNetwork(wifiInfo.getNetworkId());
-        if (currentConfiguration == null) return false;
-        // This seems like it will only return true for really old routers!
-        if (currentConfiguration.allowedKeyManagement.cardinality() != 1) return false;
-        if (!currentConfiguration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
-            return false;
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("time,session,rssi,freq,linkspeed,tx_good,tx_retry,tx_bad,rx,s0,s1");
+        for (String line : mLinkMetricsHistory) {
+            pw.println(line);
         }
-        ScanDetailCache scanDetailCache =
-                mWifiConfigManager.getScanDetailCacheForNetwork(wifiInfo.getNetworkId());
-        if (scanDetailCache == null) return false;
-        if (scanDetailCache.size() <= HOME_VISIBLE_NETWORK_MAX_COUNT) {
-            return true;
-        }
-        return false;
     }
 }
diff --git a/service/java/com/android/server/wifi/WifiServiceImpl.java b/service/java/com/android/server/wifi/WifiServiceImpl.java
index 5c9548a..d6faf9b 100644
--- a/service/java/com/android/server/wifi/WifiServiceImpl.java
+++ b/service/java/com/android/server/wifi/WifiServiceImpl.java
@@ -73,6 +73,7 @@
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiManager.LocalOnlyHotspotCallback;
 import android.net.wifi.WifiScanner;
+import android.net.wifi.hotspot2.OsuProvider;
 import android.net.wifi.hotspot2.PasspointConfiguration;
 import android.os.AsyncTask;
 import android.os.BatteryStats;
@@ -88,6 +89,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.os.ShellCallback;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.WorkSource;
@@ -167,8 +169,6 @@
     // Debug counter tracking scan requests sent by WifiManager
     private int scanRequestCounter = 0;
 
-    /* Tracks the open wi-fi network notification */
-    private WifiNotificationController mNotificationController;
     /* Polls traffic stats and notifies clients */
     private WifiTrafficPoller mTrafficPoller;
     /* Tracks the persisted states for wi-fi & airplane mode */
@@ -260,55 +260,81 @@
                     break;
                 }
                 case WifiManager.CONNECT_NETWORK: {
-                    WifiConfiguration config = (WifiConfiguration) msg.obj;
-                    int networkId = msg.arg1;
-                    Slog.d("WiFiServiceImpl ", "CONNECT "
-                            + " nid=" + Integer.toString(networkId)
-                            + " uid=" + msg.sendingUid
-                            + " name="
-                            + mContext.getPackageManager().getNameForUid(msg.sendingUid));
-                    if (config != null && isValid(config)) {
-                        if (DBG) Slog.d(TAG, "Connect with config " + config);
-                        /* Command is forwarded to state machine */
-                        mWifiStateMachine.sendMessage(Message.obtain(msg));
-                    } else if (config == null
-                            && networkId != WifiConfiguration.INVALID_NETWORK_ID) {
-                        if (DBG) Slog.d(TAG, "Connect with networkId " + networkId);
-                        mWifiStateMachine.sendMessage(Message.obtain(msg));
-                    } else {
-                        Slog.e(TAG, "ClientHandler.handleMessage ignoring invalid msg=" + msg);
-                        replyFailed(msg, WifiManager.CONNECT_NETWORK_FAILED,
-                                WifiManager.INVALID_ARGS);
+                    if (checkChangePermissionAndReplyIfNotAuthorized(
+                            msg, WifiManager.CONNECT_NETWORK_FAILED)) {
+                        WifiConfiguration config = (WifiConfiguration) msg.obj;
+                        int networkId = msg.arg1;
+                        Slog.d(TAG, "CONNECT "
+                                + " nid=" + Integer.toString(networkId)
+                                + " uid=" + msg.sendingUid
+                                + " name="
+                                + mContext.getPackageManager().getNameForUid(msg.sendingUid));
+                        if (config != null) {
+                            if (DBG) Slog.d(TAG, "Connect with config " + config);
+                            /* Command is forwarded to state machine */
+                            mWifiStateMachine.sendMessage(Message.obtain(msg));
+                        } else if (config == null
+                                && networkId != WifiConfiguration.INVALID_NETWORK_ID) {
+                            if (DBG) Slog.d(TAG, "Connect with networkId " + networkId);
+                            mWifiStateMachine.sendMessage(Message.obtain(msg));
+                        } else {
+                            Slog.e(TAG, "ClientHandler.handleMessage ignoring invalid msg=" + msg);
+                            replyFailed(msg, WifiManager.CONNECT_NETWORK_FAILED,
+                                    WifiManager.INVALID_ARGS);
+                        }
                     }
                     break;
                 }
                 case WifiManager.SAVE_NETWORK: {
-                    WifiConfiguration config = (WifiConfiguration) msg.obj;
-                    int networkId = msg.arg1;
-                    Slog.d("WiFiServiceImpl ", "SAVE"
-                            + " nid=" + Integer.toString(networkId)
-                            + " uid=" + msg.sendingUid
-                            + " name="
-                            + mContext.getPackageManager().getNameForUid(msg.sendingUid));
-                    if (config != null && isValid(config)) {
-                        if (DBG) Slog.d(TAG, "Save network with config " + config);
-                        /* Command is forwarded to state machine */
-                        mWifiStateMachine.sendMessage(Message.obtain(msg));
-                    } else {
-                        Slog.e(TAG, "ClientHandler.handleMessage ignoring invalid msg=" + msg);
-                        replyFailed(msg, WifiManager.SAVE_NETWORK_FAILED,
-                                WifiManager.INVALID_ARGS);
+                    if (checkChangePermissionAndReplyIfNotAuthorized(
+                            msg, WifiManager.SAVE_NETWORK_FAILED)) {
+                        WifiConfiguration config = (WifiConfiguration) msg.obj;
+                        int networkId = msg.arg1;
+                        Slog.d(TAG, "SAVE"
+                                + " nid=" + Integer.toString(networkId)
+                                + " uid=" + msg.sendingUid
+                                + " name="
+                                + mContext.getPackageManager().getNameForUid(msg.sendingUid));
+                        if (config != null) {
+                            if (DBG) Slog.d(TAG, "Save network with config " + config);
+                            /* Command is forwarded to state machine */
+                            mWifiStateMachine.sendMessage(Message.obtain(msg));
+                        } else {
+                            Slog.e(TAG, "ClientHandler.handleMessage ignoring invalid msg=" + msg);
+                            replyFailed(msg, WifiManager.SAVE_NETWORK_FAILED,
+                                    WifiManager.INVALID_ARGS);
+                        }
                     }
                     break;
                 }
                 case WifiManager.FORGET_NETWORK:
-                    mWifiStateMachine.sendMessage(Message.obtain(msg));
+                    if (checkChangePermissionAndReplyIfNotAuthorized(
+                            msg, WifiManager.FORGET_NETWORK_FAILED)) {
+                        mWifiStateMachine.sendMessage(Message.obtain(msg));
+                    }
                     break;
                 case WifiManager.START_WPS:
+                    if (checkChangePermissionAndReplyIfNotAuthorized(msg, WifiManager.WPS_FAILED)) {
+                        mWifiStateMachine.sendMessage(Message.obtain(msg));
+                    }
+                    break;
                 case WifiManager.CANCEL_WPS:
+                    if (checkChangePermissionAndReplyIfNotAuthorized(
+                            msg, WifiManager.CANCEL_WPS_FAILED)) {
+                        mWifiStateMachine.sendMessage(Message.obtain(msg));
+                    }
+                    break;
                 case WifiManager.DISABLE_NETWORK:
+                    if (checkChangePermissionAndReplyIfNotAuthorized(
+                            msg, WifiManager.DISABLE_NETWORK_FAILED)) {
+                        mWifiStateMachine.sendMessage(Message.obtain(msg));
+                    }
+                    break;
                 case WifiManager.RSSI_PKTCNT_FETCH: {
-                    mWifiStateMachine.sendMessage(Message.obtain(msg));
+                    if (checkChangePermissionAndReplyIfNotAuthorized(
+                            msg, WifiManager.RSSI_PKTCNT_FETCH_FAILED)) {
+                        mWifiStateMachine.sendMessage(Message.obtain(msg));
+                    }
                     break;
                 }
                 default: {
@@ -318,6 +344,25 @@
             }
         }
 
+        /**
+         * Helper method to check if the sender of the message holds the
+         * {@link Manifest.permission#CHANGE_WIFI_STATE} permission, and reply with a failure if it
+         * doesn't
+         *
+         * @param msg Incoming message.
+         * @param replyWhat Param to be filled in the {@link Message#what} field of the failure
+         *                  reply.
+         * @return true if the sender holds the permission, false otherwise.
+         */
+        private boolean checkChangePermissionAndReplyIfNotAuthorized(Message msg, int replyWhat) {
+            if (!mWifiPermissionsUtil.checkChangePermission(msg.sendingUid)) {
+                Slog.e(TAG, "ClientHandler.handleMessage ignoring unauthorized msg=" + msg);
+                replyFailed(msg, replyWhat, WifiManager.NOT_AUTHORIZED);
+                return false;
+            }
+            return true;
+        }
+
         private void replyFailed(Message msg, int what, int why) {
             if (msg.replyTo == null) return;
             Message reply = Message.obtain();
@@ -394,7 +439,6 @@
         mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
         mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
         mCertManager = mWifiInjector.getWifiCertManager();
-        mNotificationController = mWifiInjector.getWifiNotificationController();
         mWifiLockManager = mWifiInjector.getWifiLockManager();
         mWifiMulticastLockManager = mWifiInjector.getWifiMulticastLockManager();
         HandlerThread wifiServiceHandlerThread = mWifiInjector.getWifiServiceHandlerThread();
@@ -721,12 +765,6 @@
         mWifiPermissionsUtil.enforceLocationPermission(pkgName, uid);
     }
 
-    private boolean checkNetworkSettingsPermission() {
-        int result = mContext.checkCallingOrSelfPermission(
-                android.Manifest.permission.NETWORK_SETTINGS);
-        return result == PackageManager.PERMISSION_GRANTED;
-    }
-
     /**
      * see {@link android.net.wifi.WifiManager#setWifiEnabled(boolean)}
      * @param enable {@code true} to enable, {@code false} to disable.
@@ -742,10 +780,19 @@
         mLog.trace("setWifiEnabled package=% uid=% enable=%").c(packageName)
                 .c(Binder.getCallingUid()).c(enable).flush();
 
+        boolean isFromSettings =
+                mWifiPermissionsUtil.checkNetworkSettingsPermission(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();
+            return false;
+        }
+
         // If SoftAp is enabled, only Settings is allowed to toggle wifi
         boolean apEnabled =
                 mWifiStateMachine.syncGetWifiApState() != WifiManager.WIFI_AP_STATE_DISABLED;
-        boolean isFromSettings = checkNetworkSettingsPermission();
+
         if (apEnabled && !isFromSettings) {
             mLog.trace("setWifiEnabled SoftAp not disabled: only Settings can enable wifi").flush();
             return false;
@@ -1528,14 +1575,31 @@
     public WifiConfiguration getMatchingWifiConfig(ScanResult scanResult) {
         enforceAccessPermission();
         mLog.trace("getMatchingWifiConfig uid=%").c(Binder.getCallingUid()).flush();
-        if (!mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_wifi_hotspot2_enabled)) {
+        if (!mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_WIFI_PASSPOINT)) {
             throw new UnsupportedOperationException("Passpoint not enabled");
         }
         return mWifiStateMachine.syncGetMatchingWifiConfig(scanResult, mWifiStateMachineChannel);
     }
 
     /**
+     * Returns list of OSU (Online Sign-Up) providers associated with the given Passpoint network.
+     *
+     * @param scanResult scanResult of the Passpoint AP
+     * @return List of {@link OsuProvider}
+     */
+    @Override
+    public List<OsuProvider> getMatchingOsuProviders(ScanResult scanResult) {
+        enforceAccessPermission();
+        mLog.trace("getMatchingOsuProviders uid=%").c(Binder.getCallingUid()).flush();
+        if (!mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_WIFI_PASSPOINT)) {
+            throw new UnsupportedOperationException("Passpoint not enabled");
+        }
+        return mWifiStateMachine.syncGetMatchingOsuProviders(scanResult, mWifiStateMachineChannel);
+    }
+
+    /**
      * see {@link android.net.wifi.WifiManager#addOrUpdateNetwork(WifiConfiguration)}
      * @return the supplicant-assigned identifier for the new or updated
      * network if the operation succeeds, or {@code -1} if it fails
@@ -1569,7 +1633,7 @@
             return 0;
         }
 
-        if (isValid(config)) {
+        if (config != null) {
             //TODO: pass the Uid the WifiStateMachine as a message parameter
             Slog.i("addOrUpdateNetwork", " uid = " + Integer.toString(Binder.getCallingUid())
                     + " SSID " + config.SSID
@@ -1717,8 +1781,8 @@
     public boolean addOrUpdatePasspointConfiguration(PasspointConfiguration config) {
         enforceChangePermission();
         mLog.trace("addorUpdatePasspointConfiguration uid=%").c(Binder.getCallingUid()).flush();
-        if (!mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_wifi_hotspot2_enabled)) {
+        if (!mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_WIFI_PASSPOINT)) {
             throw new UnsupportedOperationException("Passpoint not enabled");
         }
         return mWifiStateMachine.syncAddOrUpdatePasspointConfig(mWifiStateMachineChannel, config,
@@ -1735,8 +1799,8 @@
     public boolean removePasspointConfiguration(String fqdn) {
         enforceChangePermission();
         mLog.trace("removePasspointConfiguration uid=%").c(Binder.getCallingUid()).flush();
-        if (!mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_wifi_hotspot2_enabled)) {
+        if (!mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_WIFI_PASSPOINT)) {
             throw new UnsupportedOperationException("Passpoint not enabled");
         }
         return mWifiStateMachine.syncRemovePasspointConfig(mWifiStateMachineChannel, fqdn);
@@ -1753,8 +1817,8 @@
     public List<PasspointConfiguration> getPasspointConfigurations() {
         enforceAccessPermission();
         mLog.trace("getPasspointConfigurations uid=%").c(Binder.getCallingUid()).flush();
-        if (!mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_wifi_hotspot2_enabled)) {
+        if (!mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_WIFI_PASSPOINT)) {
             throw new UnsupportedOperationException("Passpoint not enabled");
         }
         return mWifiStateMachine.syncGetPasspointConfigs(mWifiStateMachineChannel);
@@ -1769,8 +1833,8 @@
     public void queryPasspointIcon(long bssid, String fileName) {
         enforceAccessPermission();
         mLog.trace("queryPasspointIcon uid=%").c(Binder.getCallingUid()).flush();
-        if (!mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_wifi_hotspot2_enabled)) {
+        if (!mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_WIFI_PASSPOINT)) {
             throw new UnsupportedOperationException("Passpoint not enabled");
         }
         mWifiStateMachine.syncQueryPasspointIcon(mWifiStateMachineChannel, bssid, fileName);
@@ -2196,6 +2260,13 @@
     }
 
     @Override
+    public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+            String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
+        (new WifiShellCommand(mWifiStateMachine)).exec(this, in, out, err, args, callback,
+                resultReceiver);
+    }
+
+    @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
                 != PackageManager.PERMISSION_GRANTED) {
@@ -2213,18 +2284,9 @@
             String[] ipManagerArgs = new String[args.length - 1];
             System.arraycopy(args, 1, ipManagerArgs, 0, ipManagerArgs.length);
             mWifiStateMachine.dumpIpManager(fd, pw, ipManagerArgs);
-        } else if (args != null && args.length > 0
-                && DUMP_ARG_SET_IPREACH_DISCONNECT.equals(args[0])) {
-            if (args.length > 1) {
-                if (DUMP_ARG_SET_IPREACH_DISCONNECT_ENABLED.equals(args[1])) {
-                    mWifiStateMachine.setIpReachabilityDisconnectEnabled(true);
-                } else if (DUMP_ARG_SET_IPREACH_DISCONNECT_DISABLED.equals(args[1])) {
-                    mWifiStateMachine.setIpReachabilityDisconnectEnabled(false);
-                }
-            }
-            pw.println("IPREACH_DISCONNECT state is "
-                    + mWifiStateMachine.getIpReachabilityDisconnectEnabled());
-            return;
+        } else if (args != null && args.length > 0 && WifiScoreReport.DUMP_ARG.equals(args[0])) {
+            WifiScoreReport wifiScoreReport = mWifiStateMachine.getWifiScoreReport();
+            if (wifiScoreReport != null) wifiScoreReport.dump(fd, pw, args);
         } else {
             pw.println("Wi-Fi is " + mWifiStateMachine.syncGetWifiStateByName());
             pw.println("Stay-awake conditions: " +
@@ -2234,7 +2296,6 @@
             pw.println("mScanPending " + mScanPending);
             mWifiController.dump(fd, pw, args);
             mSettingsStore.dump(fd, pw, args);
-            mNotificationController.dump(fd, pw, args);
             mTrafficPoller.dump(fd, pw, args);
             pw.println();
             pw.println("Locks held:");
@@ -2249,6 +2310,12 @@
             pw.println();
             mWifiBackupRestore.dump(fd, pw, args);
             pw.println();
+            WifiScoreReport wifiScoreReport = mWifiStateMachine.getWifiScoreReport();
+            if (wifiScoreReport != null) {
+                pw.println("WifiScoreReport:");
+                wifiScoreReport.dump(fd, pw, args);
+            }
+            pw.println();
         }
     }
 
diff --git a/service/java/com/android/server/wifi/WifiShellCommand.java b/service/java/com/android/server/wifi/WifiShellCommand.java
new file mode 100644
index 0000000..f4db178
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiShellCommand.java
@@ -0,0 +1,137 @@
+/*
+ * 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.AppGlobals;
+import android.content.pm.IPackageManager;
+import android.os.Binder;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+/**
+ * Interprets and executes 'adb shell cmd wifi [args]'.
+ *
+ * To add new commands:
+ * - onCommand: Add a case "<command>" execute. Return a 0
+ *   if command executed successfully.
+ * - onHelp: add a description string.
+ *
+ * If additional state objects are necessary add them to the
+ * constructor.
+ *
+ * Permissions: currently root permission is required for all
+ * commands. If the requirement needs to be relaxed then modify
+ * the onCommand method to check for specific permissions on
+ * individual commands.
+ */
+public class WifiShellCommand extends ShellCommand {
+    private final WifiStateMachine mStateMachine;
+    private final IPackageManager mPM;
+
+    WifiShellCommand(WifiStateMachine stateMachine) {
+        mStateMachine = stateMachine;
+        mPM = AppGlobals.getPackageManager();
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        checkRootPermission();
+
+        final PrintWriter pw = getOutPrintWriter();
+        try {
+            switch (cmd != null ? cmd : "") {
+                case "set-ipreach-disconnect": {
+                    boolean enabled;
+                    String nextArg = getNextArgRequired();
+                    if ("enabled".equals(nextArg)) {
+                        enabled = true;
+                    } else if ("disabled".equals(nextArg)) {
+                        enabled = false;
+                    } else {
+                        pw.println(
+                                "Invalid argument to 'set-ipreach-disconnect' - must be 'enabled'"
+                                        + " or 'disabled'");
+                        return -1;
+                    }
+                    mStateMachine.setIpReachabilityDisconnectEnabled(enabled);
+                    return 0;
+                }
+                case "get-ipreach-disconnect":
+                    pw.println("IPREACH_DISCONNECT state is "
+                            + mStateMachine.getIpReachabilityDisconnectEnabled());
+                    return 0;
+                case "set-poll-rssi-interval-msecs":
+                    int newPollIntervalMsecs;
+                    try {
+                        newPollIntervalMsecs = Integer.parseInt(getNextArgRequired());
+                    } catch (NumberFormatException e) {
+                        pw.println(
+                                "Invalid argument to 'set-poll-rssi-interval-msecs' "
+                                        + "- must be a positive integer");
+                        return -1;
+                    }
+
+                    if (newPollIntervalMsecs < 1) {
+                        pw.println(
+                                "Invalid argument to 'set-poll-rssi-interval-msecs' "
+                                        + "- must be a positive integer");
+                        return -1;
+                    }
+
+                    mStateMachine.setPollRssiIntervalMsecs(newPollIntervalMsecs);
+                    return 0;
+                case "get-poll-rssi-interval-msecs":
+                    pw.println("WifiStateMachine.mPollRssiIntervalMsecs = "
+                            + mStateMachine.getPollRssiIntervalMsecs());
+                    return 0;
+                default:
+                    return handleDefaultCommands(cmd);
+            }
+        } catch (Exception e) {
+            pw.println("Exception: " + e);
+        }
+        return -1;
+    }
+
+    private void checkRootPermission() {
+        final int uid = Binder.getCallingUid();
+        if (uid == 0) {
+            // Root can do anything.
+            return;
+        }
+        throw new SecurityException("Uid " + uid + " does not have access to wifi commands");
+    }
+
+    @Override
+    public void onHelp() {
+        final PrintWriter pw = getOutPrintWriter();
+
+        pw.println("Wi-Fi (wifi) commands:");
+        pw.println("  help");
+        pw.println("    Print this help text.");
+        pw.println("  set-ipreach-disconnect enabled|disabled");
+        pw.println("    Sets whether CMD_IP_REACHABILITY_LOST events should trigger disconnects.");
+        pw.println("  get-ipreach-disconnect");
+        pw.println("    Gets setting of CMD_IP_REACHABILITY_LOST events triggering disconnects.");
+        pw.println("  set-poll-rssi-interval-msecs <int>");
+        pw.println("    Sets the interval between RSSI polls to <int> milliseconds.");
+        pw.println("  get-poll-rssi-interval-msecs");
+        pw.println("    Gets current interval between RSSI polls, in milliseconds.");
+        pw.println();
+    }
+}
diff --git a/service/java/com/android/server/wifi/WifiStateMachine.java b/service/java/com/android/server/wifi/WifiStateMachine.java
index 19c4812..3649f8f 100644
--- a/service/java/com/android/server/wifi/WifiStateMachine.java
+++ b/service/java/com/android/server/wifi/WifiStateMachine.java
@@ -26,6 +26,8 @@
 import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED;
 import static android.net.wifi.WifiManager.WIFI_STATE_ENABLING;
 import static android.net.wifi.WifiManager.WIFI_STATE_UNKNOWN;
+import static android.telephony.TelephonyManager.CALL_STATE_IDLE;
+import static android.telephony.TelephonyManager.CALL_STATE_OFFHOOK;
 
 import android.Manifest;
 import android.app.ActivityManager;
@@ -75,6 +77,7 @@
 import android.net.wifi.WpsInfo;
 import android.net.wifi.WpsResult;
 import android.net.wifi.WpsResult.Status;
+import android.net.wifi.hotspot2.OsuProvider;
 import android.net.wifi.hotspot2.PasspointConfiguration;
 import android.net.wifi.p2p.IWifiP2pManager;
 import android.os.BatteryStats;
@@ -92,9 +95,11 @@
 import android.os.UserManager;
 import android.os.WorkSource;
 import android.provider.Settings;
+import android.telephony.PhoneStateListener;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.Pair;
 import android.util.SparseArray;
 
 import com.android.internal.R;
@@ -221,6 +226,9 @@
     private final WifiCountryCode mCountryCode;
     // Object holding most recent wifi score report and bad Linkspeed count
     private final WifiScoreReport mWifiScoreReport;
+    public WifiScoreReport getWifiScoreReport() {
+        return mWifiScoreReport;
+    }
     private final PasspointManager mPasspointManager;
 
     /* Scan results handling */
@@ -284,6 +292,8 @@
     private boolean testNetworkDisconnect = false;
 
     private boolean mEnableRssiPolling = false;
+    // Accessed via Binder thread ({get,set}PollRssiIntervalMsecs), and WifiStateMachine thread.
+    private volatile int mPollRssiIntervalMsecs = DEFAULT_POLL_RSSI_INTERVAL_MSECS;
     private int mRssiPollToken = 0;
     /* 3 operational states for STA operation: CONNECT_MODE, SCAN_ONLY_MODE, SCAN_ONLY_WIFI_OFF_MODE
     * In CONNECT_MODE, the STA can scan and connect to an access point
@@ -311,7 +321,7 @@
      * Interval in milliseconds between polling for RSSI
      * and linkspeed information
      */
-    private static final int POLL_RSSI_INTERVAL_MSECS = 3000;
+    private static final int DEFAULT_POLL_RSSI_INTERVAL_MSECS = 3000;
 
     /**
      * Interval in milliseconds between receiving a disconnect event
@@ -379,6 +389,14 @@
     private long mLastDriverRoamAttempt = 0;
     private WifiConfiguration targetWificonfiguration = null;
 
+    int getPollRssiIntervalMsecs() {
+        return mPollRssiIntervalMsecs;
+    }
+
+    void setPollRssiIntervalMsecs(int newPollIntervalMsecs) {
+        mPollRssiIntervalMsecs = newPollIntervalMsecs;
+    }
+
     /**
      * Method to clear {@link #mTargetRoamBSSID} and reset the the current connected network's
      * bssid in wpa_supplicant after a roam/connect attempt.
@@ -590,6 +608,9 @@
     // Get the list of installed Passpoint configurations.
     static final int CMD_GET_PASSPOINT_CONFIGS                          = BASE + 108;
 
+    // Get the list of OSU providers associated with a Passpoint network.
+    static final int CMD_GET_MATCHING_OSU_PROVIDERS                     = BASE + 109;
+
     /* Commands from/to the SupplicantStateTracker */
     /* Reset the supplicant state tracker */
     static final int CMD_RESET_SUPPLICANT_STATE                         = BASE + 111;
@@ -711,6 +732,9 @@
     /* Indicates that diagnostics should time out a connection start event. */
     private static final int CMD_DIAGS_CONNECT_TIMEOUT                  = BASE + 252;
 
+    /* Used to set the tx power limit for SAR during the start of a phone call. */
+    private static final int CMD_SELECT_TX_POWER_SCENARIO               = BASE + 253;
+
     // For message logging.
     private static final Class[] sMessageClasses = {
             AsyncChannel.class, WifiStateMachine.class, DhcpClient.class };
@@ -742,6 +766,14 @@
     private static final int SUSPEND_DUE_TO_HIGH_PERF = 1 << 1;
     private static final int SUSPEND_DUE_TO_SCREEN = 1 << 2;
 
+    /**
+     * Time window in milliseconds for which we send
+     * {@link NetworkAgent#explicitlySelected(boolean)}
+     * after connecting to the network which the user last selected.
+     */
+    @VisibleForTesting
+    public static final int LAST_SELECTED_NETWORK_EXPIRATION_AGE_MILLIS = 30 * 1000;
+
     /* Tracks if user has enabled suspend optimizations through settings */
     private AtomicBoolean mUserWantsSuspendOpt = new AtomicBoolean(true);
 
@@ -768,6 +800,7 @@
     private final boolean mEnableLinkDebouncing;
     private final boolean mEnableChipWakeUpWhenAssociated;
     private final boolean mEnableRssiPollWhenAssociated;
+    private final boolean mEnableVoiceCallSarTxPowerLimit;
 
     int mRunningBeaconCount = 0;
 
@@ -857,6 +890,7 @@
         }
         return mTelephonyManager;
     }
+    private final WifiPhoneStateListener mPhoneStateListener;
 
     private final IBatteryStats mBatteryStats;
 
@@ -868,11 +902,13 @@
     private FrameworkFacade mFacade;
     private WifiStateTracker mWifiStateTracker;
     private final BackupManagerProxy mBackupManagerProxy;
+    private final WrongPasswordNotifier mWrongPasswordNotifier;
 
     public WifiStateMachine(Context context, FrameworkFacade facade, Looper looper,
                             UserManager userManager, WifiInjector wifiInjector,
                             BackupManagerProxy backupManagerProxy, WifiCountryCode countryCode,
-                            WifiNative wifiNative) {
+                            WifiNative wifiNative,
+                            WrongPasswordNotifier wrongPasswordNotifier) {
         super("WifiStateMachine", looper);
         mWifiInjector = wifiInjector;
         mWifiMetrics = mWifiInjector.getWifiMetrics();
@@ -883,6 +919,7 @@
         mFacade = facade;
         mWifiNative = wifiNative;
         mBackupManagerProxy = backupManagerProxy;
+        mWrongPasswordNotifier = wrongPasswordNotifier;
 
         // TODO refactor WifiNative use of context out into it's own class
         mInterfaceName = mWifiNative.getInterfaceName();
@@ -910,6 +947,7 @@
                 mFacade.makeSupplicantStateTracker(context, mWifiConfigManager, getHandler());
 
         mLinkProperties = new LinkProperties();
+        mPhoneStateListener = new WifiPhoneStateListener(looper);
 
         mNetworkInfo.setIsAvailable(false);
         mLastBssid = null;
@@ -931,7 +969,7 @@
 
         mCountryCode = countryCode;
 
-        mWifiScoreReport = new WifiScoreReport(mContext, mWifiConfigManager);
+        mWifiScoreReport = new WifiScoreReport(mContext, mWifiConfigManager, mClock);
 
         mUserWantsSuspendOpt.set(mFacade.getIntegerSetting(mContext,
                 Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED, 1) == 1);
@@ -1007,6 +1045,8 @@
                 R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz);
         mEnableLinkDebouncing = mContext.getResources().getBoolean(
                 R.bool.config_wifi_enable_disconnection_debounce);
+        mEnableVoiceCallSarTxPowerLimit = mContext.getResources().getBoolean(
+                R.bool.config_wifi_framework_enable_voice_call_sar_tx_power_limit);
         mEnableChipWakeUpWhenAssociated = true;
         mEnableRssiPollWhenAssociated = true;
 
@@ -1864,6 +1904,22 @@
     }
 
     /**
+     * Retrieve a list of {@link OsuProvider} associated with the given AP synchronously.
+     *
+     * @param scanResult The scan result of the AP
+     * @param channel Channel for communicating with the state machine
+     * @return List of {@link OsuProvider}
+     */
+    public List<OsuProvider> syncGetMatchingOsuProviders(ScanResult scanResult,
+            AsyncChannel channel) {
+        Message resultMsg =
+                channel.sendMessageSynchronously(CMD_GET_MATCHING_OSU_PROVIDERS, scanResult);
+        List<OsuProvider> providers = (List<OsuProvider>) resultMsg.obj;
+        resultMsg.recycle();
+        return providers;
+    }
+
+    /**
      * Add or update a Passpoint configuration synchronously.
      *
      * @param channel Channel for communicating with the state machine
@@ -3083,11 +3139,11 @@
     }
 
     private WifiInfo getWiFiInfoForUid(int uid) {
+        WifiInfo result = new WifiInfo(mWifiInfo);
         if (Binder.getCallingUid() == Process.myUid()) {
-            return mWifiInfo;
+            return result;
         }
 
-        WifiInfo result = new WifiInfo(mWifiInfo);
         result.setMacAddress(WifiInfo.DEFAULT_MAC_ADDRESS);
 
         IBinder binder = mFacade.getService("package");
@@ -3211,6 +3267,7 @@
             if (scanDetailCache != null) {
                 ScanDetail scanDetail = scanDetailCache.getScanDetail(stateChangeResult.BSSID);
                 if (scanDetail != null) {
+                    mWifiInfo.setFrequency(scanDetail.getScanResult().frequency);
                     NetworkDetail networkDetail = scanDetail.getNetworkDetail();
                     if (networkDetail != null
                             && networkDetail.getAnt() == NetworkDetail.Ant.ChargeablePublic) {
@@ -3358,6 +3415,7 @@
         mDiagsConnectionStartMillis = mClock.getElapsedSinceBootMillis();
         mWifiDiagnostics.reportConnectionEvent(
                 mDiagsConnectionStartMillis, WifiDiagnostics.CONNECTION_EVENT_STARTED);
+        mWrongPasswordNotifier.onNewConnectionAttempt();
         // TODO(b/35329124): Remove CMD_DIAGS_CONNECT_TIMEOUT, once WifiStateMachine
         // grows a proper CONNECTING state.
         sendMessageDelayed(CMD_DIAGS_CONNECT_TIMEOUT,
@@ -3548,6 +3606,27 @@
 
     }
 
+    /**
+     * Determine if the specified auth failure is considered to be a permanent wrong password
+     * failure. The criteria for such failure is when wrong password error is detected
+     * and the network had never been connected before.
+     *
+     * For networks that have previously connected successfully, we consider wrong password
+     * failures to be temporary, to be on the conservative side.  Since this might be the
+     * case where we are trying to connect to a wrong network (e.g. A network with same SSID
+     * but different password).
+     */
+    private boolean isPermanentWrongPasswordFailure(int networkId, int reasonCode) {
+        if (reasonCode != WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD) {
+            return false;
+        }
+        WifiConfiguration network = mWifiConfigManager.getConfiguredNetwork(networkId);
+        if (network != null && network.getNetworkSelectionStatus().getHasEverConnected()) {
+            return false;
+        }
+        return true;
+    }
+
     private class WifiNetworkFactory extends NetworkFactory {
         public WifiNetworkFactory(Looper l, Context c, String TAG, NetworkCapabilities f) {
             super(l, c, TAG, f);
@@ -3667,6 +3746,51 @@
         }
     }
 
+    /**
+     * Helper function to increment the appropriate setup failure metrics.
+     */
+    private void incrementMetricsForSetupFailure(int failureReason) {
+        if (failureReason == WifiNative.SETUP_FAILURE_HAL) {
+            mWifiMetrics.incrementNumWifiOnFailureDueToHal();
+        } else if (failureReason == WifiNative.SETUP_FAILURE_WIFICOND) {
+            mWifiMetrics.incrementNumWifiOnFailureDueToWificond();
+        }
+    }
+
+    /**
+     * Register the phone listener if we need to set/reset the power limits during voice call for
+     * this device.
+     */
+    private void maybeRegisterPhoneListener() {
+        if (mEnableVoiceCallSarTxPowerLimit) {
+            logd("Registering for telephony call state changes");
+            getTelephonyManager().listen(
+                    mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
+        }
+    }
+
+    /**
+     * Listen for phone call state events to set/reset TX power limits for SAR requirements.
+     */
+    private class WifiPhoneStateListener extends PhoneStateListener {
+        WifiPhoneStateListener(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void onCallStateChanged(int state, String incomingNumber) {
+            if (mEnableVoiceCallSarTxPowerLimit) {
+                if (state == CALL_STATE_OFFHOOK) {
+                    sendMessage(CMD_SELECT_TX_POWER_SCENARIO,
+                            WifiNative.TX_POWER_SCENARIO_VOICE_CALL);
+                } else if (state == CALL_STATE_IDLE) {
+                    sendMessage(CMD_SELECT_TX_POWER_SCENARIO,
+                            WifiNative.TX_POWER_SCENARIO_NORMAL);
+                }
+            }
+        }
+    }
+
     /********************************************************
      * HSM states
      *******************************************************/
@@ -3769,6 +3893,7 @@
                         Log.e(TAG, "Failed to load from config store");
                     }
                     maybeRegisterNetworkFactory();
+                    maybeRegisterPhoneListener();
                     break;
                 case CMD_SCREEN_STATE_CHANGED:
                     handleScreenStateChanged(message.arg1 != 0);
@@ -3818,6 +3943,7 @@
                 case CMD_ROAM_WATCHDOG_TIMER:
                 case CMD_DISABLE_P2P_WATCHDOG_TIMER:
                 case CMD_DISABLE_EPHEMERAL_NETWORK:
+                case CMD_SELECT_TX_POWER_SCENARIO:
                     messageHandlingStatus = MESSAGE_HANDLING_STATUS_DISCARD;
                     break;
                 case CMD_SET_SUSPEND_OPT_ENABLED:
@@ -3889,6 +4015,9 @@
                 case CMD_GET_MATCHING_CONFIG:
                     replyToMessage(message, message.what);
                     break;
+                case CMD_GET_MATCHING_OSU_PROVIDERS:
+                    replyToMessage(message, message.what, new ArrayList<OsuProvider>());
+                    break;
                 case CMD_IP_CONFIGURATION_SUCCESSFUL:
                 case CMD_IP_CONFIGURATION_LOST:
                 case CMD_IP_REACHABILITY_LOST:
@@ -3984,6 +4113,11 @@
                     mWifiDiagnostics.reportConnectionEvent(
                             (Long) message.obj, BaseWifiDiagnostics.CONNECTION_EVENT_FAILED);
                     break;
+                case 0:
+                    // We want to notice any empty messages (with what == 0) that might crop up.
+                    // For example, we may have recycled a message sent to multiple handlers.
+                    Log.wtf(TAG, "Error! empty message encountered");
+                    break;
                 default:
                     loge("Error! unhandled message" + message);
                     break;
@@ -4013,7 +4147,13 @@
             logStateAndMessage(message, this);
             switch (message.what) {
                 case CMD_START_SUPPLICANT:
-                    mClientInterface = mWifiNative.setupForClientMode();
+                    Pair<Integer, IClientInterface> statusAndInterface =
+                            mWifiNative.setupForClientMode();
+                    if (statusAndInterface.first == WifiNative.SETUP_SUCCESS) {
+                        mClientInterface = statusAndInterface.second;
+                    } else {
+                        incrementMetricsForSetupFailure(statusAndInterface.first);
+                    }
                     if (mClientInterface == null
                             || !mDeathRecipient.linkToDeath(mClientInterface.asBinder())) {
                         setWifiState(WifiManager.WIFI_STATE_UNKNOWN);
@@ -4050,6 +4190,7 @@
                     }
                     if (mVerboseLoggingEnabled) log("Supplicant start successful");
                     mWifiMonitor.startMonitoring(mInterfaceName, true);
+                    mWifiInjector.getWifiLastResortWatchdog().clearAllFailureCounts();
                     setSupplicantLogLevel();
                     transitionTo(mSupplicantStartingState);
                     break;
@@ -4191,6 +4332,18 @@
              * driver are changed to reduce interference with bluetooth
              */
             mWifiNative.setBluetoothCoexistenceScanMode(mBluetoothConnectionActive);
+            // Check if there is a voice call on-going and set/reset the tx power limit
+            // appropriately.
+            if (mEnableVoiceCallSarTxPowerLimit) {
+                if (getTelephonyManager().isOffhook()) {
+                    sendMessage(CMD_SELECT_TX_POWER_SCENARIO,
+                            WifiNative.TX_POWER_SCENARIO_VOICE_CALL);
+                } else {
+                    sendMessage(CMD_SELECT_TX_POWER_SCENARIO,
+                            WifiNative.TX_POWER_SCENARIO_NORMAL);
+                }
+            }
+
             // initialize network state
             setNetworkDetailedState(DetailedState.DISCONNECTED);
 
@@ -4304,7 +4457,7 @@
                     break;
                 case CMD_RESET_SIM_NETWORKS:
                     log("resetting EAP-SIM/AKA/AKA' networks since SIM was changed");
-                    mWifiConfigManager.resetSimNetworks();
+                    mWifiConfigManager.resetSimNetworks(message.arg1 == 1);
                     break;
                 case CMD_BLUETOOTH_ADAPTER_STATE_CHANGE:
                     mBluetoothConnectionActive = (message.arg1 !=
@@ -4372,6 +4525,13 @@
                         mWifiConnectivityManager.forceConnectivityScan();
                     }
                     break;
+                case CMD_SELECT_TX_POWER_SCENARIO:
+                    int txPowerScenario = message.arg1;
+                    logd("Setting Tx power scenario to " + txPowerScenario);
+                    if (!mWifiNative.selectTxPowerScenario(txPowerScenario)) {
+                        loge("Failed to set TX power scenario");
+                    }
+                    break;
                 default:
                     return NOT_HANDLED;
             }
@@ -4646,22 +4806,20 @@
             mWifiConfigManager.updateNetworkAfterConnect(mLastNetworkId);
             // On connect, reset wifiScoreReport
             mWifiScoreReport.reset();
+
+            // Notify PasspointManager of Passpoint network connected event.
+            WifiConfiguration currentNetwork = getCurrentWifiConfiguration();
+            if (currentNetwork.isPasspoint()) {
+                mPasspointManager.onPasspointNetworkConnected(currentNetwork.FQDN);
+            }
        }
     }
 
     void registerDisconnected() {
         if (mLastNetworkId != WifiConfiguration.INVALID_NETWORK_ID) {
             mWifiConfigManager.updateNetworkAfterDisconnect(mLastNetworkId);
-            // We are switching away from this configuration,
-            // hence record the time we were connected last
-            WifiConfiguration config = mWifiConfigManager.getConfiguredNetwork(mLastNetworkId);
-            if (config != null) {
-                // Remove WifiConfiguration for ephemeral or Passpoint networks, since they're
-                // temporary networks.
-                if (config.ephemeral || config.isPasspoint()) {
-                    mWifiConfigManager.removeNetwork(mLastNetworkId, Process.WIFI_UID);
-                }
-            }
+            // Let's remove any ephemeral or passpoint networks on every disconnect.
+            mWifiConfigManager.removeAllEphemeralOrPasspointConfiguredNetworks();
         }
     }
 
@@ -4796,9 +4954,21 @@
                     mWifiDiagnostics.captureBugReportData(
                             WifiDiagnostics.REPORT_REASON_AUTH_FAILURE);
                     mSupplicantStateTracker.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT);
-                    mWifiConfigManager.updateNetworkSelectionStatus(mTargetNetworkId,
-                            WifiConfiguration.NetworkSelectionStatus
-                                    .DISABLED_AUTHENTICATION_FAILURE);
+                    int disableReason = WifiConfiguration.NetworkSelectionStatus
+                            .DISABLED_AUTHENTICATION_FAILURE;
+                    // Check if this is a permanent wrong password failure.
+                    if (isPermanentWrongPasswordFailure(mTargetNetworkId, message.arg2)) {
+                        disableReason = WifiConfiguration.NetworkSelectionStatus
+                                .DISABLED_BY_WRONG_PASSWORD;
+                        WifiConfiguration targetedNetwork =
+                                mWifiConfigManager.getConfiguredNetwork(mTargetNetworkId);
+                        if (targetedNetwork != null) {
+                            mWrongPasswordNotifier.onWrongPasswordError(
+                                    targetedNetwork.SSID);
+                        }
+                    }
+                    mWifiConfigManager.updateNetworkSelectionStatus(
+                            mTargetNetworkId, disableReason);
                     //If failure occurred while Metrics is tracking a ConnnectionEvent, end it.
                     reportConnectionAttemptEnd(
                             WifiMetrics.ConnectionEvent.FAILURE_AUTHENTICATION_FAILURE,
@@ -4967,6 +5137,10 @@
                     replyToMessage(message, message.what,
                             mPasspointManager.getMatchingWifiConfig((ScanResult) message.obj));
                     break;
+                case CMD_GET_MATCHING_OSU_PROVIDERS:
+                    replyToMessage(message, message.what,
+                            mPasspointManager.getMatchingOsuProviders((ScanResult) message.obj));
+                    break;
                 case CMD_RECONNECT:
                     mWifiConnectivityManager.forceConnectivityScan();
                     break;
@@ -5226,6 +5400,15 @@
                     if (config != null) {
                         mWifiInfo.setBSSID(mLastBssid);
                         mWifiInfo.setNetworkId(mLastNetworkId);
+
+                        ScanDetailCache scanDetailCache =
+                                mWifiConfigManager.getScanDetailCacheForNetwork(config.networkId);
+                        if (scanDetailCache != null && mLastBssid != null) {
+                            ScanResult scanResult = scanDetailCache.get(mLastBssid);
+                            if (scanResult != null) {
+                                mWifiInfo.setFrequency(scanResult.frequency);
+                            }
+                        }
                         mWifiConnectivityManager.trackBssid(mLastBssid, true, reasonCode);
                         // We need to get the updated pseudonym from supplicant for EAP-SIM/AKA/AKA'
                         if (config.enterpriseConfig != null
@@ -5704,8 +5887,8 @@
                                     mWifiInfo, mNetworkAgent, mAggressiveHandover,
                                     mWifiMetrics);
                         }
-                        sendMessageDelayed(obtainMessage(CMD_RSSI_POLL,
-                                mRssiPollToken, 0), POLL_RSSI_INTERVAL_MSECS);
+                        sendMessageDelayed(obtainMessage(CMD_RSSI_POLL, mRssiPollToken, 0),
+                                mPollRssiIntervalMsecs);
                         if (mVerboseLoggingEnabled) sendRssiChangeBroadcast(mWifiInfo.getRssi());
                     } else {
                         // Polling has completed
@@ -5722,8 +5905,8 @@
                     if (mEnableRssiPolling) {
                         // First poll
                         fetchRssiLinkSpeedAndFrequencyNative();
-                        sendMessageDelayed(obtainMessage(CMD_RSSI_POLL,
-                                mRssiPollToken, 0), POLL_RSSI_INTERVAL_MSECS);
+                        sendMessageDelayed(obtainMessage(CMD_RSSI_POLL, mRssiPollToken, 0),
+                                mPollRssiIntervalMsecs);
                     }
                     break;
                 case WifiManager.RSSI_PKTCNT_FETCH:
@@ -5768,7 +5951,18 @@
                     mLastBssid = (String) message.obj;
                     if (mLastBssid != null && (mWifiInfo.getBSSID() == null
                             || !mLastBssid.equals(mWifiInfo.getBSSID()))) {
-                        mWifiInfo.setBSSID((String) message.obj);
+                        mWifiInfo.setBSSID(mLastBssid);
+                        WifiConfiguration config = getCurrentWifiConfiguration();
+                        if (config != null) {
+                            ScanDetailCache scanDetailCache = mWifiConfigManager
+                                    .getScanDetailCacheForNetwork(config.networkId);
+                            if (scanDetailCache != null) {
+                                ScanResult scanResult = scanDetailCache.get(mLastBssid);
+                                if (scanResult != null) {
+                                    mWifiInfo.setFrequency(scanResult.frequency);
+                                }
+                            }
+                        }
                         sendNetworkStateChangeBroadcast(mLastBssid);
                     }
                     break;
@@ -5899,13 +6093,29 @@
         }
     }
 
+    /**
+     * Helper function to check if we need to invoke
+     * {@link NetworkAgent#explicitlySelected(boolean)} to indicate that we connected to a network
+     * which the user just chose
+     * (i.e less than {@link #LAST_SELECTED_NETWORK_EXPIRATION_AGE_MILLIS) before).
+     */
+    @VisibleForTesting
+    public boolean shouldEvaluateWhetherToSendExplicitlySelected(WifiConfiguration currentConfig) {
+        if (currentConfig == null) {
+            Log.wtf(TAG, "Current WifiConfiguration is null, but IP provisioning just succeeded");
+            return false;
+        }
+        long currentTimeMillis = mClock.getElapsedSinceBootMillis();
+        return (mWifiConfigManager.getLastSelectedNetwork() == currentConfig.networkId
+                && currentTimeMillis - mWifiConfigManager.getLastSelectedTimeStamp()
+                < LAST_SELECTED_NETWORK_EXPIRATION_AGE_MILLIS);
+    }
+
     private void sendConnectedState() {
         // If this network was explicitly selected by the user, evaluate whether to call
         // explicitlySelected() so the system can treat it appropriately.
         WifiConfiguration config = getCurrentWifiConfiguration();
-        if (config == null) {
-            Log.wtf(TAG, "Current WifiConfiguration is null, but IP provisioning just succeeded");
-        } else if (mWifiConfigManager.getLastSelectedNetwork() == config.networkId) {
+        if (shouldEvaluateWhetherToSendExplicitlySelected(config)) {
             boolean prompt =
                     mWifiPermissionsUtil.checkConfigOverridePermission(config.lastConnectUid);
             if (mVerboseLoggingEnabled) {
@@ -6081,7 +6291,9 @@
     class ConnectedState extends State {
         @Override
         public void enter() {
-            updateDefaultRouteMacAddress(1000);
+            // TODO: b/64349637 Investigate getting default router IP/MAC address info from
+            // IpManager
+            //updateDefaultRouteMacAddress(1000);
             if (mVerboseLoggingEnabled) {
                 log("Enter ConnectedState "
                        + " mScreenOn=" + mScreenOn);
@@ -6699,7 +6911,13 @@
             SoftApModeConfiguration config = (SoftApModeConfiguration) message.obj;
             mMode = config.getTargetMode();
 
-            IApInterface apInterface = mWifiNative.setupForSoftApMode();
+            IApInterface apInterface = null;
+            Pair<Integer, IApInterface> statusAndInterface = mWifiNative.setupForSoftApMode();
+            if (statusAndInterface.first == WifiNative.SETUP_SUCCESS) {
+                apInterface = statusAndInterface.second;
+            } else {
+                incrementMetricsForSetupFailure(statusAndInterface.first);
+            }
             if (apInterface == null) {
                 setWifiApState(WIFI_AP_STATE_FAILED,
                         WifiManager.SAP_START_FAILURE_GENERAL, null, mMode);
@@ -6912,6 +7130,7 @@
      */
     public void updateWifiMetrics() {
         mWifiMetrics.updateSavedNetworks(mWifiConfigManager.getSavedNetworks());
+        mPasspointManager.updateMetrics();
     }
 
     /**
diff --git a/service/java/com/android/server/wifi/WifiVendorHal.java b/service/java/com/android/server/wifi/WifiVendorHal.java
index 88f1898..12674aa 100644
--- a/service/java/com/android/server/wifi/WifiVendorHal.java
+++ b/service/java/com/android/server/wifi/WifiVendorHal.java
@@ -778,9 +778,41 @@
     }
 
     /**
+     * Translation table used by getSupportedFeatureSet for translating IWifiChip caps
+     */
+    private static final int[][] sChipFeatureCapabilityTranslation = {
+            {WifiManager.WIFI_FEATURE_TX_POWER_LIMIT,
+                    android.hardware.wifi.V1_1.IWifiChip.ChipCapabilityMask.SET_TX_POWER_LIMIT
+            },
+            {WifiManager.WIFI_FEATURE_D2D_RTT,
+                    android.hardware.wifi.V1_1.IWifiChip.ChipCapabilityMask.D2D_RTT
+            },
+            {WifiManager.WIFI_FEATURE_D2AP_RTT,
+                    android.hardware.wifi.V1_1.IWifiChip.ChipCapabilityMask.D2AP_RTT
+            }
+    };
+
+    /**
+     * Feature bit mask translation for Chip
+     *
+     * @param capabilities bitmask defined IWifiChip.ChipCapabilityMask
+     * @return bitmask defined by WifiManager.WIFI_FEATURE_*
+     */
+    @VisibleForTesting
+    int wifiFeatureMaskFromChipCapabilities(int capabilities) {
+        int features = 0;
+        for (int i = 0; i < sChipFeatureCapabilityTranslation.length; i++) {
+            if ((capabilities & sChipFeatureCapabilityTranslation[i][1]) != 0) {
+                features |= sChipFeatureCapabilityTranslation[i][0];
+            }
+        }
+        return features;
+    }
+
+    /**
      * Translation table used by getSupportedFeatureSet for translating IWifiStaIface caps
      */
-    private static final int[][] sFeatureCapabilityTranslation = {
+    private static final int[][] sStaFeatureCapabilityTranslation = {
             {WifiManager.WIFI_FEATURE_INFRA_5G,
                     IWifiStaIface.StaIfaceCapabilityMask.STA_5G
             },
@@ -831,9 +863,9 @@
     @VisibleForTesting
     int wifiFeatureMaskFromStaCapabilities(int capabilities) {
         int features = 0;
-        for (int i = 0; i < sFeatureCapabilityTranslation.length; i++) {
-            if ((capabilities & sFeatureCapabilityTranslation[i][1]) != 0) {
-                features |= sFeatureCapabilityTranslation[i][0];
+        for (int i = 0; i < sStaFeatureCapabilityTranslation.length; i++) {
+            if ((capabilities & sStaFeatureCapabilityTranslation[i][1]) != 0) {
+                features |= sStaFeatureCapabilityTranslation[i][0];
             }
         }
         return features;
@@ -848,13 +880,22 @@
      */
     public int getSupportedFeatureSet() {
         int featureSet = 0;
+        if (!mHalDeviceManager.isStarted()) {
+            return featureSet; // TODO: can't get capabilities with Wi-Fi down
+        }
         try {
             final MutableInt feat = new MutableInt(0);
             synchronized (sLock) {
+                if (mIWifiChip != null) {
+                    mIWifiChip.getCapabilities((status, capabilities) -> {
+                        if (!ok(status)) return;
+                        feat.value = wifiFeatureMaskFromChipCapabilities(capabilities);
+                    });
+                }
                 if (mIWifiStaIface != null) {
                     mIWifiStaIface.getCapabilities((status, capabilities) -> {
                         if (!ok(status)) return;
-                        feat.value = wifiFeatureMaskFromStaCapabilities(capabilities);
+                        feat.value |= wifiFeatureMaskFromStaCapabilities(capabilities);
                     });
                 }
             }
@@ -2318,6 +2359,62 @@
         }
     }
 
+    /**
+     * Method to mock out the V1_1 IWifiChip retrieval in unit tests.
+     *
+     * @return 1.1 IWifiChip object if the device is running the 1.1 wifi hal service, null
+     * otherwise.
+     */
+    protected android.hardware.wifi.V1_1.IWifiChip getWifiChipForV1_1Mockable() {
+        if (mIWifiChip == null) return null;
+        return android.hardware.wifi.V1_1.IWifiChip.castFrom(mIWifiChip);
+    }
+
+    private int frameworkToHalTxPowerScenario(int scenario) {
+        switch (scenario) {
+            case WifiNative.TX_POWER_SCENARIO_VOICE_CALL:
+                return android.hardware.wifi.V1_1.IWifiChip.TxPowerScenario.VOICE_CALL;
+            default:
+                throw new IllegalArgumentException("bad scenario: " + scenario);
+        }
+    }
+
+    /**
+     * Select one of the pre-configured TX power level scenarios or reset it back to normal.
+     * Primarily used for meeting SAR requirements during voice calls.
+     *
+     * @param scenario Should be one {@link WifiNative#TX_POWER_SCENARIO_NORMAL} or
+     *        {@link WifiNative#TX_POWER_SCENARIO_VOICE_CALL}.
+     * @return true for success; false for failure or if the HAL version does not support this API.
+     */
+    public boolean selectTxPowerScenario(int scenario) {
+        synchronized (sLock) {
+            try {
+                android.hardware.wifi.V1_1.IWifiChip iWifiChipV11 = getWifiChipForV1_1Mockable();
+                if (iWifiChipV11 == null) return boolResult(false);
+                WifiStatus status;
+                if (scenario != WifiNative.TX_POWER_SCENARIO_NORMAL) {
+                    int halScenario;
+                    try {
+                        halScenario = frameworkToHalTxPowerScenario(scenario);
+                    } catch (IllegalArgumentException e) {
+                        mLog.err("Illegal argument for select tx power scenario")
+                                .c(e.toString()).flush();
+                        return false;
+                    }
+                    status = iWifiChipV11.selectTxPowerScenario(halScenario);
+                } else {
+                    status = iWifiChipV11.resetTxPowerScenario();
+                }
+                if (!ok(status)) return false;
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return false;
+            }
+            return true;
+        }
+    }
+
     // This creates a blob of IE elements from the array received.
     // TODO: This ugly conversion can be removed if we put IE elements in ScanResult.
     private static byte[] hidlIeArrayToFrameworkIeBlob(ArrayList<WifiInformationElement> ies) {
diff --git a/service/java/com/android/server/wifi/WrongPasswordNotifier.java b/service/java/com/android/server/wifi/WrongPasswordNotifier.java
new file mode 100644
index 0000000..37e23da
--- /dev/null
+++ b/service/java/com/android/server/wifi/WrongPasswordNotifier.java
@@ -0,0 +1,110 @@
+/*
+ * 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.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Settings;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+import com.android.internal.notification.SystemNotificationChannels;
+import com.android.server.wifi.util.NativeUtil;
+
+/**
+ * Responsible for notifying user for wrong password errors.
+ */
+public class WrongPasswordNotifier {
+    // Number of milliseconds to wait before automatically dismiss the notification.
+    private static final long CANCEL_TIMEOUT_MILLISECONDS = 5 * 60 * 1000;
+
+    // Unique ID associated with the notification.
+    @VisibleForTesting
+    public static final int NOTIFICATION_ID = SystemMessage.NOTE_WIFI_WRONG_PASSWORD;
+
+    // Flag indicating if a wrong password error is detected for the current connection.
+    private boolean mWrongPasswordDetected;
+
+    private final Context mContext;
+    private final NotificationManager mNotificationManager;
+    private final FrameworkFacade mFrameworkFacade;
+
+    public WrongPasswordNotifier(Context context, FrameworkFacade frameworkFacade) {
+        mContext = context;
+        mFrameworkFacade = frameworkFacade;
+        mNotificationManager =
+                (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+    }
+
+    /**
+     * Invoked when a wrong password error for a Wi-Fi network is detected.
+     *
+     * @param ssid The SSID of the Wi-Fi network
+     */
+    public void onWrongPasswordError(String ssid) {
+        showNotification(ssid);
+        mWrongPasswordDetected = true;
+    }
+
+    /**
+     * Invoked when attempting a new Wi-Fi network connection.
+     */
+    public void onNewConnectionAttempt() {
+        if (mWrongPasswordDetected) {
+            dismissNotification();
+            mWrongPasswordDetected = false;
+        }
+    }
+
+    /**
+     * Display wrong password notification for a given Wi-Fi network (specified by its SSID).
+     *
+     * @param ssid SSID of the Wi-FI network
+     */
+    private void showNotification(String ssid) {
+        Intent intent = new Intent(Settings.ACTION_WIFI_SETTINGS);
+        intent.putExtra("wifi_start_connect_ssid", NativeUtil.removeEnclosingQuotes(ssid));
+        Notification.Builder builder = mFrameworkFacade.makeNotificationBuilder(mContext,
+                SystemNotificationChannels.NETWORK_ALERTS)
+                .setAutoCancel(true)
+                .setTimeoutAfter(CANCEL_TIMEOUT_MILLISECONDS)
+                // TODO(zqiu): consider creating a new icon.
+                .setSmallIcon(com.android.internal.R.drawable.stat_notify_wifi_in_range)
+                .setContentTitle(mContext.getString(
+                        com.android.internal.R.string.wifi_available_title_failed_to_connect))
+                .setContentText(ssid)
+                .setContentIntent(mFrameworkFacade.getActivity(
+                        mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT))
+                .setColor(mContext.getResources().getColor(
+                        com.android.internal.R.color.system_notification_accent_color));
+        mNotificationManager.notify(NOTIFICATION_ID, builder.build());
+    }
+
+    /**
+     * Dismiss the notification that was generated by {@link #showNotification}. The notification
+     * might have already been dismissed, either by user or timeout. We'll attempt to dismiss it
+     * regardless if it is been dismissed or not, to reduce code complexity.
+     */
+    private void dismissNotification() {
+        // Notification might have already been dismissed, either by user or timeout. It is
+        // still okay to cancel it if already dismissed.
+        mNotificationManager.cancel(null, NOTIFICATION_ID);
+    }
+}
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareClientState.java b/service/java/com/android/server/wifi/aware/WifiAwareClientState.java
index 7123d01..3570f1d 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareClientState.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareClientState.java
@@ -61,14 +61,15 @@
     private final String mCallingPackage;
     private final boolean mNotifyIdentityChange;
 
-    private AppOpsManager mAppOps;
+    private final AppOpsManager mAppOps;
+    private final long mCreationTime;
 
     private static final byte[] ALL_ZERO_MAC = new byte[] {0, 0, 0, 0, 0, 0};
     private byte[] mLastDiscoveryInterfaceMac = ALL_ZERO_MAC;
 
     public WifiAwareClientState(Context context, int clientId, int uid, int pid,
             String callingPackage, IWifiAwareEventCallback callback, ConfigRequest configRequest,
-            boolean notifyIdentityChange) {
+            boolean notifyIdentityChange, long creationTime) {
         mContext = context;
         mClientId = clientId;
         mUid = uid;
@@ -79,6 +80,7 @@
         mNotifyIdentityChange = notifyIdentityChange;
 
         mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+        mCreationTime = creationTime;
     }
 
     /**
@@ -109,6 +111,14 @@
         return mNotifyIdentityChange;
     }
 
+    public long getCreationTime() {
+        return mCreationTime;
+    }
+
+    public SparseArray<WifiAwareDiscoverySessionState> getSessions() {
+        return mSessions;
+    }
+
     /**
      * Searches the discovery sessions of this client and returns the one
      * corresponding to the publish/subscribe ID. Used on callbacks from HAL to
@@ -164,15 +174,17 @@
      *
      * @param sessionId The session ID of the session to be destroyed.
      */
-    public void terminateSession(int sessionId) {
+    public WifiAwareDiscoverySessionState terminateSession(int sessionId) {
         WifiAwareDiscoverySessionState session = mSessions.get(sessionId);
         if (session == null) {
             Log.e(TAG, "terminateSession: sessionId doesn't exist - " + sessionId);
-            return;
+            return null;
         }
 
         session.terminate();
         mSessions.delete(sessionId);
+
+        return session;
     }
 
     /**
@@ -240,7 +252,8 @@
             try {
                 boolean hasPermission = hasLocationingPermission();
                 if (VDBG) Log.v(TAG, "hasPermission=" + hasPermission);
-                mCallback.onIdentityChanged(hasPermission ? mac : ALL_ZERO_MAC);
+                mCallback.onIdentityChanged(
+                        hasPermission ? currentDiscoveryInterfaceMac : ALL_ZERO_MAC);
             } catch (RemoteException e) {
                 Log.w(TAG, "onIdentityChanged: RemoteException - ignored: " + e);
             }
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java b/service/java/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java
index 723828d..04bf2e0 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java
@@ -16,8 +16,11 @@
 
 package com.android.server.wifi.aware;
 
+import android.Manifest;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.hardware.wifi.V1_0.NanDataPathChannelCfg;
+import android.hardware.wifi.V1_0.NanStatusType;
 import android.net.ConnectivityManager;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
@@ -32,14 +35,18 @@
 import android.net.RouteInfo;
 import android.net.wifi.aware.WifiAwareManager;
 import android.net.wifi.aware.WifiAwareNetworkSpecifier;
+import android.net.wifi.aware.WifiAwareUtils;
 import android.os.IBinder;
 import android.os.INetworkManagementService;
 import android.os.Looper;
 import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wifi.util.WifiPermissionsWrapper;
 
 import libcore.util.HexEncoding;
 
@@ -84,6 +91,8 @@
     private final Map<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation>
             mNetworkRequestsCache = new ArrayMap<>();
     private Context mContext;
+    private WifiAwareMetrics mAwareMetrics;
+    private WifiPermissionsWrapper mPermissionsWrapper;
     private Looper mLooper;
     private WifiAwareNetworkFactory mNetworkFactory;
     private INetworkManagementService mNwService;
@@ -96,10 +105,13 @@
      * Initialize the Aware data-path state manager. Specifically register the network factory with
      * connectivity service.
      */
-    public void start(Context context, Looper looper) {
+    public void start(Context context, Looper looper, WifiAwareMetrics awareMetrics,
+            WifiPermissionsWrapper permissionsWrapper) {
         if (VDBG) Log.v(TAG, "start");
 
         mContext = context;
+        mAwareMetrics = awareMetrics;
+        mPermissionsWrapper = permissionsWrapper;
         mLooper = looper;
 
         mNetworkCapabilitiesFilter.clearAll();
@@ -167,7 +179,13 @@
     public void deleteAllInterfaces() {
         if (VDBG) Log.v(TAG, "deleteAllInterfaces");
 
-        for (String name : mInterfaces) {
+        if (mMgr.getCapabilities() == null) {
+            Log.e(TAG, "deleteAllInterfaces: capabilities aren't initialized yet!");
+            return;
+        }
+
+        for (int i = 0; i < mMgr.getCapabilities().maxNdiInterfaces; ++i) {
+            String name = AWARE_INTERFACE_PREFIX + i;
             mMgr.deleteDataPathInterface(name);
         }
     }
@@ -260,6 +278,7 @@
         }
 
         mNetworkRequestsCache.remove(networkSpecifier);
+        mAwareMetrics.recordNdpStatus(reason, networkSpecifier.isOutOfBand(), nnri.startTimestamp);
     }
 
 
@@ -311,23 +330,32 @@
             if (DBG) {
                 Log.d(TAG, "onDataPathRequest: network request cache = " + mNetworkRequestsCache);
             }
-            mMgr.respondToDataPathRequest(false, ndpId, "", null, null);
+            mMgr.respondToDataPathRequest(false, ndpId, "", null, null, false);
             return null;
         }
 
         if (nnri.state != AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_REQUEST) {
             Log.w(TAG, "onDataPathRequest: request " + networkSpecifier + " is incorrect state="
                     + nnri.state);
-            mMgr.respondToDataPathRequest(false, ndpId, "", null, null);
+            mMgr.respondToDataPathRequest(false, ndpId, "", null, null, false);
+            mNetworkRequestsCache.remove(networkSpecifier);
+            return null;
+        }
+
+        nnri.interfaceName = selectInterfaceForRequest(nnri);
+        if (nnri.interfaceName == null) {
+            Log.w(TAG,
+                    "onDataPathRequest: request " + networkSpecifier + " no interface available");
+            mMgr.respondToDataPathRequest(false, ndpId, "", null, null, false);
             mNetworkRequestsCache.remove(networkSpecifier);
             return null;
         }
 
         nnri.state = AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_RESPOND_RESPONSE;
         nnri.ndpId = ndpId;
-        nnri.interfaceName = selectInterfaceForRequest(nnri);
+        nnri.startTimestamp = SystemClock.elapsedRealtime();
         mMgr.respondToDataPathRequest(true, ndpId, nnri.interfaceName, nnri.networkSpecifier.pmk,
-                nnri.networkSpecifier.passphrase);
+                nnri.networkSpecifier.passphrase, nnri.networkSpecifier.isOutOfBand());
 
         return networkSpecifier;
     }
@@ -338,7 +366,7 @@
      * @param ndpId The ID of the data-path (NDP)
      * @param success Whether or not the 'RespondToDataPathRequest' operation was a success.
      */
-    public void onRespondToDataPathRequest(int ndpId, boolean success) {
+    public void onRespondToDataPathRequest(int ndpId, boolean success, int reasonOnFailure) {
         if (VDBG) {
             Log.v(TAG, "onRespondToDataPathRequest: ndpId=" + ndpId + ", success=" + success);
         }
@@ -369,6 +397,8 @@
                     + " failed responding");
             mMgr.endDataPath(ndpId);
             mNetworkRequestsCache.remove(networkSpecifier);
+            mAwareMetrics.recordNdpStatus(reasonOnFailure, networkSpecifier.isOutOfBand(),
+                    nnri.startTimestamp);
             return;
         }
 
@@ -475,12 +505,19 @@
                     networkCapabilities, linkProperties, NETWORK_FACTORY_SCORE_AVAIL,
                     networkSpecifier, ndpId);
             nnri.networkAgent.sendNetworkInfo(networkInfo);
+
+            mAwareMetrics.recordNdpStatus(NanStatusType.SUCCESS, networkSpecifier.isOutOfBand(),
+                    nnri.startTimestamp);
+            nnri.startTimestamp = SystemClock.elapsedRealtime(); // update time-stamp for duration
+            mAwareMetrics.recordNdpCreation(nnri.uid, mNetworkRequestsCache);
         } else {
             if (DBG) {
                 Log.d(TAG, "onDataPathConfirm: data-path for networkSpecifier=" + networkSpecifier
                         + " rejected - reason=" + reason);
             }
             mNetworkRequestsCache.remove(networkSpecifier);
+            mAwareMetrics.recordNdpStatus(reason, networkSpecifier.isOutOfBand(),
+                    nnri.startTimestamp);
         }
 
         return networkSpecifier;
@@ -505,6 +542,11 @@
         }
 
         tearDownInterface(nnriE.getValue());
+        if (nnriE.getValue().state == AwareNetworkRequestInformation.STATE_RESPONDER_CONFIRMED
+                || nnriE.getValue().state
+                == AwareNetworkRequestInformation.STATE_INITIATOR_CONFIRMED) {
+            mAwareMetrics.recordNdpSessionDuration(nnriE.getValue().startTimestamp);
+        }
         mNetworkRequestsCache.remove(nnriE.getKey());
     }
 
@@ -538,6 +580,8 @@
             }
             return;
         }
+        mAwareMetrics.recordNdpStatus(NanStatusType.INTERNAL_FAILURE,
+                nnri.networkSpecifier.isOutOfBand(), nnri.startTimestamp);
 
         mMgr.endDataPath(nnri.ndpId);
     }
@@ -592,12 +636,21 @@
                 return true;
             }
 
-            nnri = AwareNetworkRequestInformation.processNetworkSpecifier(networkSpecifier, mMgr);
+            nnri = AwareNetworkRequestInformation.processNetworkSpecifier(networkSpecifier, mMgr,
+                    mPermissionsWrapper);
             if (nnri == null) {
                 Log.e(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request
                         + " - can't parse network specifier");
                 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).");
+                return false;
+            }
+
             mNetworkRequestsCache.put(networkSpecifier, nnri);
 
             return true;
@@ -636,12 +689,20 @@
                 }
 
                 nnri.interfaceName = selectInterfaceForRequest(nnri);
-                mMgr.initiateDataPathSetup(networkSpecifier, nnri.networkSpecifier.peerId,
-                        NanDataPathChannelCfg.REQUEST_CHANNEL_SETUP, selectChannelForRequest(nnri),
+                if (nnri.interfaceName == null) {
+                    Log.w(TAG, "needNetworkFor: request " + networkSpecifier
+                            + " no interface available");
+                    mNetworkRequestsCache.remove(networkSpecifier);
+                    return;
+                }
+
+                mMgr.initiateDataPathSetup(networkSpecifier, nnri.peerInstanceId,
+                        NanDataPathChannelCfg.CHANNEL_NOT_REQUESTED, selectChannelForRequest(nnri),
                         nnri.peerDiscoveryMac, nnri.interfaceName, nnri.networkSpecifier.pmk,
-                        nnri.networkSpecifier.passphrase);
+                        nnri.networkSpecifier.passphrase, nnri.networkSpecifier.isOutOfBand());
                 nnri.state =
                         AwareNetworkRequestInformation.STATE_INITIATOR_WAIT_FOR_REQUEST_RESPONSE;
+                nnri.startTimestamp = SystemClock.elapsedRealtime();
             } else {
                 if (nnri.state != AwareNetworkRequestInformation.STATE_RESPONDER_IDLE) {
                     if (DBG) {
@@ -773,13 +834,14 @@
 
         Log.e(TAG, "selectInterfaceForRequest: req=" + req + " - but no interfaces available!");
 
-        return "";
+        return null;
     }
 
     /**
      * Select a channel for the network request.
      *
-     * TODO: for now simply select channel 6
+     * TODO (b/38209409): The value from this function isn't currently used - the channel selection
+     * is delegated to the HAL.
      */
     private int selectChannelForRequest(AwareNetworkRequestInformation req) {
         return 2437;
@@ -807,16 +869,19 @@
         public int uid;
         public String interfaceName;
         public int pubSubId = 0;
+        public int peerInstanceId = 0;
         public byte[] peerDiscoveryMac = null;
         public int ndpId;
         public byte[] peerDataMac;
         public WifiAwareNetworkSpecifier networkSpecifier;
+        public long startTimestamp = 0; // request is made (initiator) / get request (responder)
 
         public WifiAwareNetworkAgent networkAgent;
 
         static AwareNetworkRequestInformation processNetworkSpecifier(WifiAwareNetworkSpecifier ns,
-                WifiAwareStateManager mgr) {
+                WifiAwareStateManager mgr, WifiPermissionsWrapper permissionWrapper) {
             int uid, pubSubId = 0;
+            int peerInstanceId = 0;
             byte[] peerMac = ns.peerMac;
 
             if (VDBG) {
@@ -879,15 +944,17 @@
 
                 if (ns.type == WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB) {
                     pubSubId = session.getPubSubId();
-                    String peerMacStr = session.getMac(ns.peerId, null);
-                    if (peerMacStr == null) {
+                    WifiAwareDiscoverySessionState.PeerInfo peerInfo = session.getPeerInfo(
+                            ns.peerId);
+                    if (peerInfo == null) {
                         Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns
-                                + " -- no MAC address associated with this peer id -- peerId="
+                                + " -- no peer info associated with this peer id -- peerId="
                                 + ns.peerId);
                         return null;
                     }
+                    peerInstanceId = peerInfo.mInstanceId;
                     try {
-                        peerMac = HexEncoding.decode(peerMacStr.toCharArray(), false);
+                        peerMac = peerInfo.mMac;
                         if (peerMac == null || peerMac.length != 6) {
                             Log.e(TAG, "processNetworkSpecifier: networkSpecifier="
                                     + ns + " -- invalid peer MAC address");
@@ -908,6 +975,30 @@
                 return null;
             }
 
+            // validate permission if PMK is used (SystemApi)
+            if (ns.pmk != null && ns.pmk.length != 0) {
+                if (permissionWrapper.getUidPermission(Manifest.permission.CONNECTIVITY_INTERNAL,
+                        ns.requestorUid) != PackageManager.PERMISSION_GRANTED) {
+                    Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns.toString()
+                            + " -- UID doesn't have permission to use PMK API");
+                    return null;
+                }
+            }
+
+            // validate passphrase & PMK (if provided)
+            if (!TextUtils.isEmpty(ns.passphrase)) { // non-null indicates usage
+                if (!WifiAwareUtils.validatePassphrase(ns.passphrase)) {
+                    Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns.toString()
+                            + " -- invalid passphrase length: " + ns.passphrase.length());
+                    return null;
+                }
+            }
+            if (ns.pmk != null && !WifiAwareUtils.validatePmk(ns.pmk)) { // non-null indicates usage
+                Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns.toString()
+                        + " -- invalid pmk length: " + ns.pmk.length);
+                return null;
+            }
+
             // create container and populate
             AwareNetworkRequestInformation nnri = new AwareNetworkRequestInformation();
             nnri.state = (ns.role == WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR)
@@ -915,6 +1006,7 @@
                     : AwareNetworkRequestInformation.STATE_RESPONDER_IDLE;
             nnri.uid = uid;
             nnri.pubSubId = pubSubId;
+            nnri.peerInstanceId = peerInstanceId;
             nnri.peerDiscoveryMac = peerMac;
             nnri.networkSpecifier = ns;
 
@@ -926,11 +1018,14 @@
             StringBuilder sb = new StringBuilder("AwareNetworkRequestInformation: ");
             sb.append("state=").append(state).append(", ns=").append(networkSpecifier).append(
                     ", uid=").append(uid).append(", interfaceName=").append(interfaceName).append(
-                    ", pubSubId=").append(pubSubId).append(", peerDiscoveryMac=").append(
+                    ", pubSubId=").append(pubSubId).append(", peerInstanceId=").append(
+                    peerInstanceId).append(", peerDiscoveryMac=").append(
                     peerDiscoveryMac == null ? ""
                             : String.valueOf(HexEncoding.encode(peerDiscoveryMac))).append(
                     ", ndpId=").append(ndpId).append(", peerDataMac=").append(
-                    peerDataMac == null ? "" : String.valueOf(HexEncoding.encode(peerDataMac)));
+                    peerDataMac == null ? ""
+                            : String.valueOf(HexEncoding.encode(peerDataMac))).append(
+                    ", startTimestamp=").append(startTimestamp);
             return sb.toString();
         }
     }
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareDiscoverySessionState.java b/service/java/com/android/server/wifi/aware/WifiAwareDiscoverySessionState.java
index d006aa8..86f4e37 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareDiscoverySessionState.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareDiscoverySessionState.java
@@ -28,6 +28,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.Arrays;
 
 /**
  * Manages the state of a single Aware discovery session (publish or subscribe).
@@ -40,21 +41,43 @@
     private static final boolean DBG = false;
     private static final boolean VDBG = false; // STOPSHIP if true
 
+    private int mNextPeerIdToBeAllocated = 100; // used to create a unique peer ID
+
     private final WifiAwareNativeApi mWifiAwareNativeApi;
     private int mSessionId;
-    private int mPubSubId;
+    private byte mPubSubId;
     private IWifiAwareDiscoverySessionCallback mCallback;
     private boolean mIsPublishSession;
+    private final long mCreationTime;
 
-    private final SparseArray<String> mMacByRequestorInstanceId = new SparseArray<>();
+    static class PeerInfo {
+        PeerInfo(int instanceId, byte[] mac) {
+            mInstanceId = instanceId;
+            mMac = mac;
+        }
+
+        int mInstanceId;
+        byte[] mMac;
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder("instanceId [");
+            sb.append(mInstanceId).append(", mac=").append(HexEncoding.encode(mMac)).append("]");
+            return sb.toString();
+        }
+    }
+
+    private final SparseArray<PeerInfo> mPeerInfoByRequestorInstanceId = new SparseArray<>();
 
     public WifiAwareDiscoverySessionState(WifiAwareNativeApi wifiAwareNativeApi, int sessionId,
-            int pubSubId, IWifiAwareDiscoverySessionCallback callback, boolean isPublishSession) {
+            byte pubSubId, IWifiAwareDiscoverySessionCallback callback, boolean isPublishSession,
+            long creationTime) {
         mWifiAwareNativeApi = wifiAwareNativeApi;
         mSessionId = sessionId;
         mPubSubId = pubSubId;
         mCallback = callback;
         mIsPublishSession = isPublishSession;
+        mCreationTime = creationTime;
     }
 
     public int getSessionId() {
@@ -69,21 +92,20 @@
         return mIsPublishSession;
     }
 
+    public long getCreationTime() {
+        return mCreationTime;
+    }
+
     public IWifiAwareDiscoverySessionCallback getCallback() {
         return mCallback;
     }
 
     /**
-     * Return the MAC address (String) of the specified peer ID - or a null if no such address is
+     * Return the peer information of the specified peer ID - or a null if no such peer ID is
      * registered.
      */
-    public String getMac(int peerId, String sep) {
-        String mac = mMacByRequestorInstanceId.get(peerId);
-        if (mac != null && sep != null && !sep.isEmpty()) {
-            mac = new StringBuilder(mac).insert(10, sep).insert(8, sep).insert(6, sep)
-                    .insert(4, sep).insert(2, sep).toString();
-        }
-        return mac;
+    public PeerInfo getPeerInfo(int peerId) {
+        return mPeerInfoByRequestorInstanceId.get(peerId);
     }
 
     /**
@@ -183,8 +205,8 @@
      *            callbacks related to the message (success/failure).
      */
     public boolean sendMessage(short transactionId, int peerId, byte[] message, int messageId) {
-        String peerMacStr = mMacByRequestorInstanceId.get(peerId);
-        if (peerMacStr == null) {
+        PeerInfo peerInfo = mPeerInfoByRequestorInstanceId.get(peerId);
+        if (peerInfo == null) {
             Log.e(TAG, "sendMessage: attempting to send a message to an address which didn't "
                     + "match/contact us");
             try {
@@ -194,10 +216,9 @@
             }
             return false;
         }
-        byte[] peerMac = HexEncoding.decode(peerMacStr.toCharArray(), false);
 
-        boolean success = mWifiAwareNativeApi.sendMessage(transactionId, mPubSubId, peerId, peerMac,
-                message, messageId);
+        boolean success = mWifiAwareNativeApi.sendMessage(transactionId, mPubSubId,
+                peerInfo.mInstanceId, peerInfo.mMac, message, messageId);
         if (!success) {
             try {
                 mCallback.onMessageSendFail(messageId, NanStatusType.INTERNAL_FAILURE);
@@ -226,13 +247,10 @@
      */
     public void onMatch(int requestorInstanceId, byte[] peerMac, byte[] serviceSpecificInfo,
             byte[] matchFilter) {
-        String prevMac = mMacByRequestorInstanceId.get(requestorInstanceId);
-        mMacByRequestorInstanceId.put(requestorInstanceId, new String(HexEncoding.encode(peerMac)));
-
-        if (DBG) Log.d(TAG, "onMatch: previous peer MAC replaced - " + prevMac);
+        int peerId = getPeerIdOrAddIfNew(requestorInstanceId, peerMac);
 
         try {
-            mCallback.onMatch(requestorInstanceId, serviceSpecificInfo, matchFilter);
+            mCallback.onMatch(peerId, serviceSpecificInfo, matchFilter);
         } catch (RemoteException e) {
             Log.w(TAG, "onMatch: RemoteException (FYI): " + e);
         }
@@ -249,20 +267,35 @@
      * @param message The received message.
      */
     public void onMessageReceived(int requestorInstanceId, byte[] peerMac, byte[] message) {
-        String prevMac = mMacByRequestorInstanceId.get(requestorInstanceId);
-        mMacByRequestorInstanceId.put(requestorInstanceId, new String(HexEncoding.encode(peerMac)));
-
-        if (DBG) {
-            Log.d(TAG, "onMessageReceived: previous peer MAC replaced - " + prevMac);
-        }
+        int peerId = getPeerIdOrAddIfNew(requestorInstanceId, peerMac);
 
         try {
-            mCallback.onMessageReceived(requestorInstanceId, message);
+            mCallback.onMessageReceived(peerId, message);
         } catch (RemoteException e) {
             Log.w(TAG, "onMessageReceived: RemoteException (FYI): " + e);
         }
     }
 
+    private int getPeerIdOrAddIfNew(int requestorInstanceId, byte[] peerMac) {
+        for (int i = 0; i < mPeerInfoByRequestorInstanceId.size(); ++i) {
+            PeerInfo peerInfo = mPeerInfoByRequestorInstanceId.valueAt(i);
+            if (peerInfo.mInstanceId == requestorInstanceId && Arrays.equals(peerMac,
+                    peerInfo.mMac)) {
+                return mPeerInfoByRequestorInstanceId.keyAt(i);
+            }
+        }
+
+        int newPeerId = mNextPeerIdToBeAllocated++;
+        PeerInfo newPeerInfo = new PeerInfo(requestorInstanceId, peerMac);
+        mPeerInfoByRequestorInstanceId.put(newPeerId, newPeerInfo);
+
+        if (DBG) {
+            Log.d(TAG, "New peer info: peerId=" + newPeerId + ", peerInfo=" + newPeerInfo);
+        }
+
+        return newPeerId;
+    }
+
     /**
      * Dump the internal state of the class.
      */
@@ -271,6 +304,6 @@
         pw.println("  mSessionId: " + mSessionId);
         pw.println("  mIsPublishSession: " + mIsPublishSession);
         pw.println("  mPubSubId: " + mPubSubId);
-        pw.println("  mMacByRequestorInstanceId: [" + mMacByRequestorInstanceId + "]");
+        pw.println("  mPeerInfoByRequestorInstanceId: [" + mPeerInfoByRequestorInstanceId + "]");
     }
 }
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareMetrics.java b/service/java/com/android/server/wifi/aware/WifiAwareMetrics.java
new file mode 100644
index 0000000..02eaf5d
--- /dev/null
+++ b/service/java/com/android/server/wifi/aware/WifiAwareMetrics.java
@@ -0,0 +1,829 @@
+/*
+ * 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.aware;
+
+import android.hardware.wifi.V1_0.NanStatusType;
+import android.net.wifi.aware.WifiAwareNetworkSpecifier;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wifi.Clock;
+import com.android.server.wifi.nano.WifiMetricsProto;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Wi-Fi Aware metric container/processor.
+ */
+public class WifiAwareMetrics {
+    private static final String TAG = "WifiAwareMetrics";
+    private static final boolean DBG = false;
+
+    // Histogram: 8 buckets (i=0, ..., 7) of 9 slots in range 10^i -> 10^(i+1)
+    // Buckets:
+    //    1 -> 10: 9 @ 1
+    //    10 -> 100: 9 @ 10
+    //    100 -> 1000: 9 @ 10^2
+    //    10^3 -> 10^4: 9 @ 10^3
+    //    10^4 -> 10^5: 9 @ 10^4
+    //    10^5 -> 10^6: 9 @ 10^5
+    //    10^6 -> 10^7: 9 @ 10^6
+    //    10^7 -> 10^8: 9 @ 10^7 --> 10^8 ms -> 10^5s -> 28 hours
+    private static final HistParms DURATION_LOG_HISTOGRAM = new HistParms(0, 1, 10, 9, 8);
+
+    private final Object mLock = new Object();
+    private final Clock mClock;
+
+    // enableUsage/disableUsage data
+    private long mLastEnableUsageMs = 0;
+    private long mLastEnableUsageInThisSampleWindowMs = 0;
+    private long mAvailableTimeMs = 0;
+    private SparseIntArray mHistogramAwareAvailableDurationMs = new SparseIntArray();
+
+    // enabled data
+    private long mLastEnableAwareMs = 0;
+    private long mLastEnableAwareInThisSampleWindowMs = 0;
+    private long mEnabledTimeMs = 0;
+    private SparseIntArray mHistogramAwareEnabledDurationMs = new SparseIntArray();
+
+    // attach data
+    private static class AttachData {
+        boolean mUsesIdentityCallback; // do any attach sessions of the UID use identity callback
+        int mMaxConcurrentAttaches;
+    }
+    private Map<Integer, AttachData> mAttachDataByUid = new HashMap<>();
+    private SparseIntArray mAttachStatusData = new SparseIntArray();
+    private SparseIntArray mHistogramAttachDuration = new SparseIntArray();
+
+    // discovery data
+    private int mMaxPublishInApp = 0;
+    private int mMaxSubscribeInApp = 0;
+    private int mMaxDiscoveryInApp = 0;
+    private int mMaxPublishInSystem = 0;
+    private int mMaxSubscribeInSystem = 0;
+    private int mMaxDiscoveryInSystem = 0;
+    private SparseIntArray mPublishStatusData = new SparseIntArray();
+    private SparseIntArray mSubscribeStatusData = new SparseIntArray();
+    private SparseIntArray mHistogramPublishDuration = new SparseIntArray();
+    private SparseIntArray mHistogramSubscribeDuration = new SparseIntArray();
+    private Set<Integer> mAppsWithDiscoverySessionResourceFailure = new HashSet<>();
+
+    // data-path (NDI/NDP) data
+    private int mMaxNdiInApp = 0;
+    private int mMaxNdpInApp = 0;
+    private int mMaxSecureNdpInApp = 0;
+    private int mMaxNdiInSystem = 0;
+    private int mMaxNdpInSystem = 0;
+    private int mMaxSecureNdpInSystem = 0;
+    private int mMaxNdpPerNdi = 0;
+    private SparseIntArray mInBandNdpStatusData = new SparseIntArray();
+    private SparseIntArray mOutOfBandNdpStatusData = new SparseIntArray();
+
+    private SparseIntArray mNdpCreationTimeDuration = new SparseIntArray();
+    private long mNdpCreationTimeMin = -1;
+    private long mNdpCreationTimeMax = 0;
+    private long mNdpCreationTimeSum = 0;
+    private long mNdpCreationTimeSumSq = 0;
+    private long mNdpCreationTimeNumSamples = 0;
+
+    private SparseIntArray mHistogramNdpDuration = new SparseIntArray();
+
+    public WifiAwareMetrics(Clock clock) {
+        mClock = clock;
+    }
+
+    /**
+     * Push usage stats for WifiAwareStateMachine.enableUsage() to
+     * histogram_aware_available_duration_ms.
+     */
+    public void recordEnableUsage() {
+        synchronized (mLock) {
+            if (mLastEnableUsageMs != 0) {
+                Log.w(TAG, "enableUsage: mLastEnableUsage*Ms initialized!?");
+            }
+            mLastEnableUsageMs = mClock.getElapsedSinceBootMillis();
+            mLastEnableUsageInThisSampleWindowMs = mLastEnableUsageMs;
+        }
+    }
+
+    /**
+     * Push usage stats for WifiAwareStateMachine.disableUsage() to
+     * histogram_aware_available_duration_ms.
+     */
+
+    public void recordDisableUsage() {
+        synchronized (mLock) {
+            if (mLastEnableUsageMs == 0) {
+                Log.e(TAG, "disableUsage: mLastEnableUsage not initialized!?");
+                return;
+            }
+
+            long now = mClock.getElapsedSinceBootMillis();
+            addLogValueToHistogram(now - mLastEnableUsageMs, mHistogramAwareAvailableDurationMs,
+                    DURATION_LOG_HISTOGRAM);
+            mAvailableTimeMs += now - mLastEnableUsageInThisSampleWindowMs;
+            mLastEnableUsageMs = 0;
+            mLastEnableUsageInThisSampleWindowMs = 0;
+        }
+    }
+
+    /**
+     * Push usage stats of Aware actually being enabled on-the-air: start
+     */
+    public void recordEnableAware() {
+        synchronized (mLock) {
+            if (mLastEnableAwareMs != 0) {
+                return; // already enabled
+            }
+            mLastEnableAwareMs = mClock.getElapsedSinceBootMillis();
+            mLastEnableAwareInThisSampleWindowMs = mLastEnableAwareMs;
+        }
+    }
+
+    /**
+     * Push usage stats of Aware actually being enabled on-the-air: stop (disable)
+     */
+    public void recordDisableAware() {
+        synchronized (mLock) {
+            if (mLastEnableAwareMs == 0) {
+                return; // already disabled
+            }
+
+            long now = mClock.getElapsedSinceBootMillis();
+            addLogValueToHistogram(now - mLastEnableAwareMs, mHistogramAwareEnabledDurationMs,
+                    DURATION_LOG_HISTOGRAM);
+            mEnabledTimeMs += now - mLastEnableAwareInThisSampleWindowMs;
+            mLastEnableAwareMs = 0;
+            mLastEnableAwareInThisSampleWindowMs = 0;
+        }
+    }
+
+    /**
+     * Push information about a new attach session.
+     */
+    public void recordAttachSession(int uid, boolean usesIdentityCallback,
+            SparseArray<WifiAwareClientState> clients) {
+        // count the number of clients with the specific uid
+        int currentConcurrentCount = 0;
+        for (int i = 0; i < clients.size(); ++i) {
+            if (clients.valueAt(i).getUid() == uid) {
+                ++currentConcurrentCount;
+            }
+        }
+
+        synchronized (mLock) {
+            AttachData data = mAttachDataByUid.get(uid);
+            if (data == null) {
+                data = new AttachData();
+                mAttachDataByUid.put(uid, data);
+            }
+            data.mUsesIdentityCallback |= usesIdentityCallback;
+            data.mMaxConcurrentAttaches = Math.max(data.mMaxConcurrentAttaches,
+                    currentConcurrentCount);
+            recordAttachStatus(NanStatusType.SUCCESS);
+        }
+    }
+
+    /**
+     * Push information about a new attach session status (recorded when attach session is created).
+     */
+    public void recordAttachStatus(int status) {
+        synchronized (mLock) {
+            mAttachStatusData.put(status, mAttachStatusData.get(status) + 1);
+        }
+    }
+
+    /**
+     * Push duration information of an attach session.
+     */
+    public void recordAttachSessionDuration(long creationTime) {
+        synchronized (mLock) {
+            addLogValueToHistogram(mClock.getElapsedSinceBootMillis() - creationTime,
+                    mHistogramAttachDuration,
+                    DURATION_LOG_HISTOGRAM);
+        }
+    }
+
+    /**
+     * Push information about the new discovery session.
+     */
+    public void recordDiscoverySession(int uid, boolean isPublish,
+            SparseArray<WifiAwareClientState> clients) {
+        // count the number of sessions per uid and overall
+        int numPublishesInSystem = 0;
+        int numSubscribesInSystem = 0;
+        int numPublishesOnUid = 0;
+        int numSubscribesOnUid = 0;
+
+        for (int i = 0; i < clients.size(); ++i) {
+            WifiAwareClientState client = clients.valueAt(i);
+            boolean sameUid = client.getUid() == uid;
+
+            SparseArray<WifiAwareDiscoverySessionState> sessions = client.getSessions();
+            for (int j = 0; j < sessions.size(); ++j) {
+                WifiAwareDiscoverySessionState session = sessions.valueAt(j);
+
+                if (session.isPublishSession()) {
+                    numPublishesInSystem += 1;
+                    if (sameUid) {
+                        numPublishesOnUid += 1;
+                    }
+                } else {
+                    numSubscribesInSystem += 1;
+                    if (sameUid) {
+                        numSubscribesOnUid += 1;
+                    }
+                }
+            }
+        }
+
+        synchronized (mLock) {
+            mMaxPublishInApp = Math.max(mMaxPublishInApp, numPublishesOnUid);
+            mMaxSubscribeInApp = Math.max(mMaxSubscribeInApp, numSubscribesOnUid);
+            mMaxDiscoveryInApp = Math.max(mMaxDiscoveryInApp,
+                    numPublishesOnUid + numSubscribesOnUid);
+            mMaxPublishInSystem = Math.max(mMaxPublishInSystem, numPublishesInSystem);
+            mMaxSubscribeInSystem = Math.max(mMaxSubscribeInSystem, numSubscribesInSystem);
+            mMaxDiscoveryInSystem = Math.max(mMaxDiscoveryInSystem,
+                    numPublishesInSystem + numSubscribesInSystem);
+        }
+    }
+
+    /**
+     * Push information about a new discovery session status (recorded when the discovery session is
+     * created).
+     */
+    public void recordDiscoveryStatus(int uid, int status, boolean isPublish) {
+        synchronized (mLock) {
+            if (isPublish) {
+                mPublishStatusData.put(status, mPublishStatusData.get(status) + 1);
+            } else {
+                mSubscribeStatusData.put(status, mSubscribeStatusData.get(status) + 1);
+            }
+
+            if (status == NanStatusType.NO_RESOURCES_AVAILABLE) {
+                mAppsWithDiscoverySessionResourceFailure.add(uid);
+            }
+        }
+    }
+
+    /**
+     * Push duration information of a discovery session.
+     */
+    public void recordDiscoverySessionDuration(long creationTime, boolean isPublish) {
+        synchronized (mLock) {
+            addLogValueToHistogram(mClock.getElapsedSinceBootMillis() - creationTime,
+                    isPublish ? mHistogramPublishDuration : mHistogramSubscribeDuration,
+                    DURATION_LOG_HISTOGRAM);
+        }
+    }
+
+    /**
+     * Record NDP (and by extension NDI) usage - on successful creation of an NDP.
+     */
+    public void recordNdpCreation(int uid,
+            Map<WifiAwareNetworkSpecifier, WifiAwareDataPathStateManager
+                    .AwareNetworkRequestInformation> networkRequestCache) {
+        int numNdpInApp = 0;
+        int numSecureNdpInApp = 0;
+        int numNdpInSystem = 0;
+        int numSecureNdpInSystem = 0;
+
+        Map<String, Integer> ndpPerNdiMap = new HashMap<>();
+        Set<String> ndiInApp = new HashSet<>();
+        Set<String> ndiInSystem = new HashSet<>();
+
+        for (WifiAwareDataPathStateManager.AwareNetworkRequestInformation anri :
+                networkRequestCache.values()) {
+            if (anri.state
+                    != WifiAwareDataPathStateManager.AwareNetworkRequestInformation
+                    .STATE_INITIATOR_CONFIRMED
+                    && anri.state
+                    != WifiAwareDataPathStateManager.AwareNetworkRequestInformation
+                    .STATE_RESPONDER_CONFIRMED) {
+                continue; // only count completed (up-and-running) NDPs
+            }
+
+            boolean sameUid = anri.uid == uid;
+            boolean isSecure = !TextUtils.isEmpty(anri.networkSpecifier.passphrase) || (
+                    anri.networkSpecifier.pmk != null && anri.networkSpecifier.pmk.length != 0);
+
+            // in-app stats
+            if (sameUid) {
+                numNdpInApp += 1;
+                if (isSecure) {
+                    numSecureNdpInApp += 1;
+                }
+
+                ndiInApp.add(anri.interfaceName);
+            }
+
+            // system stats
+            numNdpInSystem += 1;
+            if (isSecure) {
+                numSecureNdpInSystem += 1;
+            }
+
+            // ndp/ndi stats
+            Integer ndpCount = ndpPerNdiMap.get(anri.interfaceName);
+            if (ndpCount == null) {
+                ndpPerNdiMap.put(anri.interfaceName, 1);
+            } else {
+                ndpPerNdiMap.put(anri.interfaceName, ndpCount + 1);
+            }
+
+            // ndi stats
+            ndiInSystem.add(anri.interfaceName);
+        }
+
+        synchronized (mLock) {
+            mMaxNdiInApp = Math.max(mMaxNdiInApp, ndiInApp.size());
+            mMaxNdpInApp = Math.max(mMaxNdpInApp, numNdpInApp);
+            mMaxSecureNdpInApp = Math.max(mMaxSecureNdpInApp, numSecureNdpInApp);
+            mMaxNdiInSystem = Math.max(mMaxNdiInSystem, ndiInSystem.size());
+            mMaxNdpInSystem = Math.max(mMaxNdpInSystem, numNdpInSystem);
+            mMaxSecureNdpInSystem = Math.max(mMaxSecureNdpInSystem, numSecureNdpInSystem);
+            mMaxNdpPerNdi = Math.max(mMaxNdpPerNdi, Collections.max(ndpPerNdiMap.values()));
+        }
+    }
+
+    /**
+     * Record the completion status of NDP negotiation. There are multiple steps in NDP negotiation
+     * a failure on any aborts the process and is recorded. A success on intermediate stages is
+     * not recorded - only the final success.
+     */
+    public void recordNdpStatus(int status, boolean isOutOfBand, long startTimestamp) {
+        synchronized (mLock) {
+            if (isOutOfBand) {
+                mOutOfBandNdpStatusData.put(status, mOutOfBandNdpStatusData.get(status) + 1);
+            } else {
+                mInBandNdpStatusData.put(status, mOutOfBandNdpStatusData.get(status) + 1);
+            }
+
+            if (status == NanStatusType.SUCCESS) {
+                long creationTime = mClock.getElapsedSinceBootMillis() - startTimestamp;
+                addLogValueToHistogram(creationTime, mNdpCreationTimeDuration,
+                        DURATION_LOG_HISTOGRAM);
+                mNdpCreationTimeMin = (mNdpCreationTimeMin == -1) ? creationTime : Math.min(
+                        mNdpCreationTimeMin, creationTime);
+                mNdpCreationTimeMax = Math.max(mNdpCreationTimeMax, creationTime);
+                mNdpCreationTimeSum += creationTime;
+                mNdpCreationTimeSumSq += creationTime * creationTime;
+                mNdpCreationTimeNumSamples += 1;
+            }
+        }
+    }
+
+    /**
+     * Record the duration of the NDP session. The creation time is assumed to be the time at
+     * which a confirm message was received (i.e. the end of the setup negotiation).
+     */
+    public void recordNdpSessionDuration(long creationTime) {
+        synchronized (mLock) {
+            addLogValueToHistogram(mClock.getElapsedSinceBootMillis() - creationTime,
+                    mHistogramNdpDuration, DURATION_LOG_HISTOGRAM);
+        }
+    }
+
+    /**
+     * Consolidate all metrics into the proto.
+     */
+    public WifiMetricsProto.WifiAwareLog consolidateProto() {
+        WifiMetricsProto.WifiAwareLog log = new WifiMetricsProto.WifiAwareLog();
+        long now = mClock.getElapsedSinceBootMillis();
+        synchronized (mLock) {
+            log.histogramAwareAvailableDurationMs = histogramToProtoArray(
+                    mHistogramAwareAvailableDurationMs, DURATION_LOG_HISTOGRAM);
+            log.availableTimeMs = mAvailableTimeMs;
+            if (mLastEnableUsageInThisSampleWindowMs != 0) {
+                log.availableTimeMs += now - mLastEnableUsageInThisSampleWindowMs;
+            }
+
+            log.histogramAwareEnabledDurationMs = histogramToProtoArray(
+                    mHistogramAwareEnabledDurationMs, DURATION_LOG_HISTOGRAM);
+            log.enabledTimeMs = mEnabledTimeMs;
+            if (mLastEnableAwareInThisSampleWindowMs != 0) {
+                log.enabledTimeMs += now - mLastEnableAwareInThisSampleWindowMs;
+            }
+
+            log.numApps = mAttachDataByUid.size();
+            log.numAppsUsingIdentityCallback = 0;
+            log.maxConcurrentAttachSessionsInApp = 0;
+            for (AttachData ad: mAttachDataByUid.values()) {
+                if (ad.mUsesIdentityCallback) {
+                    ++log.numAppsUsingIdentityCallback;
+                }
+                log.maxConcurrentAttachSessionsInApp = Math.max(
+                        log.maxConcurrentAttachSessionsInApp, ad.mMaxConcurrentAttaches);
+            }
+            log.histogramAttachSessionStatus = histogramToProtoArray(mAttachStatusData);
+            log.histogramAttachDurationMs = histogramToProtoArray(mHistogramAttachDuration,
+                    DURATION_LOG_HISTOGRAM);
+
+            log.maxConcurrentPublishInApp = mMaxPublishInApp;
+            log.maxConcurrentSubscribeInApp = mMaxSubscribeInApp;
+            log.maxConcurrentDiscoverySessionsInApp = mMaxDiscoveryInApp;
+            log.maxConcurrentPublishInSystem = mMaxPublishInSystem;
+            log.maxConcurrentSubscribeInSystem = mMaxSubscribeInSystem;
+            log.maxConcurrentDiscoverySessionsInSystem = mMaxDiscoveryInSystem;
+            log.histogramPublishStatus = histogramToProtoArray(mPublishStatusData);
+            log.histogramSubscribeStatus = histogramToProtoArray(mSubscribeStatusData);
+            log.numAppsWithDiscoverySessionFailureOutOfResources =
+                    mAppsWithDiscoverySessionResourceFailure.size();
+            log.histogramPublishSessionDurationMs = histogramToProtoArray(mHistogramPublishDuration,
+                    DURATION_LOG_HISTOGRAM);
+            log.histogramSubscribeSessionDurationMs = histogramToProtoArray(
+                    mHistogramSubscribeDuration, DURATION_LOG_HISTOGRAM);
+
+            log.maxConcurrentNdiInApp = mMaxNdiInApp;
+            log.maxConcurrentNdiInSystem = mMaxNdiInSystem;
+            log.maxConcurrentNdpInApp = mMaxNdpInApp;
+            log.maxConcurrentNdpInSystem = mMaxNdpInSystem;
+            log.maxConcurrentSecureNdpInApp = mMaxSecureNdpInApp;
+            log.maxConcurrentSecureNdpInSystem = mMaxSecureNdpInSystem;
+            log.maxConcurrentNdpPerNdi = mMaxNdpPerNdi;
+            log.histogramRequestNdpStatus = histogramToProtoArray(mInBandNdpStatusData);
+            log.histogramRequestNdpOobStatus = histogramToProtoArray(mOutOfBandNdpStatusData);
+
+            log.histogramNdpCreationTimeMs = histogramToProtoArray(mNdpCreationTimeDuration,
+                    DURATION_LOG_HISTOGRAM);
+            log.ndpCreationTimeMsMin = mNdpCreationTimeMin;
+            log.ndpCreationTimeMsMax = mNdpCreationTimeMax;
+            log.ndpCreationTimeMsSum = mNdpCreationTimeSum;
+            log.ndpCreationTimeMsSumOfSq = mNdpCreationTimeSumSq;
+            log.ndpCreationTimeMsNumSamples = mNdpCreationTimeNumSamples;
+
+            log.histogramNdpSessionDurationMs = histogramToProtoArray(mHistogramNdpDuration,
+                    DURATION_LOG_HISTOGRAM);
+        }
+        return log;
+    }
+
+    /**
+     * clear Wi-Fi Aware metrics
+     */
+    public void clear() {
+        long now = mClock.getElapsedSinceBootMillis();
+        synchronized (mLock) {
+            // don't clear mLastEnableUsage since could be valid for next measurement period
+            mHistogramAwareAvailableDurationMs.clear();
+            mAvailableTimeMs = 0;
+            if (mLastEnableUsageInThisSampleWindowMs != 0) {
+                mLastEnableUsageInThisSampleWindowMs = now;
+            }
+
+            // don't clear mLastEnableAware since could be valid for next measurement period
+            mHistogramAwareEnabledDurationMs.clear();
+            mEnabledTimeMs = 0;
+            if (mLastEnableAwareInThisSampleWindowMs != 0) {
+                mLastEnableAwareInThisSampleWindowMs = now;
+            }
+
+            mAttachDataByUid.clear();
+            mAttachStatusData.clear();
+            mHistogramAttachDuration.clear();
+
+            mMaxPublishInApp = 0;
+            mMaxSubscribeInApp = 0;
+            mMaxDiscoveryInApp = 0;
+            mMaxPublishInSystem = 0;
+            mMaxSubscribeInSystem = 0;
+            mMaxDiscoveryInSystem = 0;
+            mPublishStatusData.clear();
+            mSubscribeStatusData.clear();
+            mHistogramPublishDuration.clear();
+            mHistogramSubscribeDuration.clear();
+            mAppsWithDiscoverySessionResourceFailure.clear();
+
+            mMaxNdiInApp = 0;
+            mMaxNdpInApp = 0;
+            mMaxSecureNdpInApp = 0;
+            mMaxNdiInSystem = 0;
+            mMaxNdpInSystem = 0;
+            mMaxSecureNdpInSystem = 0;
+            mMaxNdpPerNdi = 0;
+            mInBandNdpStatusData.clear();
+            mOutOfBandNdpStatusData.clear();
+
+            mNdpCreationTimeDuration.clear();
+            mNdpCreationTimeMin = -1;
+            mNdpCreationTimeMax = 0;
+            mNdpCreationTimeSum = 0;
+            mNdpCreationTimeSumSq = 0;
+            mNdpCreationTimeNumSamples = 0;
+
+            mHistogramNdpDuration.clear();
+        }
+    }
+
+    /**
+     * Dump all WifiAwareMetrics to console (pw) - this method is never called to dump the
+     * serialized metrics (handled by parent WifiMetrics).
+     *
+     * @param fd   unused
+     * @param pw   PrintWriter for writing dump to
+     * @param args unused
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        synchronized (mLock) {
+            pw.println("mLastEnableUsageMs:" + mLastEnableUsageMs);
+            pw.println(
+                    "mLastEnableUsageInThisSampleWindowMs:" + mLastEnableUsageInThisSampleWindowMs);
+            pw.println("mAvailableTimeMs:" + mAvailableTimeMs);
+            pw.println("mHistogramAwareAvailableDurationMs:");
+            for (int i = 0; i < mHistogramAwareAvailableDurationMs.size(); ++i) {
+                pw.println("  " + mHistogramAwareAvailableDurationMs.keyAt(i) + ": "
+                        + mHistogramAwareAvailableDurationMs.valueAt(i));
+            }
+
+            pw.println("mLastEnableAwareMs:" + mLastEnableAwareMs);
+            pw.println(
+                    "mLastEnableAwareInThisSampleWindowMs:" + mLastEnableAwareInThisSampleWindowMs);
+            pw.println("mEnabledTimeMs:" + mEnabledTimeMs);
+            pw.println("mHistogramAwareEnabledDurationMs:");
+            for (int i = 0; i < mHistogramAwareEnabledDurationMs.size(); ++i) {
+                pw.println("  " + mHistogramAwareEnabledDurationMs.keyAt(i) + ": "
+                        + mHistogramAwareEnabledDurationMs.valueAt(i));
+            }
+
+            pw.println("mAttachDataByUid:");
+            for (Map.Entry<Integer, AttachData> ade: mAttachDataByUid.entrySet()) {
+                pw.println("  " + "uid=" + ade.getKey() + ": identity="
+                        + ade.getValue().mUsesIdentityCallback + ", maxConcurrent="
+                        + ade.getValue().mMaxConcurrentAttaches);
+            }
+            pw.println("mAttachStatusData:");
+            for (int i = 0; i < mAttachStatusData.size(); ++i) {
+                pw.println("  " + mAttachStatusData.keyAt(i) + ": "
+                        + mAttachStatusData.valueAt(i));
+            }
+            pw.println("mHistogramAttachDuration:");
+            for (int i = 0; i < mHistogramAttachDuration.size(); ++i) {
+                pw.println("  " + mHistogramAttachDuration.keyAt(i) + ": "
+                        + mHistogramAttachDuration.valueAt(i));
+            }
+
+            pw.println("mMaxPublishInApp:" + mMaxPublishInApp);
+            pw.println("mMaxSubscribeInApp:" + mMaxSubscribeInApp);
+            pw.println("mMaxDiscoveryInApp:" + mMaxDiscoveryInApp);
+            pw.println("mMaxPublishInSystem:" + mMaxPublishInSystem);
+            pw.println("mMaxSubscribeInSystem:" + mMaxSubscribeInSystem);
+            pw.println("mMaxDiscoveryInSystem:" + mMaxDiscoveryInSystem);
+            pw.println("mPublishStatusData:");
+            for (int i = 0; i < mPublishStatusData.size(); ++i) {
+                pw.println("  " + mPublishStatusData.keyAt(i) + ": "
+                        + mPublishStatusData.valueAt(i));
+            }
+            pw.println("mSubscribeStatusData:");
+            for (int i = 0; i < mSubscribeStatusData.size(); ++i) {
+                pw.println("  " + mSubscribeStatusData.keyAt(i) + ": "
+                        + mSubscribeStatusData.valueAt(i));
+            }
+            pw.println("mHistogramPublishDuration:");
+            for (int i = 0; i < mHistogramPublishDuration.size(); ++i) {
+                pw.println("  " + mHistogramPublishDuration.keyAt(i) + ": "
+                        + mHistogramPublishDuration.valueAt(i));
+            }
+            pw.println("mHistogramSubscribeDuration:");
+            for (int i = 0; i < mHistogramSubscribeDuration.size(); ++i) {
+                pw.println("  " + mHistogramSubscribeDuration.keyAt(i) + ": "
+                        + mHistogramSubscribeDuration.valueAt(i));
+            }
+            pw.println("mAppsWithDiscoverySessionResourceFailure:");
+            for (Integer uid: mAppsWithDiscoverySessionResourceFailure) {
+                pw.println("  " + uid);
+            }
+
+            pw.println("mMaxNdiInApp:" + mMaxNdiInApp);
+            pw.println("mMaxNdpInApp:" + mMaxNdpInApp);
+            pw.println("mMaxSecureNdpInApp:" + mMaxSecureNdpInApp);
+            pw.println("mMaxNdiInSystem:" + mMaxNdiInSystem);
+            pw.println("mMaxNdpInSystem:" + mMaxNdpInSystem);
+            pw.println("mMaxSecureNdpInSystem:" + mMaxSecureNdpInSystem);
+            pw.println("mMaxNdpPerNdi:" + mMaxNdpPerNdi);
+            pw.println("mInBandNdpStatusData:");
+            for (int i = 0; i < mInBandNdpStatusData.size(); ++i) {
+                pw.println("  " + mInBandNdpStatusData.keyAt(i) + ": "
+                        + mInBandNdpStatusData.valueAt(i));
+            }
+            pw.println("mOutOfBandNdpStatusData:");
+            for (int i = 0; i < mOutOfBandNdpStatusData.size(); ++i) {
+                pw.println("  " + mOutOfBandNdpStatusData.keyAt(i) + ": "
+                        + mOutOfBandNdpStatusData.valueAt(i));
+            }
+
+            pw.println("mNdpCreationTimeDuration:");
+            for (int i = 0; i < mNdpCreationTimeDuration.size(); ++i) {
+                pw.println("  " + mNdpCreationTimeDuration.keyAt(i) + ": "
+                        + mNdpCreationTimeDuration.valueAt(i));
+            }
+            pw.println("mNdpCreationTimeMin:" + mNdpCreationTimeMin);
+            pw.println("mNdpCreationTimeMax:" + mNdpCreationTimeMax);
+            pw.println("mNdpCreationTimeSum:" + mNdpCreationTimeSum);
+            pw.println("mNdpCreationTimeSumSq:" + mNdpCreationTimeSumSq);
+            pw.println("mNdpCreationTimeNumSamples:" + mNdpCreationTimeNumSamples);
+
+            pw.println("mHistogramNdpDuration:");
+            for (int i = 0; i < mHistogramNdpDuration.size(); ++i) {
+                pw.println("  " + mHistogramNdpDuration.keyAt(i) + ": "
+                        + mHistogramNdpDuration.valueAt(i));
+            }
+        }
+    }
+
+    // histogram utilities
+
+    /**
+     * Specifies a ~log histogram consisting of two levels of buckets - a set of N big buckets:
+     *
+     * Buckets starts at: B + P * M^i, where i=0, ... , N-1 (N big buckets)
+     * Each big bucket is divided into S sub-buckets
+     *
+     * Each (big) bucket is M times bigger than the previous one.
+     *
+     * The buckets are then:
+     * #0: B + P * M^0 with S buckets each of width (P*M^1-P*M^0)/S
+     * #1: B + P * M^1 with S buckets each of width (P*M^2-P*M^1)/S
+     * ...
+     * #N-1: B + P * M^(N-1) with S buckets each of width (P*M^N-P*M^(N-1))/S
+     */
+    @VisibleForTesting
+    public static class HistParms {
+        public HistParms(int b, int p, int m, int s, int n) {
+            this.b = b;
+            this.p = p;
+            this.m = m;
+            this.s = s;
+            this.n = n;
+
+            // derived values
+            mLog = Math.log(m);
+            bb = new double[n];
+            sbw = new double[n];
+            bb[0] = b + p;
+            sbw[0] = p * (m - 1.0) / (double) s;
+            for (int i = 1; i < n; ++i) {
+                bb[i] = m * (bb[i - 1] - b) + b;
+                sbw[i] = m * sbw[i - 1];
+            }
+        }
+
+        // spec
+        public int b;
+        public int p;
+        public int m;
+        public int s;
+        public int n;
+
+        // derived
+        public double mLog;
+        public double[] bb; // bucket base
+        public double[] sbw; // sub-bucket width
+    }
+
+    /**
+     * Adds the input value to the histogram based on the histogram parameters.
+     */
+    @VisibleForTesting
+    public static int addLogValueToHistogram(long x, SparseIntArray histogram, HistParms hp) {
+        double logArg = (double) (x - hp.b) / (double) hp.p;
+        int bigBucketIndex = -1;
+        if (logArg > 0) {
+            bigBucketIndex = (int) (Math.log(logArg) / hp.mLog);
+        }
+        int subBucketIndex;
+        if (bigBucketIndex < 0) {
+            bigBucketIndex = 0;
+            subBucketIndex = 0;
+        } else if (bigBucketIndex >= hp.n) {
+            bigBucketIndex = hp.n - 1;
+            subBucketIndex = hp.s - 1;
+        } else {
+            subBucketIndex = (int) ((x - hp.bb[bigBucketIndex]) / hp.sbw[bigBucketIndex]);
+            if (subBucketIndex >= hp.s) { // probably a rounding error so move to next big bucket
+                bigBucketIndex++;
+                if (bigBucketIndex >= hp.n) {
+                    bigBucketIndex = hp.n - 1;
+                    subBucketIndex = hp.s - 1;
+                } else {
+                    subBucketIndex = (int) ((x - hp.bb[bigBucketIndex]) / hp.sbw[bigBucketIndex]);
+                }
+            }
+        }
+        int key = bigBucketIndex * hp.s + subBucketIndex;
+
+        // note that get() returns 0 if index not there already
+        int newValue = histogram.get(key) + 1;
+        histogram.put(key, newValue);
+
+        return newValue;
+    }
+
+    /**
+     * Converts the histogram (with the specified histogram parameters) to an array of proto
+     * histogram buckets.
+     */
+    @VisibleForTesting
+    public static WifiMetricsProto.WifiAwareLog.HistogramBucket[] histogramToProtoArray(
+            SparseIntArray histogram, HistParms hp) {
+        WifiMetricsProto.WifiAwareLog.HistogramBucket[] protoArray =
+                new WifiMetricsProto.WifiAwareLog.HistogramBucket[histogram.size()];
+        for (int i = 0; i < histogram.size(); ++i) {
+            int key = histogram.keyAt(i);
+
+            protoArray[i] = new WifiMetricsProto.WifiAwareLog.HistogramBucket();
+            protoArray[i].start = (long) (hp.bb[key / hp.s] + hp.sbw[key / hp.s] * (key % hp.s));
+            protoArray[i].end = (long) (protoArray[i].start + hp.sbw[key / hp.s]);
+            protoArray[i].count = histogram.valueAt(i);
+        }
+
+        return protoArray;
+    }
+
+    /**
+     * Adds the NanStatusType to the histogram (translating to the proto enumeration of the status).
+     */
+    public static void addNanHalStatusToHistogram(int halStatus, SparseIntArray histogram) {
+        int protoStatus = convertNanStatusTypeToProtoEnum(halStatus);
+        int newValue = histogram.get(protoStatus) + 1;
+        histogram.put(protoStatus, newValue);
+    }
+
+    /**
+     * Converts a histogram of proto NanStatusTypeEnum to a raw proto histogram.
+     */
+    @VisibleForTesting
+    public static WifiMetricsProto.WifiAwareLog.NanStatusHistogramBucket[] histogramToProtoArray(
+            SparseIntArray histogram) {
+        WifiMetricsProto.WifiAwareLog.NanStatusHistogramBucket[] protoArray =
+                new WifiMetricsProto.WifiAwareLog.NanStatusHistogramBucket[histogram.size()];
+
+        for (int i = 0; i < histogram.size(); ++i) {
+            protoArray[i] = new WifiMetricsProto.WifiAwareLog.NanStatusHistogramBucket();
+            protoArray[i].nanStatusType = histogram.keyAt(i);
+            protoArray[i].count = histogram.valueAt(i);
+        }
+
+        return protoArray;
+    }
+
+    /**
+     * Convert a HAL NanStatusType enum to a Metrics proto enum NanStatusTypeEnum.
+     */
+    public static int convertNanStatusTypeToProtoEnum(int nanStatusType) {
+        switch (nanStatusType) {
+            case NanStatusType.SUCCESS:
+                return WifiMetricsProto.WifiAwareLog.SUCCESS;
+            case NanStatusType.INTERNAL_FAILURE:
+                return WifiMetricsProto.WifiAwareLog.INTERNAL_FAILURE;
+            case NanStatusType.PROTOCOL_FAILURE:
+                return WifiMetricsProto.WifiAwareLog.PROTOCOL_FAILURE;
+            case NanStatusType.INVALID_SESSION_ID:
+                return WifiMetricsProto.WifiAwareLog.INVALID_SESSION_ID;
+            case NanStatusType.NO_RESOURCES_AVAILABLE:
+                return WifiMetricsProto.WifiAwareLog.NO_RESOURCES_AVAILABLE;
+            case NanStatusType.INVALID_ARGS:
+                return WifiMetricsProto.WifiAwareLog.INVALID_ARGS;
+            case NanStatusType.INVALID_PEER_ID:
+                return WifiMetricsProto.WifiAwareLog.INVALID_PEER_ID;
+            case NanStatusType.INVALID_NDP_ID:
+                return WifiMetricsProto.WifiAwareLog.INVALID_NDP_ID;
+            case NanStatusType.NAN_NOT_ALLOWED:
+                return WifiMetricsProto.WifiAwareLog.NAN_NOT_ALLOWED;
+            case NanStatusType.NO_OTA_ACK:
+                return WifiMetricsProto.WifiAwareLog.NO_OTA_ACK;
+            case NanStatusType.ALREADY_ENABLED:
+                return WifiMetricsProto.WifiAwareLog.ALREADY_ENABLED;
+            case NanStatusType.FOLLOWUP_TX_QUEUE_FULL:
+                return WifiMetricsProto.WifiAwareLog.FOLLOWUP_TX_QUEUE_FULL;
+            case NanStatusType.UNSUPPORTED_CONCURRENCY_NAN_DISABLED:
+                return WifiMetricsProto.WifiAwareLog.UNSUPPORTED_CONCURRENCY_NAN_DISABLED;
+            default:
+                Log.e(TAG, "Unrecognized NanStatusType: " + nanStatusType);
+                return WifiMetricsProto.WifiAwareLog.UNKNOWN_HAL_STATUS;
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareNativeApi.java b/service/java/com/android/server/wifi/aware/WifiAwareNativeApi.java
index 2efa2ae..a6e724f 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareNativeApi.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareNativeApi.java
@@ -36,28 +36,131 @@
 import android.net.wifi.aware.PublishConfig;
 import android.net.wifi.aware.SubscribeConfig;
 import android.os.RemoteException;
+import android.os.ShellCommand;
 import android.util.Log;
 
 import libcore.util.HexEncoding;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * Translates Wi-Fi Aware requests from the framework to the HAL (HIDL).
  *
  * Delegates the management of the NAN interface to WifiAwareNativeManager.
  */
-public class WifiAwareNativeApi {
+public class WifiAwareNativeApi implements WifiAwareShellCommand.DelegatedShellCommand {
     private static final String TAG = "WifiAwareNativeApi";
     private static final boolean DBG = false;
     private static final boolean VDBG = false; // STOPSHIP if true
 
+    private static final String SERVICE_NAME_FOR_OOB_DATA_PATH = "Wi-Fi Aware Data Path";
+
     private final WifiAwareNativeManager mHal;
 
     public WifiAwareNativeApi(WifiAwareNativeManager wifiAwareNativeManager) {
         mHal = wifiAwareNativeManager;
+        onReset();
+    }
+
+    /*
+     * Parameters settable through the shell command.
+     * see wifi/1.0/types.hal NanBandSpecificConfig.discoveryWindowIntervalVal for description
+     */
+    public static final String PARAM_DW_DEFAULT_24GHZ = "dw_default_24ghz";
+    public static final int PARAM_DW_DEFAULT_24GHZ_DEFAULT = -1; // Firmware default
+    public static final String PARAM_DW_DEFAULT_5GHZ = "dw_default_5ghz";
+    public static final int PARAM_DW_DEFAULT_5GHZ_DEFAULT = -1; // Firmware default
+    public static final String PARAM_DW_ON_INACTIVE_24GHZ = "dw_on_inactive_24ghz";
+    public static final int PARAM_DW_ON_INACTIVE_24GHZ_DEFAULT = 4; // 4 -> DW=8, latency=4s
+    public static final String PARAM_DW_ON_INACTIVE_5GHZ = "dw_on_inactive_5ghz";
+    public static final int PARAM_DW_ON_INACTIVE_5GHZ_DEFAULT = 0; // 0 = disabled
+    public static final String PARAM_DW_ON_IDLE_24GHZ = "dw_on_idle_24ghz";
+    public static final int PARAM_DW_ON_IDLE_24GHZ_DEFAULT = -1; // NOP (but disabling on IDLE)
+    public static final String PARAM_DW_ON_IDLE_5GHZ = "dw_on_idle_5ghz";
+    public static final int PARAM_DW_ON_IDLE_5GHZ_DEFAULT = -1; // NOP (but disabling on IDLE)
+
+    public static final String PARAM_MAC_RANDOM_INTERVAL_SEC = "mac_random_interval_sec";
+    public static final int PARAM_MAC_RANDOM_INTERVAL_SEC_DEFAULT = 1800; // 30 minutes
+
+    private Map<String, Integer> mSettableParameters = new HashMap<>();
+
+    /**
+     * Interpreter of adb shell command 'adb shell wifiaware native_api ...'.
+     *
+     * @return -1 if parameter not recognized or invalid value, 0 otherwise.
+     */
+    @Override
+    public int onCommand(ShellCommand parentShell) {
+        final PrintWriter pw = parentShell.getErrPrintWriter();
+
+        String subCmd = parentShell.getNextArgRequired();
+        if (VDBG) Log.v(TAG, "onCommand: subCmd='" + subCmd + "'");
+        switch (subCmd) {
+            case "set": {
+                String name = parentShell.getNextArgRequired();
+                if (VDBG) Log.v(TAG, "onCommand: name='" + name + "'");
+                if (!mSettableParameters.containsKey(name)) {
+                    pw.println("Unknown parameter name -- '" + name + "'");
+                    return -1;
+                }
+
+                String valueStr = parentShell.getNextArgRequired();
+                if (VDBG) Log.v(TAG, "onCommand: valueStr='" + valueStr + "'");
+                int value;
+                try {
+                    value = Integer.valueOf(valueStr);
+                } catch (NumberFormatException e) {
+                    pw.println("Can't convert value to integer -- '" + valueStr + "'");
+                    return -1;
+                }
+                mSettableParameters.put(name, value);
+                return 0;
+            }
+            case "get": {
+                String name = parentShell.getNextArgRequired();
+                if (VDBG) Log.v(TAG, "onCommand: name='" + name + "'");
+                if (!mSettableParameters.containsKey(name)) {
+                    pw.println("Unknown parameter name -- '" + name + "'");
+                    return -1;
+                }
+
+                parentShell.getOutPrintWriter().println((int) mSettableParameters.get(name));
+                return 0;
+            }
+            default:
+                pw.println("Unknown 'wifiaware native_api <cmd>'");
+        }
+
+        return -1;
+    }
+
+    @Override
+    public void onReset() {
+        mSettableParameters.put(PARAM_DW_DEFAULT_24GHZ, PARAM_DW_DEFAULT_24GHZ_DEFAULT);
+        mSettableParameters.put(PARAM_DW_DEFAULT_5GHZ, PARAM_DW_DEFAULT_5GHZ_DEFAULT);
+        mSettableParameters.put(PARAM_DW_ON_INACTIVE_24GHZ, PARAM_DW_ON_INACTIVE_24GHZ_DEFAULT);
+        mSettableParameters.put(PARAM_DW_ON_INACTIVE_5GHZ, PARAM_DW_ON_INACTIVE_5GHZ_DEFAULT);
+        mSettableParameters.put(PARAM_DW_ON_IDLE_24GHZ, PARAM_DW_ON_IDLE_24GHZ_DEFAULT);
+        mSettableParameters.put(PARAM_DW_ON_IDLE_5GHZ, PARAM_DW_ON_IDLE_5GHZ_DEFAULT);
+
+        mSettableParameters.put(PARAM_MAC_RANDOM_INTERVAL_SEC,
+                PARAM_MAC_RANDOM_INTERVAL_SEC_DEFAULT);
+    }
+
+    @Override
+    public void onHelp(String command, ShellCommand parentShell) {
+        final PrintWriter pw = parentShell.getOutPrintWriter();
+
+        pw.println("  " + command);
+        pw.println("    set <name> <value>: sets named parameter to value. Names: "
+                + mSettableParameters.keySet());
+        pw.println("    get <name>: gets named parameter value. Names: "
+                + mSettableParameters.keySet());
     }
 
     /**
@@ -98,13 +201,17 @@
      * @param notifyIdentityChange Indicates whether or not to get address change callbacks.
      * @param initialConfiguration Specifies whether initial configuration
      *            (true) or an update (false) to the configuration.
+     * @param isInteractive PowerManager.isInteractive
+     * @param isIdle PowerManager.isIdle
      */
     public boolean enableAndConfigure(short transactionId, ConfigRequest configRequest,
-            boolean notifyIdentityChange, boolean initialConfiguration) {
+            boolean notifyIdentityChange, boolean initialConfiguration, boolean isInteractive,
+            boolean isIdle) {
         if (VDBG) {
             Log.v(TAG, "enableAndConfigure: transactionId=" + transactionId + ", configRequest="
                     + configRequest + ", notifyIdentityChange=" + notifyIdentityChange
-                    + ", initialConfiguration=" + initialConfiguration);
+                    + ", initialConfiguration=" + initialConfiguration
+                    + ", isInteractive=" + isInteractive + ", isIdle=" + isIdle);
         }
 
         IWifiNanIface iface = mHal.getWifiNanIface();
@@ -131,7 +238,8 @@
                 req.configParams.includeSubscribeServiceIdsInBeacon = true;
                 req.configParams.numberOfSubscribeServiceIdsInBeacon = 0;
                 req.configParams.rssiWindowSize = 8;
-                req.configParams.macAddressRandomizationIntervalSec = 1800;
+                req.configParams.macAddressRandomizationIntervalSec = mSettableParameters.get(
+                        PARAM_MAC_RANDOM_INTERVAL_SEC);
 
                 NanBandSpecificConfig config24 = new NanBandSpecificConfig();
                 config24.rssiClose = 60;
@@ -187,6 +295,8 @@
                 req.debugConfigs.useSdfInBandVal[NanBandIndex.NAN_BAND_24GHZ] = true;
                 req.debugConfigs.useSdfInBandVal[NanBandIndex.NAN_BAND_5GHZ] = true;
 
+                updateConfigForPowerSettings(req.configParams, isInteractive, isIdle);
+
                 status = iface.enableRequest(transactionId, req);
             } else {
                 NanConfigRequest req = new NanConfigRequest();
@@ -199,7 +309,8 @@
                 req.includeSubscribeServiceIdsInBeacon = true;
                 req.numberOfSubscribeServiceIdsInBeacon = 0;
                 req.rssiWindowSize = 8;
-                req.macAddressRandomizationIntervalSec = 1800;
+                req.macAddressRandomizationIntervalSec = mSettableParameters.get(
+                        PARAM_MAC_RANDOM_INTERVAL_SEC);
 
                 NanBandSpecificConfig config24 = new NanBandSpecificConfig();
                 config24.rssiClose = 60;
@@ -235,6 +346,8 @@
                 }
                 req.bandSpecificConfig[NanBandIndex.NAN_BAND_5GHZ] = config5;
 
+                updateConfigForPowerSettings(req, isInteractive, isIdle);
+
                 status = iface.configRequest(transactionId, req);
             }
             if (status.code == WifiStatusCode.SUCCESS) {
@@ -287,9 +400,10 @@
      *            session.
      * @param publishConfig Configuration of the discovery session.
      */
-    public boolean publish(short transactionId, int publishId, PublishConfig publishConfig) {
+    public boolean publish(short transactionId, byte publishId, PublishConfig publishConfig) {
         if (VDBG) {
-            Log.d(TAG, "publish: transactionId=" + transactionId + ", config=" + publishConfig);
+            Log.d(TAG, "publish: transactionId=" + transactionId + ", publishId=" + publishId
+                    + ", config=" + publishConfig);
         }
 
         IWifiNanIface iface = mHal.getWifiNanIface();
@@ -299,13 +413,12 @@
         }
 
         NanPublishRequest req = new NanPublishRequest();
-        req.baseConfigs.sessionId = 0;
+        req.baseConfigs.sessionId = publishId;
         req.baseConfigs.ttlSec = (short) publishConfig.mTtlSec;
         req.baseConfigs.discoveryWindowPeriod = 1;
         req.baseConfigs.discoveryCount = 0;
         convertNativeByteArrayToArrayList(publishConfig.mServiceName, req.baseConfigs.serviceName);
-        // TODO: what's the right value on publish?
-        req.baseConfigs.discoveryMatchIndicator = NanMatchAlg.MATCH_ONCE;
+        req.baseConfigs.discoveryMatchIndicator = NanMatchAlg.MATCH_NEVER;
         convertNativeByteArrayToArrayList(publishConfig.mServiceSpecificInfo,
                 req.baseConfigs.serviceSpecificInfo);
         convertNativeByteArrayToArrayList(publishConfig.mMatchFilter,
@@ -348,10 +461,11 @@
      *            subscribe session.
      * @param subscribeConfig Configuration of the discovery session.
      */
-    public boolean subscribe(short transactionId, int subscribeId,
+    public boolean subscribe(short transactionId, byte subscribeId,
             SubscribeConfig subscribeConfig) {
         if (VDBG) {
-            Log.d(TAG, "subscribe: transactionId=" + transactionId + ", config=" + subscribeConfig);
+            Log.d(TAG, "subscribe: transactionId=" + transactionId + ", subscribeId=" + subscribeId
+                    + ", config=" + subscribeConfig);
         }
 
         IWifiNanIface iface = mHal.getWifiNanIface();
@@ -361,7 +475,7 @@
         }
 
         NanSubscribeRequest req = new NanSubscribeRequest();
-        req.baseConfigs.sessionId = 0;
+        req.baseConfigs.sessionId = subscribeId;
         req.baseConfigs.ttlSec = (short) subscribeConfig.mTtlSec;
         req.baseConfigs.discoveryWindowPeriod = 1;
         req.baseConfigs.discoveryCount = 0;
@@ -413,14 +527,16 @@
      * @param messageId Arbitary integer from host (not sent to HAL - useful for
      *                  testing/debugging at this level)
      */
-    public boolean sendMessage(short transactionId, int pubSubId, int requestorInstanceId,
+    public boolean sendMessage(short transactionId, byte pubSubId, int requestorInstanceId,
             byte[] dest, byte[] message, int messageId) {
         if (VDBG) {
             Log.d(TAG,
                     "sendMessage: transactionId=" + transactionId + ", pubSubId=" + pubSubId
                             + ", requestorInstanceId=" + requestorInstanceId + ", dest="
-                            + String.valueOf(HexEncoding.encode(dest)) + ", messageId="
-                            + messageId);
+                            + String.valueOf(HexEncoding.encode(dest)) + ", messageId=" + messageId
+                            + ", message=" + (message == null ? "<null>"
+                            : HexEncoding.encode(message)) + ", message.length=" + (message == null
+                            ? 0 : message.length));
         }
 
         IWifiNanIface iface = mHal.getWifiNanIface();
@@ -430,7 +546,7 @@
         }
 
         NanTransmitFollowupRequest req = new NanTransmitFollowupRequest();
-        req.discoverySessionId = (byte) pubSubId;
+        req.discoverySessionId = pubSubId;
         req.peerId = requestorInstanceId;
         copyArray(dest, req.addr);
         req.isHighPriority = false;
@@ -460,7 +576,7 @@
      * @param pubSubId ID of the publish/subscribe session - obtained when
      *            creating a session.
      */
-    public boolean stopPublish(short transactionId, int pubSubId) {
+    public boolean stopPublish(short transactionId, byte pubSubId) {
         if (VDBG) {
             Log.d(TAG, "stopPublish: transactionId=" + transactionId + ", pubSubId=" + pubSubId);
         }
@@ -472,7 +588,7 @@
         }
 
         try {
-            WifiStatus status = iface.stopPublishRequest(transactionId, (byte) pubSubId);
+            WifiStatus status = iface.stopPublishRequest(transactionId, pubSubId);
             if (status.code == WifiStatusCode.SUCCESS) {
                 return true;
             } else {
@@ -493,7 +609,7 @@
      * @param pubSubId ID of the publish/subscribe session - obtained when
      *            creating a session.
      */
-    public boolean stopSubscribe(short transactionId, int pubSubId) {
+    public boolean stopSubscribe(short transactionId, byte pubSubId) {
         if (VDBG) {
             Log.d(TAG, "stopSubscribe: transactionId=" + transactionId + ", pubSubId=" + pubSubId);
         }
@@ -505,7 +621,7 @@
         }
 
         try {
-            WifiStatus status = iface.stopSubscribeRequest(transactionId, (byte) pubSubId);
+            WifiStatus status = iface.stopSubscribeRequest(transactionId, pubSubId);
             if (status.code == WifiStatusCode.SUCCESS) {
                 return true;
             } else {
@@ -605,7 +721,7 @@
      */
     public boolean initiateDataPath(short transactionId, int peerId, int channelRequestType,
             int channel, byte[] peer, String interfaceName, byte[] pmk, String passphrase,
-            Capabilities capabilities) {
+            boolean isOutOfBand, Capabilities capabilities) {
         if (VDBG) {
             Log.v(TAG, "initiateDataPath: transactionId=" + transactionId + ", peerId=" + peerId
                     + ", channelRequestType=" + channelRequestType + ", channel=" + channel
@@ -644,6 +760,12 @@
             convertNativeByteArrayToArrayList(passphrase.getBytes(), req.securityConfig.passphrase);
         }
 
+        if (req.securityConfig.securityType != NanDataPathSecurityType.OPEN && isOutOfBand) {
+            convertNativeByteArrayToArrayList(
+                    SERVICE_NAME_FOR_OOB_DATA_PATH.getBytes(StandardCharsets.UTF_8),
+                    req.serviceNameOutOfBand);
+        }
+
         try {
             WifiStatus status = iface.initiateDataPathRequest(transactionId, req);
             if (status.code == WifiStatusCode.SUCCESS) {
@@ -670,10 +792,13 @@
      *                      request callback.
      * @param pmk Pairwise master key (PMK - see IEEE 802.11i) for the data-path.
      * @param passphrase  Passphrase for the data-path.
+     * @param isOutOfBand Is the data-path out-of-band (i.e. without a corresponding Aware discovery
+     *                    session).
      * @param capabilities The capabilities of the firmware.
      */
     public boolean respondToDataPathRequest(short transactionId, boolean accept, int ndpId,
-            String interfaceName, byte[] pmk, String passphrase, Capabilities capabilities) {
+            String interfaceName, byte[] pmk, String passphrase, boolean isOutOfBand,
+            Capabilities capabilities) {
         if (VDBG) {
             Log.v(TAG, "respondToDataPathRequest: transactionId=" + transactionId + ", accept="
                     + accept + ", int ndpId=" + ndpId + ", interfaceName=" + interfaceName);
@@ -708,6 +833,12 @@
             convertNativeByteArrayToArrayList(passphrase.getBytes(), req.securityConfig.passphrase);
         }
 
+        if (req.securityConfig.securityType != NanDataPathSecurityType.OPEN && isOutOfBand) {
+            convertNativeByteArrayToArrayList(
+                    SERVICE_NAME_FOR_OOB_DATA_PATH.getBytes(StandardCharsets.UTF_8),
+                    req.serviceNameOutOfBand);
+        }
+
         try {
             WifiStatus status = iface.respondToDataPathIndicationRequest(transactionId, req);
             if (status.code == WifiStatusCode.SUCCESS) {
@@ -758,6 +889,38 @@
     // utilities
 
     /**
+     * Update the NAN configuration to reflect the current power settings.
+     */
+    private void updateConfigForPowerSettings(NanConfigRequest req, boolean isInteractive,
+            boolean isIdle) {
+        if (isIdle) { // lowest power state: doze
+            updateSingleConfigForPowerSettings(req.bandSpecificConfig[NanBandIndex.NAN_BAND_5GHZ],
+                    mSettableParameters.get(PARAM_DW_ON_IDLE_5GHZ));
+            updateSingleConfigForPowerSettings(req.bandSpecificConfig[NanBandIndex.NAN_BAND_24GHZ],
+                    mSettableParameters.get(PARAM_DW_ON_IDLE_24GHZ));
+        } else if (!isInteractive) { // intermediate power state: inactive
+            updateSingleConfigForPowerSettings(req.bandSpecificConfig[NanBandIndex.NAN_BAND_5GHZ],
+                    mSettableParameters.get(PARAM_DW_ON_INACTIVE_5GHZ));
+            updateSingleConfigForPowerSettings(req.bandSpecificConfig[NanBandIndex.NAN_BAND_24GHZ],
+                    mSettableParameters.get(PARAM_DW_ON_INACTIVE_24GHZ));
+        } else { // the default state
+            updateSingleConfigForPowerSettings(req.bandSpecificConfig[NanBandIndex.NAN_BAND_5GHZ],
+                    mSettableParameters.get(PARAM_DW_DEFAULT_5GHZ));
+            updateSingleConfigForPowerSettings(req.bandSpecificConfig[NanBandIndex.NAN_BAND_24GHZ],
+                    mSettableParameters.get(PARAM_DW_DEFAULT_24GHZ));
+        }
+
+        // else do nothing - normal power state
+    }
+
+    private void updateSingleConfigForPowerSettings(NanBandSpecificConfig cfg, int override) {
+        if (override != -1) {
+            cfg.validDiscoveryWindowIntervalVal = true;
+            cfg.discoveryWindowIntervalVal = (byte) override;
+        }
+    }
+
+    /**
      * Returns the strongest supported cipher suite.
      *
      * Baseline is very simple: 256 > 128 > 0.
@@ -820,6 +983,8 @@
      * Dump the internal state of the class.
      */
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("WifiAwareNativeApi:");
+        pw.println("  mSettableParameters: " + mSettableParameters);
         mHal.dump(fd, pw, args);
     }
 }
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareNativeCallback.java b/service/java/com/android/server/wifi/aware/WifiAwareNativeCallback.java
index 6f1925f..05721af 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareNativeCallback.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareNativeCallback.java
@@ -26,17 +26,25 @@
 import android.hardware.wifi.V1_0.NanMatchInd;
 import android.hardware.wifi.V1_0.NanStatusType;
 import android.hardware.wifi.V1_0.WifiNanStatus;
+import android.os.ShellCommand;
 import android.util.Log;
+import android.util.SparseIntArray;
 
 import libcore.util.HexEncoding;
 
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 
 /**
  * Manages the callbacks from Wi-Fi Aware HIDL (HAL).
  */
-public class WifiAwareNativeCallback extends IWifiNanIfaceEventCallback.Stub {
+public class WifiAwareNativeCallback extends IWifiNanIfaceEventCallback.Stub implements
+        WifiAwareShellCommand.DelegatedShellCommand {
     private static final String TAG = "WifiAwareNativeCallback";
     private static final boolean DBG = false;
     private static final boolean VDBG = false;
@@ -47,6 +55,89 @@
         mWifiAwareStateManager = wifiAwareStateManager;
     }
 
+    /*
+     * Counts of callbacks from HAL. Retrievable through shell command.
+     */
+    private static final int CB_EV_CLUSTER = 0;
+    private static final int CB_EV_DISABLED = 1;
+    private static final int CB_EV_PUBLISH_TERMINATED = 2;
+    private static final int CB_EV_SUBSCRIBE_TERMINATED = 3;
+    private static final int CB_EV_MATCH = 4;
+    private static final int CB_EV_MATCH_EXPIRED = 5;
+    private static final int CB_EV_FOLLOWUP_RECEIVED = 6;
+    private static final int CB_EV_TRANSMIT_FOLLOWUP = 7;
+    private static final int CB_EV_DATA_PATH_REQUEST = 8;
+    private static final int CB_EV_DATA_PATH_CONFIRM = 9;
+    private static final int CB_EV_DATA_PATH_TERMINATED = 10;
+
+    private SparseIntArray mCallbackCounter = new SparseIntArray();
+
+    private void incrementCbCount(int callbackId) {
+        mCallbackCounter.put(callbackId, mCallbackCounter.get(callbackId) + 1);
+    }
+
+    /**
+     * Interpreter of adb shell command 'adb shell wifiaware native_cb ...'.
+     *
+     * @return -1 if parameter not recognized or invalid value, 0 otherwise.
+     */
+    @Override
+    public int onCommand(ShellCommand parentShell) {
+        final PrintWriter pwe = parentShell.getErrPrintWriter();
+        final PrintWriter pwo = parentShell.getOutPrintWriter();
+
+        String subCmd = parentShell.getNextArgRequired();
+        if (VDBG) Log.v(TAG, "onCommand: subCmd='" + subCmd + "'");
+        switch (subCmd) {
+            case "get_cb_count": {
+                String option = parentShell.getNextOption();
+                Log.v(TAG, "option='" + option + "'");
+                boolean reset = false;
+                if (option != null) {
+                    if ("--reset".equals(option)) {
+                        reset = true;
+                    } else {
+                        pwe.println("Unknown option to 'get_cb_count'");
+                        return -1;
+                    }
+                }
+
+                JSONObject j = new JSONObject();
+                try {
+                    for (int i = 0; i < mCallbackCounter.size(); ++i) {
+                        j.put(Integer.toString(mCallbackCounter.keyAt(i)),
+                                mCallbackCounter.valueAt(i));
+                    }
+                } catch (JSONException e) {
+                    Log.e(TAG, "onCommand: get_cb_count e=" + e);
+                }
+                pwo.println(j.toString());
+                if (reset) {
+                    mCallbackCounter.clear();
+                }
+                return 0;
+            }
+            default:
+                pwe.println("Unknown 'wifiaware native_cb <cmd>'");
+        }
+
+        return -1;
+    }
+
+    @Override
+    public void onReset() {
+        // NOP (onReset is intended for configuration reset - not data reset)
+    }
+
+    @Override
+    public void onHelp(String command, ShellCommand parentShell) {
+        final PrintWriter pw = parentShell.getOutPrintWriter();
+
+        pw.println("  " + command);
+        pw.println("    get_cb_count [--reset]: gets the number of callbacks (and optionally reset "
+                + "count)");
+    }
+
     @Override
     public void notifyCapabilitiesResponse(short id, WifiNanStatus status,
             NanCapabilities capabilities) {
@@ -76,6 +167,11 @@
                     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 + " ("
@@ -87,7 +183,12 @@
     public void notifyEnableResponse(short id, WifiNanStatus status) {
         if (VDBG) Log.v(TAG, "notifyEnableResponse: id=" + id + ", status=" + statusString(status));
 
-        if (status.status == NanStatusType.SUCCESS) {
+        if (status.status == NanStatusType.ALREADY_ENABLED) {
+            Log.wtf(TAG, "notifyEnableResponse: id=" + id + ", already enabled!?");
+        }
+
+        if (status.status == NanStatusType.SUCCESS
+                || status.status == NanStatusType.ALREADY_ENABLED) {
             mWifiAwareStateManager.onConfigSuccessResponse(id);
         } else {
             mWifiAwareStateManager.onConfigFailedResponse(id, status.status);
@@ -111,12 +212,11 @@
             Log.v(TAG, "notifyDisableResponse: id=" + id + ", status=" + statusString(status));
         }
 
-        if (status.status == NanStatusType.SUCCESS) {
-            // NOP
-        } else {
+        if (status.status != NanStatusType.SUCCESS) {
             Log.e(TAG, "notifyDisableResponse: failure - code=" + status.status + " ("
                     + status.description + ")");
         }
+        mWifiAwareStateManager.onDisableResponse(id, status.status);
     }
 
     @Override
@@ -255,6 +355,7 @@
             Log.v(TAG, "eventClusterEvent: eventType=" + event.eventType + ", addr="
                     + String.valueOf(HexEncoding.encode(event.addr)));
         }
+        incrementCbCount(CB_EV_CLUSTER);
 
         if (event.eventType == NanClusterEventType.DISCOVERY_MAC_ADDRESS_CHANGED) {
             mWifiAwareStateManager.onInterfaceAddressChangeNotification(event.addr);
@@ -272,6 +373,7 @@
     @Override
     public void eventDisabled(WifiNanStatus status) {
         if (VDBG) Log.v(TAG, "eventDisabled: status=" + statusString(status));
+        incrementCbCount(CB_EV_DISABLED);
 
         mWifiAwareStateManager.onAwareDownNotification(status.status);
     }
@@ -282,6 +384,7 @@
             Log.v(TAG, "eventPublishTerminated: sessionId=" + sessionId + ", status="
                     + statusString(status));
         }
+        incrementCbCount(CB_EV_PUBLISH_TERMINATED);
 
         mWifiAwareStateManager.onSessionTerminatedNotification(sessionId, status.status, true);
     }
@@ -292,6 +395,7 @@
             Log.v(TAG, "eventSubscribeTerminated: sessionId=" + sessionId + ", status="
                     + statusString(status));
         }
+        incrementCbCount(CB_EV_SUBSCRIBE_TERMINATED);
 
         mWifiAwareStateManager.onSessionTerminatedNotification(sessionId, status.status, false);
     }
@@ -302,9 +406,13 @@
             Log.v(TAG, "eventMatch: discoverySessionId=" + event.discoverySessionId + ", peerId="
                     + event.peerId + ", addr=" + String.valueOf(HexEncoding.encode(event.addr))
                     + ", serviceSpecificInfo=" + Arrays.toString(
-                    convertArrayListToNativeByteArray(event.serviceSpecificInfo)) + ", matchFilter="
-                    + Arrays.toString(convertArrayListToNativeByteArray(event.matchFilter)));
+                    convertArrayListToNativeByteArray(event.serviceSpecificInfo)) + ", ssi.size()="
+                    + (event.serviceSpecificInfo == null ? 0 : event.serviceSpecificInfo.size())
+                    + ", matchFilter=" + Arrays.toString(
+                    convertArrayListToNativeByteArray(event.matchFilter)) + ", mf.size()=" + (
+                    event.matchFilter == null ? 0 : event.matchFilter.size()));
         }
+        incrementCbCount(CB_EV_MATCH);
 
         mWifiAwareStateManager.onMatchNotification(event.discoverySessionId, event.peerId,
                 event.addr, convertArrayListToNativeByteArray(event.serviceSpecificInfo),
@@ -317,6 +425,7 @@
             Log.v(TAG, "eventMatchExpired: discoverySessionId=" + discoverySessionId
                     + ", peerId=" + peerId);
         }
+        incrementCbCount(CB_EV_MATCH_EXPIRED);
 
         // NOP
     }
@@ -326,8 +435,11 @@
         if (VDBG) {
             Log.v(TAG, "eventFollowupReceived: discoverySessionId=" + event.discoverySessionId
                     + ", peerId=" + event.peerId + ", addr=" + String.valueOf(
-                    HexEncoding.encode(event.addr)));
+                    HexEncoding.encode(event.addr)) + ", serviceSpecificInfo=" + Arrays.toString(
+                    convertArrayListToNativeByteArray(event.serviceSpecificInfo)) + ", ssi.size()="
+                    + (event.serviceSpecificInfo == null ? 0 : event.serviceSpecificInfo.size()));
         }
+        incrementCbCount(CB_EV_FOLLOWUP_RECEIVED);
 
         mWifiAwareStateManager.onMessageReceivedNotification(event.discoverySessionId, event.peerId,
                 event.addr, convertArrayListToNativeByteArray(event.serviceSpecificInfo));
@@ -338,6 +450,7 @@
         if (VDBG) {
             Log.v(TAG, "eventTransmitFollowup: id=" + id + ", status=" + statusString(status));
         }
+        incrementCbCount(CB_EV_TRANSMIT_FOLLOWUP);
 
         if (status.status == NanStatusType.SUCCESS) {
             mWifiAwareStateManager.onMessageSendSuccessNotification(id);
@@ -354,6 +467,7 @@
                     HexEncoding.encode(event.peerDiscMacAddr)) + ", ndpInstanceId="
                     + event.ndpInstanceId);
         }
+        incrementCbCount(CB_EV_DATA_PATH_REQUEST);
 
         mWifiAwareStateManager.onDataPathRequestNotification(event.discoverySessionId,
                 event.peerDiscMacAddr, event.ndpInstanceId);
@@ -367,6 +481,7 @@
                     + ", dataPathSetupSuccess=" + event.dataPathSetupSuccess + ", reason="
                     + event.status.status);
         }
+        incrementCbCount(CB_EV_DATA_PATH_CONFIRM);
 
         mWifiAwareStateManager.onDataPathConfirmNotification(event.ndpInstanceId,
                 event.peerNdiMacAddr, event.dataPathSetupSuccess, event.status.status,
@@ -376,10 +491,20 @@
     @Override
     public void eventDataPathTerminated(int ndpInstanceId) {
         if (VDBG) Log.v(TAG, "eventDataPathTerminated: ndpInstanceId=" + ndpInstanceId);
+        incrementCbCount(CB_EV_DATA_PATH_TERMINATED);
 
         mWifiAwareStateManager.onDataPathEndNotification(ndpInstanceId);
     }
 
+    /**
+     * Dump the internal state of the class.
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("WifiAwareNativeCallback:");
+        pw.println("  mCallbackCounter: " + mCallbackCounter);
+    }
+
+
     // utilities
 
     /**
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareNativeManager.java b/service/java/com/android/server/wifi/aware/WifiAwareNativeManager.java
index 22f1385..e855d81 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareNativeManager.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareNativeManager.java
@@ -23,6 +23,7 @@
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.wifi.HalDeviceManager;
 
 import java.io.FileDescriptor;
@@ -31,7 +32,7 @@
 /**
  * Manages the interface to Wi-Fi Aware HIDL (HAL).
  */
-class WifiAwareNativeManager {
+public class WifiAwareNativeManager {
     private static final String TAG = "WifiAwareNativeManager";
     private static final boolean DBG = false;
 
@@ -53,6 +54,10 @@
         mWifiAwareStateManager = awareStateManager;
         mHalDeviceManager = halDeviceManager;
         mWifiAwareNativeCallback = wifiAwareNativeCallback;
+    }
+
+    public void start() {
+        mHalDeviceManager.initialize();
         mHalDeviceManager.registerStatusListener(
                 new HalDeviceManager.ManagerStatusListener() {
                     @Override
@@ -71,16 +76,26 @@
                     }
                 }, null);
         if (mHalDeviceManager.isStarted()) {
+            mHalDeviceManager.registerInterfaceAvailableForRequestListener(
+                    IfaceType.NAN, mInterfaceAvailableForRequestListener, null);
             tryToGetAware();
         }
     }
 
-    /* package */ IWifiNanIface getWifiNanIface() {
+    /**
+     * Returns the native HAL WifiNanIface through which commands to the NAN HAL are dispatched.
+     * Return may be null if not initialized/available.
+     */
+    @VisibleForTesting
+    public IWifiNanIface getWifiNanIface() {
         synchronized (mLock) {
             return mWifiNanIface;
         }
     }
 
+    /**
+     * Attempt to obtain the HAL NAN interface. If available then enables Aware usage.
+     */
     private void tryToGetAware() {
         synchronized (mLock) {
             if (DBG) Log.d(TAG, "tryToGetAware: mWifiNanIface=" + mWifiNanIface);
@@ -157,6 +172,7 @@
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("WifiAwareNativeManager:");
         pw.println("  mWifiNanIface: " + mWifiNanIface);
+        mWifiAwareNativeCallback.dump(fd, pw, args);
         mHalDeviceManager.dump(fd, pw, args);
     }
 }
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareService.java b/service/java/com/android/server/wifi/aware/WifiAwareService.java
index 75efa02..e210d3e 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareService.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareService.java
@@ -53,7 +53,6 @@
             }
 
             HalDeviceManager halDeviceManager = wifiInjector.getHalDeviceManager();
-            halDeviceManager.initialize();
 
             WifiAwareStateManager wifiAwareStateManager = new WifiAwareStateManager();
             WifiAwareNativeCallback wifiAwareNativeCallback = new WifiAwareNativeCallback(
@@ -61,10 +60,16 @@
             WifiAwareNativeManager wifiAwareNativeManager = new WifiAwareNativeManager(
                     wifiAwareStateManager, halDeviceManager, wifiAwareNativeCallback);
             WifiAwareNativeApi wifiAwareNativeApi = new WifiAwareNativeApi(wifiAwareNativeManager);
-            wifiAwareStateManager.setNative(wifiAwareNativeApi);
+            wifiAwareStateManager.setNative(wifiAwareNativeManager, wifiAwareNativeApi);
+            WifiAwareShellCommand wifiAwareShellCommand = new WifiAwareShellCommand();
+            wifiAwareShellCommand.register("native_api", wifiAwareNativeApi);
+            wifiAwareShellCommand.register("native_cb", wifiAwareNativeCallback);
+            wifiAwareShellCommand.register("state_mgr", wifiAwareStateManager);
 
             HandlerThread awareHandlerThread = wifiInjector.getWifiAwareHandlerThread();
-            mImpl.start(awareHandlerThread, wifiAwareStateManager);
+            mImpl.start(awareHandlerThread, wifiAwareStateManager, wifiAwareShellCommand,
+                    wifiInjector.getWifiMetrics().getWifiAwareMetrics(),
+                    wifiInjector.getWifiPermissionsWrapper());
         } else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
             mImpl.startLate();
         }
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareServiceImpl.java b/service/java/com/android/server/wifi/aware/WifiAwareServiceImpl.java
index fa695cd..b77ae63 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareServiceImpl.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareServiceImpl.java
@@ -32,10 +32,14 @@
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
 import android.util.Log;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 
+import com.android.server.wifi.util.WifiPermissionsWrapper;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.Arrays;
@@ -53,6 +57,7 @@
 
     private Context mContext;
     private WifiAwareStateManager mStateManager;
+    private WifiAwareShellCommand mShellCommand;
 
     private final Object mLock = new Object();
     private final SparseArray<IBinder.DeathRecipient> mDeathRecipientsByClientId =
@@ -77,11 +82,14 @@
      * Start the service: allocate a new thread (for now), start the handlers of
      * the components of the service.
      */
-    public void start(HandlerThread handlerThread, WifiAwareStateManager awareStateManager) {
+    public void start(HandlerThread handlerThread, WifiAwareStateManager awareStateManager,
+            WifiAwareShellCommand awareShellCommand, WifiAwareMetrics awareMetrics,
+            WifiPermissionsWrapper permissionsWrapper) {
         Log.i(TAG, "Starting Wi-Fi Aware service");
 
         mStateManager = awareStateManager;
-        mStateManager.start(mContext, handlerThread.getLooper());
+        mShellCommand = awareShellCommand;
+        mStateManager.start(mContext, handlerThread.getLooper(), awareMetrics, permissionsWrapper);
     }
 
     /**
@@ -371,6 +379,12 @@
     }
 
     @Override
+    public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+            String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
+        mShellCommand.exec(this, in, out, err, args, callback, resultReceiver);
+    }
+
+    @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (mContext.checkCallingOrSelfPermission(
                 android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) {
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareShellCommand.java b/service/java/com/android/server/wifi/aware/WifiAwareShellCommand.java
new file mode 100644
index 0000000..41eef69
--- /dev/null
+++ b/service/java/com/android/server/wifi/aware/WifiAwareShellCommand.java
@@ -0,0 +1,124 @@
+/*
+ * 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.aware;
+
+import android.os.Binder;
+import android.os.ShellCommand;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Interprets and executes 'adb shell cmd wifiaware [args]'.
+ */
+public class WifiAwareShellCommand extends ShellCommand {
+    private static final String TAG = "WifiAwareShellCommand";
+
+    private Map<String, DelegatedShellCommand> mDelegatedCommands = new HashMap<>();
+
+    /**
+     * Register an delegated command interpreter for the specified 'command'. Each class can
+     * interpret and execute their own commands.
+     */
+    public void register(String command, DelegatedShellCommand shellCommand) {
+        if (mDelegatedCommands.containsKey(command)) {
+            Log.e(TAG, "register: overwriting existing command -- '" + command + "'");
+        }
+
+        mDelegatedCommands.put(command, shellCommand);
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        checkRootPermission();
+
+        final PrintWriter pw = getErrPrintWriter();
+        try {
+            if ("reset".equals(cmd)) {
+                for (DelegatedShellCommand dsc: mDelegatedCommands.values()) {
+                    dsc.onReset();
+                }
+            } else {
+                DelegatedShellCommand delegatedCmd = null;
+                if (!TextUtils.isEmpty(cmd)) {
+                    delegatedCmd = mDelegatedCommands.get(cmd);
+                }
+
+                if (delegatedCmd != null) {
+                    return delegatedCmd.onCommand(this);
+                } else {
+                    return handleDefaultCommands(cmd);
+                }
+            }
+        } catch (Exception e) {
+            pw.println("Exception: " + e);
+        }
+        return -1;
+    }
+
+    private void checkRootPermission() {
+        final int uid = Binder.getCallingUid();
+        if (uid == 0) {
+            // Root can do anything.
+            return;
+        }
+        throw new SecurityException("Uid " + uid + " does not have access to wifiaware commands");
+    }
+
+    @Override
+    public void onHelp() {
+        final PrintWriter pw = getOutPrintWriter();
+
+        pw.println("Wi-Fi Aware (wifiaware) commands:");
+        pw.println("  help");
+        pw.println("    Print this help text.");
+        pw.println("  reset");
+        pw.println("    Reset parameters to default values.");
+        for (Map.Entry<String, DelegatedShellCommand> sce: mDelegatedCommands.entrySet()) {
+            sce.getValue().onHelp(sce.getKey(), this);
+        }
+        pw.println();
+    }
+
+    /**
+     * Interface that delegated command targets must implement. They are passed the parent shell
+     * command (the real command interpreter) from which they can obtain arguments.
+     */
+    public interface DelegatedShellCommand {
+        /**
+         * Execute the specified command. Use the parent shell to obtain arguments. Note that the
+         * first argument (which specified the delegated shell) has already been extracted.
+         */
+        int onCommand(ShellCommand parentShell);
+
+        /**
+         * Reset all parameters to their default values.
+         */
+        void onReset();
+
+        /**
+         * Print out help for the delegated command. The name of the delegated command is passed
+         * as a first argument as an assist (prevents hard-coding of that string in multiple
+         * places).
+         */
+        void onHelp(String command, ShellCommand parentShell);
+
+    }
+}
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareStateManager.java b/service/java/com/android/server/wifi/aware/WifiAwareStateManager.java
index a35c786..30aa42a 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareStateManager.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareStateManager.java
@@ -16,8 +16,10 @@
 
 package com.android.server.wifi.aware;
 
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.hardware.wifi.V1_0.NanStatusType;
 import android.net.wifi.RttManager;
 import android.net.wifi.aware.Characteristics;
@@ -31,7 +33,9 @@
 import android.os.Bundle;
 import android.os.Looper;
 import android.os.Message;
+import android.os.PowerManager;
 import android.os.RemoteException;
+import android.os.ShellCommand;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.util.ArrayMap;
@@ -44,12 +48,18 @@
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 import com.android.internal.util.WakeupMessage;
+import com.android.server.wifi.util.NativeUtil;
+import com.android.server.wifi.util.WifiPermissionsWrapper;
 
 import libcore.util.HexEncoding;
 
+import org.json.JSONException;
+import org.json.JSONObject;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.Map;
@@ -57,7 +67,7 @@
 /**
  * Manages the state of the Wi-Fi Aware system service.
  */
-public class WifiAwareStateManager {
+public class WifiAwareStateManager implements WifiAwareShellCommand.DelegatedShellCommand {
     private static final String TAG = "WifiAwareStateManager";
     private static final boolean DBG = false;
     private static final boolean VDBG = false; // STOPSHIP if true
@@ -108,6 +118,8 @@
     private static final int COMMAND_TYPE_RESPOND_TO_DATA_PATH_SETUP_REQUEST = 117;
     private static final int COMMAND_TYPE_END_DATA_PATH = 118;
     private static final int COMMAND_TYPE_TRANSMIT_NEXT_MESSAGE = 119;
+    private static final int COMMAND_TYPE_RECONFIGURE = 120;
+    private static final int COMMAND_TYPE_DELAYED_INITIALIZATION = 121;
 
     private static final int RESPONSE_TYPE_ON_CONFIG_SUCCESS = 200;
     private static final int RESPONSE_TYPE_ON_CONFIG_FAIL = 201;
@@ -122,6 +134,7 @@
     private static final int RESPONSE_TYPE_ON_INITIATE_DATA_PATH_FAIL = 210;
     private static final int RESPONSE_TYPE_ON_RESPOND_TO_DATA_PATH_SETUP_REQUEST = 211;
     private static final int RESPONSE_TYPE_ON_END_DATA_PATH = 212;
+    private static final int RESPONSE_TYPE_ON_DISABLE = 213;
 
     private static final int NOTIFICATION_TYPE_INTERFACE_CHANGE = 301;
     private static final int NOTIFICATION_TYPE_CLUSTER_CHANGE = 302;
@@ -171,8 +184,10 @@
     private static final String MESSAGE_BUNDLE_KEY_NOTIFY_IDENTITY_CHANGE = "notify_identity_chg";
     private static final String MESSAGE_BUNDLE_KEY_PMK = "pmk";
     private static final String MESSAGE_BUNDLE_KEY_PASSPHRASE = "passphrase";
+    private static final String MESSAGE_BUNDLE_KEY_OOB = "out_of_band";
 
     private WifiAwareNativeApi mWifiAwareNativeApi;
+    private WifiAwareNativeManager mWifiAwareNativeManager;
 
     /*
      * Asynchronous access with no lock
@@ -184,11 +199,13 @@
      * handler thread: no need to use a lock.
      */
     private Context mContext;
+    private WifiAwareMetrics mAwareMetrics;
     private volatile Capabilities mCapabilities;
     private volatile Characteristics mCharacteristics = null;
     private WifiAwareStateMachine mSm;
     private WifiAwareRttStateManager mRtt;
     private WifiAwareDataPathStateManager mDataPathMgr;
+    private PowerManager mPowerManager;
 
     private final SparseArray<WifiAwareClientState> mClients = new SparseArray<>();
     private ConfigRequest mCurrentAwareConfiguration = null;
@@ -198,37 +215,180 @@
     private byte[] mCurrentDiscoveryInterfaceMac = ALL_ZERO_MAC;
 
     public WifiAwareStateManager() {
-        // empty
+        onReset();
     }
 
-    public void setNative(WifiAwareNativeApi wifiAwareNativeApi) {
+    /**
+     * Inject references to other manager objects. Needed to resolve
+     * circular dependencies and to allow mocking.
+     */
+    public void setNative(WifiAwareNativeManager wifiAwareNativeManager,
+            WifiAwareNativeApi wifiAwareNativeApi) {
+        mWifiAwareNativeManager = wifiAwareNativeManager;
         mWifiAwareNativeApi = wifiAwareNativeApi;
     }
 
+    /*
+     * parameters settable through shell command
+     */
+    public static final String PARAM_ON_IDLE_DISABLE_AWARE = "on_idle_disable_aware";
+    public static final int PARAM_ON_IDLE_DISABLE_AWARE_DEFAULT = 1; // 0 = false, 1 = true
+
+    private Map<String, Integer> mSettableParameters = new HashMap<>();
+
+    /**
+     * Interpreter of adb shell command 'adb shell wifiaware native_api ...'.
+     *
+     * @return -1 if parameter not recognized or invalid value, 0 otherwise.
+     */
+    @Override
+    public int onCommand(ShellCommand parentShell) {
+        final PrintWriter pw_err = parentShell.getErrPrintWriter();
+        final PrintWriter pw_out = parentShell.getOutPrintWriter();
+
+        String subCmd = parentShell.getNextArgRequired();
+        if (VDBG) Log.v(TAG, "onCommand: subCmd='" + subCmd + "'");
+        switch (subCmd) {
+            case "set": {
+                String name = parentShell.getNextArgRequired();
+                if (VDBG) Log.v(TAG, "onCommand: name='" + name + "'");
+                if (!mSettableParameters.containsKey(name)) {
+                    pw_err.println("Unknown parameter name -- '" + name + "'");
+                    return -1;
+                }
+
+                String valueStr = parentShell.getNextArgRequired();
+                if (VDBG) Log.v(TAG, "onCommand: valueStr='" + valueStr + "'");
+                int value;
+                try {
+                    value = Integer.valueOf(valueStr);
+                } catch (NumberFormatException e) {
+                    pw_err.println("Can't convert value to integer -- '" + valueStr + "'");
+                    return -1;
+                }
+                mSettableParameters.put(name, value);
+                return 0;
+            }
+            case "get": {
+                String name = parentShell.getNextArgRequired();
+                if (VDBG) Log.v(TAG, "onCommand: name='" + name + "'");
+                if (!mSettableParameters.containsKey(name)) {
+                    pw_err.println("Unknown parameter name -- '" + name + "'");
+                    return -1;
+                }
+
+                pw_out.println((int) mSettableParameters.get(name));
+                return 0;
+            }
+            case "get_capabilities": {
+                JSONObject j = new JSONObject();
+                if (mCapabilities != null) {
+                    try {
+                        j.put("maxConcurrentAwareClusters",
+                                mCapabilities.maxConcurrentAwareClusters);
+                        j.put("maxPublishes", mCapabilities.maxPublishes);
+                        j.put("maxSubscribes", mCapabilities.maxSubscribes);
+                        j.put("maxServiceNameLen", mCapabilities.maxServiceNameLen);
+                        j.put("maxMatchFilterLen", mCapabilities.maxMatchFilterLen);
+                        j.put("maxTotalMatchFilterLen", mCapabilities.maxTotalMatchFilterLen);
+                        j.put("maxServiceSpecificInfoLen", mCapabilities.maxServiceSpecificInfoLen);
+                        j.put("maxExtendedServiceSpecificInfoLen",
+                                mCapabilities.maxExtendedServiceSpecificInfoLen);
+                        j.put("maxNdiInterfaces", mCapabilities.maxNdiInterfaces);
+                        j.put("maxNdpSessions", mCapabilities.maxNdpSessions);
+                        j.put("maxAppInfoLen", mCapabilities.maxAppInfoLen);
+                        j.put("maxQueuedTransmitMessages", mCapabilities.maxQueuedTransmitMessages);
+                        j.put("maxSubscribeInterfaceAddresses",
+                                mCapabilities.maxSubscribeInterfaceAddresses);
+                        j.put("supportedCipherSuites", mCapabilities.supportedCipherSuites);
+                    } catch (JSONException e) {
+                        Log.e(TAG, "onCommand: get_capabilities e=" + e);
+                    }
+                }
+                pw_out.println(j.toString());
+                return 0;
+            }
+            default:
+                pw_err.println("Unknown 'wifiaware state_mgr <cmd>'");
+        }
+
+        return -1;
+    }
+
+    @Override
+    public void onReset() {
+        mSettableParameters.put(PARAM_ON_IDLE_DISABLE_AWARE, PARAM_ON_IDLE_DISABLE_AWARE_DEFAULT);
+    }
+
+    @Override
+    public void onHelp(String command, ShellCommand parentShell) {
+        final PrintWriter pw = parentShell.getOutPrintWriter();
+
+        pw.println("  " + command);
+        pw.println("    set <name> <value>: sets named parameter to value. Names: "
+                + mSettableParameters.keySet());
+        pw.println("    get <name>: gets named parameter value. Names: "
+                + mSettableParameters.keySet());
+        pw.println("    get_capabilities: prints out the capabilities as a JSON string");
+    }
+
     /**
      * Initialize the handler of the state manager with the specified thread
      * looper.
      *
      * @param looper Thread looper on which to run the handler.
      */
-    public void start(Context context, Looper looper) {
+    public void start(Context context, Looper looper, WifiAwareMetrics awareMetrics,
+            WifiPermissionsWrapper permissionsWrapper) {
         Log.i(TAG, "start()");
 
         mContext = context;
+        mAwareMetrics = awareMetrics;
         mSm = new WifiAwareStateMachine(TAG, looper);
         mSm.setDbg(DBG);
         mSm.start();
 
         mRtt = new WifiAwareRttStateManager();
         mDataPathMgr = new WifiAwareDataPathStateManager(this);
-        mDataPathMgr.start(mContext, mSm.getHandler().getLooper());
+        mDataPathMgr.start(mContext, mSm.getHandler().getLooper(), awareMetrics,
+                permissionsWrapper);
+
+        mPowerManager = mContext.getSystemService(PowerManager.class);
+
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_SCREEN_ON);
+        intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
+        intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+        mContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                String action = intent.getAction();
+                if (VDBG) Log.v(TAG, "BroadcastReceiver: action=" + action);
+                if (action.equals(Intent.ACTION_SCREEN_ON)
+                        || action.equals(Intent.ACTION_SCREEN_OFF)) {
+                    reconfigure();
+                }
+
+                if (action.equals(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)) {
+                    if (mSettableParameters.get(PARAM_ON_IDLE_DISABLE_AWARE) != 0) {
+                        if (mPowerManager.isDeviceIdleMode()) {
+                            disableUsage();
+                        } else {
+                            enableUsage();
+                        }
+                    } else {
+                        reconfigure();
+                    }
+                }
+            }
+        }, intentFilter);
     }
 
     /**
      * Initialize the late-initialization sub-services: depend on other services already existing.
      */
     public void startLate() {
-        mRtt.start(mContext, mSm.getHandler().getLooper());
+        delayedInitialization();
     }
 
     /**
@@ -261,6 +421,15 @@
      */
 
     /**
+     * Place a request for delayed start operation on the state machine queue.
+     */
+    public void delayedInitialization() {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
+        msg.arg1 = COMMAND_TYPE_DELAYED_INITIALIZATION;
+        mSm.sendMessage(msg);
+    }
+
+    /**
      * Place a request for a new client connection on the state machine queue.
      */
     public void connect(int clientId, int uid, int pid, String callingPackage,
@@ -291,6 +460,17 @@
     }
 
     /**
+     * Place a request to reconfigure Aware. No additional input - intended to use current
+     * power settings when executed. Thus possibly entering or exiting power saving mode if
+     * needed (or do nothing if Aware is not active).
+     */
+    public void reconfigure() {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
+        msg.arg1 = COMMAND_TYPE_RECONFIGURE;
+        mSm.sendMessage(msg);
+    }
+
+    /**
      * Place a request to stop a discovery session on the state machine queue.
      */
     public void terminateSession(int clientId, int sessionId) {
@@ -391,6 +571,11 @@
      * only happens when a connection is created.
      */
     public void enableUsage() {
+        if (mSettableParameters.get(PARAM_ON_IDLE_DISABLE_AWARE) != 0
+                && mPowerManager.isDeviceIdleMode()) {
+            Log.d(TAG, "enableUsage(): while device is in IDLE mode - ignoring");
+            return;
+        }
         Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
         msg.arg1 = COMMAND_TYPE_ENABLE_USAGE;
         mSm.sendMessage(msg);
@@ -468,7 +653,7 @@
      */
     public void initiateDataPathSetup(WifiAwareNetworkSpecifier networkSpecifier, int peerId,
             int channelRequestType, int channel, byte[] peer, String interfaceName, byte[] pmk,
-            String passphrase) {
+            String passphrase, boolean isOutOfBand) {
         Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
         msg.arg1 = COMMAND_TYPE_INITIATE_DATA_PATH_SETUP;
         msg.obj = networkSpecifier;
@@ -479,6 +664,7 @@
         msg.getData().putString(MESSAGE_BUNDLE_KEY_INTERFACE_NAME, interfaceName);
         msg.getData().putByteArray(MESSAGE_BUNDLE_KEY_PMK, pmk);
         msg.getData().putString(MESSAGE_BUNDLE_KEY_PASSPHRASE, passphrase);
+        msg.getData().putBoolean(MESSAGE_BUNDLE_KEY_OOB, isOutOfBand);
         mSm.sendMessage(msg);
     }
 
@@ -486,7 +672,7 @@
      * Command to respond to the data-path request (executed by the responder).
      */
     public void respondToDataPathRequest(boolean accept, int ndpId, String interfaceName,
-            byte[] pmk, String passphrase) {
+            byte[] pmk, String passphrase, boolean isOutOfBand) {
         Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
         msg.arg1 = COMMAND_TYPE_RESPOND_TO_DATA_PATH_SETUP_REQUEST;
         msg.arg2 = ndpId;
@@ -494,6 +680,7 @@
         msg.getData().putString(MESSAGE_BUNDLE_KEY_INTERFACE_NAME, interfaceName);
         msg.getData().putByteArray(MESSAGE_BUNDLE_KEY_PMK, pmk);
         msg.getData().putString(MESSAGE_BUNDLE_KEY_PASSPHRASE, passphrase);
+        msg.getData().putBoolean(MESSAGE_BUNDLE_KEY_OOB, isOutOfBand);
         mSm.sendMessage(msg);
     }
 
@@ -549,11 +736,23 @@
     }
 
     /**
+     * Place a callback request on the stage machine queue: disable request finished
+     * (with the provided reason code).
+     */
+    public void onDisableResponse(short transactionId, int reason) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_RESPONSE);
+        msg.arg1 = RESPONSE_TYPE_ON_DISABLE;
+        msg.arg2 = transactionId;
+        msg.obj = reason;
+        mSm.sendMessage(msg);
+    }
+
+    /**
      * Place a callback request on the state machine queue: session
      * configuration (new or update) request succeeded.
      */
     public void onSessionConfigSuccessResponse(short transactionId, boolean isPublish,
-            int pubSubId) {
+            byte pubSubId) {
         Message msg = mSm.obtainMessage(MESSAGE_TYPE_RESPONSE);
         msg.arg1 = RESPONSE_TYPE_ON_SESSION_CONFIG_SUCCESS;
         msg.arg2 = transactionId;
@@ -663,7 +862,7 @@
 
     /**
      * Response from firmware to
-     * {@link #respondToDataPathRequest(boolean, int, String, byte[], String)}.
+     * {@link #respondToDataPathRequest(boolean, int, String, byte[], String, boolean)}
      */
     public void onRespondToDataPathSetupRequestResponse(short transactionId, boolean success,
             int reasonOnFailure) {
@@ -856,7 +1055,7 @@
         private WakeupMessage mSendMessageTimeoutMessage = new WakeupMessage(mContext, getHandler(),
                 HAL_SEND_MESSAGE_TIMEOUT_TAG, MESSAGE_TYPE_SEND_MESSAGE_TIMEOUT);
 
-        private static final long AWARE_WAIT_FOR_DP_CONFIRM_TIMEOUT = 5_000;
+        private static final long AWARE_WAIT_FOR_DP_CONFIRM_TIMEOUT = 20_000;
         private final Map<WifiAwareNetworkSpecifier, WakeupMessage>
                 mDataPathConfirmTimeoutMessages = new ArrayMap<>();
 
@@ -1218,6 +1417,9 @@
                     waitForResponse = disconnectLocal(mCurrentTransactionId, clientId);
                     break;
                 }
+                case COMMAND_TYPE_RECONFIGURE:
+                    waitForResponse = reconfigureLocal(mCurrentTransactionId);
+                    break;
                 case COMMAND_TYPE_TERMINATE_SESSION: {
                     int clientId = msg.arg2;
                     int sessionId = (Integer) msg.obj;
@@ -1321,8 +1523,7 @@
                     waitForResponse = false;
                     break;
                 case COMMAND_TYPE_DISABLE_USAGE:
-                    disableUsageLocal();
-                    waitForResponse = false;
+                    waitForResponse = disableUsageLocal(mCurrentTransactionId);
                     break;
                 case COMMAND_TYPE_START_RANGING: {
                     Bundle data = msg.getData();
@@ -1377,10 +1578,11 @@
                     String interfaceName = data.getString(MESSAGE_BUNDLE_KEY_INTERFACE_NAME);
                     byte[] pmk = data.getByteArray(MESSAGE_BUNDLE_KEY_PMK);
                     String passphrase = data.getString(MESSAGE_BUNDLE_KEY_PASSPHRASE);
+                    boolean isOutOfBand = data.getBoolean(MESSAGE_BUNDLE_KEY_OOB);
 
                     waitForResponse = initiateDataPathSetupLocal(mCurrentTransactionId,
                             networkSpecifier, peerId, channelRequestType, channel, peer,
-                            interfaceName, pmk, passphrase);
+                            interfaceName, pmk, passphrase, isOutOfBand);
 
                     if (waitForResponse) {
                         WakeupMessage timeout = new WakeupMessage(mContext, getHandler(),
@@ -1400,15 +1602,21 @@
                     String interfaceName = data.getString(MESSAGE_BUNDLE_KEY_INTERFACE_NAME);
                     byte[] pmk = data.getByteArray(MESSAGE_BUNDLE_KEY_PMK);
                     String passphrase = data.getString(MESSAGE_BUNDLE_KEY_PASSPHRASE);
+                    boolean isOutOfBand = data.getBoolean(MESSAGE_BUNDLE_KEY_OOB);
 
                     waitForResponse = respondToDataPathRequestLocal(mCurrentTransactionId, accept,
-                            ndpId, interfaceName, pmk, passphrase);
+                            ndpId, interfaceName, pmk, passphrase, isOutOfBand);
 
                     break;
                 }
                 case COMMAND_TYPE_END_DATA_PATH:
                     waitForResponse = endDataPathLocal(mCurrentTransactionId, msg.arg2);
                     break;
+                case COMMAND_TYPE_DELAYED_INITIALIZATION:
+                    mWifiAwareNativeManager.start();
+                    mRtt.start(mContext, mSm.getHandler().getLooper());
+                    waitForResponse = false;
+                    break;
                 default:
                     waitForResponse = false;
                     Log.wtf(TAG, "processCommand: this isn't a COMMAND -- msg=" + msg);
@@ -1447,7 +1655,7 @@
                     break;
                 }
                 case RESPONSE_TYPE_ON_SESSION_CONFIG_SUCCESS: {
-                    int pubSubId = (Integer) msg.obj;
+                    byte pubSubId = (Byte) msg.obj;
                     boolean isPublish = msg.getData().getBoolean(MESSAGE_BUNDLE_KEY_SESSION_TYPE);
 
                     onSessionConfigSuccessLocal(mCurrentCommand, pubSubId, isPublish);
@@ -1535,6 +1743,9 @@
                             msg.getData().getBoolean(MESSAGE_BUNDLE_KEY_SUCCESS_FLAG),
                             msg.getData().getInt(MESSAGE_BUNDLE_KEY_STATUS_CODE));
                     break;
+                case RESPONSE_TYPE_ON_DISABLE:
+                    onDisableResponseLocal(mCurrentCommand, (Integer) msg.obj);
+                    break;
                 default:
                     Log.wtf(TAG, "processResponse: this isn't a RESPONSE -- msg=" + msg);
                     mCurrentCommand = null;
@@ -1566,13 +1777,16 @@
                     break;
                 }
                 case COMMAND_TYPE_DISCONNECT: {
-                    /*
-                     * Will only get here on DISCONNECT if was downgrading. The
-                     * callback will do a NOP - but should still call it.
-                     */
                     onConfigFailedLocal(mCurrentCommand, NanStatusType.INTERNAL_FAILURE);
                     break;
                 }
+                case COMMAND_TYPE_RECONFIGURE:
+                    /*
+                     * Reconfigure timed-out. There is nothing to do but log the issue - which
+                      * will be done in the callback.
+                     */
+                    onConfigFailedLocal(mCurrentCommand, NanStatusType.INTERNAL_FAILURE);
+                    break;
                 case COMMAND_TYPE_TERMINATE_SESSION: {
                     Log.wtf(TAG, "processTimeout: TERMINATE_SESSION - shouldn't be waiting!");
                     break;
@@ -1651,6 +1865,11 @@
                     // TODO: fix status: timeout
                     onEndPathEndResponseLocal(mCurrentCommand, false, 0);
                     break;
+                case COMMAND_TYPE_DELAYED_INITIALIZATION:
+                    Log.wtf(TAG,
+                            "processTimeout: COMMAND_TYPE_DELAYED_INITIALIZATION - shouldn't be "
+                                    + "waiting!");
+                    break;
                 default:
                     Log.wtf(TAG, "processTimeout: this isn't a COMMAND -- msg=" + msg);
                     /* fall-through */
@@ -1775,6 +1994,12 @@
 
         if (!mUsageEnabled) {
             Log.w(TAG, "connect(): called with mUsageEnabled=false");
+            try {
+                callback.onConnectFail(NanStatusType.INTERNAL_FAILURE);
+                mAwareMetrics.recordAttachStatus(NanStatusType.INTERNAL_FAILURE);
+            } catch (RemoteException e) {
+                Log.w(TAG, "connectLocal onConnectFail(): RemoteException (FYI): " + e);
+            }
             return false;
         }
 
@@ -1793,6 +2018,7 @@
                     + ", incompatible with current configurations");
             try {
                 callback.onConnectFail(NanStatusType.INTERNAL_FAILURE);
+                mAwareMetrics.recordAttachStatus(NanStatusType.INTERNAL_FAILURE);
             } catch (RemoteException e) {
                 Log.w(TAG, "connectLocal onConnectFail(): RemoteException (FYI): " + e);
             }
@@ -1802,26 +2028,30 @@
         }
 
         if (mCurrentAwareConfiguration != null && mCurrentAwareConfiguration.equals(merged)
-                && mCurrentIdentityNotification == notifyIdentityChange) {
+                && (mCurrentIdentityNotification || !notifyIdentityChange)) {
             try {
                 callback.onConnectSuccess(clientId);
             } catch (RemoteException e) {
                 Log.w(TAG, "connectLocal onConnectSuccess(): RemoteException (FYI): " + e);
             }
             WifiAwareClientState client = new WifiAwareClientState(mContext, clientId, uid, pid,
-                    callingPackage, callback, configRequest, notifyIdentityChange);
+                    callingPackage, callback, configRequest, notifyIdentityChange,
+                    SystemClock.elapsedRealtime());
             client.onInterfaceAddressChange(mCurrentDiscoveryInterfaceMac);
             mClients.append(clientId, client);
+            mAwareMetrics.recordAttachSession(uid, notifyIdentityChange, mClients);
             return false;
         }
         boolean notificationRequired =
                 doesAnyClientNeedIdentityChangeNotifications() || notifyIdentityChange;
 
         boolean success = mWifiAwareNativeApi.enableAndConfigure(transactionId, merged,
-                notificationRequired, mCurrentAwareConfiguration == null);
+                notificationRequired, mCurrentAwareConfiguration == null,
+                mPowerManager.isInteractive(), mPowerManager.isDeviceIdleMode());
         if (!success) {
             try {
                 callback.onConnectFail(NanStatusType.INTERNAL_FAILURE);
+                mAwareMetrics.recordAttachStatus(NanStatusType.INTERNAL_FAILURE);
             } catch (RemoteException e) {
                 Log.w(TAG, "connectLocal onConnectFail(): RemoteException (FYI):  " + e);
             }
@@ -1842,12 +2072,18 @@
             return false;
         }
         mClients.delete(clientId);
+        mAwareMetrics.recordAttachSessionDuration(client.getCreationTime());
+        SparseArray<WifiAwareDiscoverySessionState> sessions = client.getSessions();
+        for (int i = 0; i < sessions.size(); ++i) {
+            mAwareMetrics.recordDiscoverySessionDuration(sessions.valueAt(i).getCreationTime(),
+                    sessions.valueAt(i).isPublishSession());
+        }
         client.destroy();
 
         if (mClients.size() == 0) {
             mCurrentAwareConfiguration = null;
-            mWifiAwareNativeApi.disable((short) 0);
-            return false;
+            deleteAllDataPathInterfaces();
+            return mWifiAwareNativeApi.disable(transactionId);
         }
 
         ConfigRequest merged = mergeConfigRequests(null);
@@ -1862,7 +2098,22 @@
         }
 
         return mWifiAwareNativeApi.enableAndConfigure(transactionId, merged, notificationReqs,
-                false);
+                false, mPowerManager.isInteractive(), mPowerManager.isDeviceIdleMode());
+    }
+
+    private boolean reconfigureLocal(short transactionId) {
+        if (VDBG) Log.v(TAG, "reconfigureLocal(): transactionId=" + transactionId);
+
+        if (mClients.size() == 0) {
+            // no clients - Aware is not enabled, nothing to reconfigure
+            return false;
+        }
+
+        boolean notificationReqs = doesAnyClientNeedIdentityChangeNotifications();
+
+        return mWifiAwareNativeApi.enableAndConfigure(transactionId, mCurrentAwareConfiguration,
+                notificationReqs, false, mPowerManager.isInteractive(),
+                mPowerManager.isDeviceIdleMode());
     }
 
     private void terminateSessionLocal(int clientId, int sessionId) {
@@ -1877,7 +2128,11 @@
             return;
         }
 
-        client.terminateSession(sessionId);
+        WifiAwareDiscoverySessionState session = client.terminateSession(sessionId);
+        if (session != null) {
+            mAwareMetrics.recordDiscoverySessionDuration(session.getCreationTime(),
+                    session.isPublishSession());
+        }
     }
 
     private boolean publishLocal(short transactionId, int clientId, PublishConfig publishConfig,
@@ -1893,13 +2148,15 @@
             return false;
         }
 
-        boolean success = mWifiAwareNativeApi.publish(transactionId, 0, publishConfig);
+        boolean success = mWifiAwareNativeApi.publish(transactionId, (byte) 0, publishConfig);
         if (!success) {
             try {
                 callback.onSessionConfigFail(NanStatusType.INTERNAL_FAILURE);
             } catch (RemoteException e) {
                 Log.w(TAG, "publishLocal onSessionConfigFail(): RemoteException (FYI): " + e);
             }
+            mAwareMetrics.recordDiscoveryStatus(client.getUid(), NanStatusType.INTERNAL_FAILURE,
+                    true);
         }
 
         return success;
@@ -1925,7 +2182,12 @@
             return false;
         }
 
-        return session.updatePublish(transactionId, publishConfig);
+        boolean status = session.updatePublish(transactionId, publishConfig);
+        if (!status) {
+            mAwareMetrics.recordDiscoveryStatus(client.getUid(), NanStatusType.INTERNAL_FAILURE,
+                    true);
+        }
+        return status;
     }
 
     private boolean subscribeLocal(short transactionId, int clientId,
@@ -1941,13 +2203,15 @@
             return false;
         }
 
-        boolean success = mWifiAwareNativeApi.subscribe(transactionId, 0, subscribeConfig);
+        boolean success = mWifiAwareNativeApi.subscribe(transactionId, (byte) 0, subscribeConfig);
         if (!success) {
             try {
                 callback.onSessionConfigFail(NanStatusType.INTERNAL_FAILURE);
             } catch (RemoteException e) {
                 Log.w(TAG, "subscribeLocal onSessionConfigFail(): RemoteException (FYI): " + e);
             }
+            mAwareMetrics.recordDiscoveryStatus(client.getUid(), NanStatusType.INTERNAL_FAILURE,
+                    false);
         }
 
         return success;
@@ -1975,7 +2239,12 @@
             return false;
         }
 
-        return session.updateSubscribe(transactionId, subscribeConfig);
+        boolean status = session.updateSubscribe(transactionId, subscribeConfig);
+        if (!status) {
+            mAwareMetrics.recordDiscoveryStatus(client.getUid(), NanStatusType.INTERNAL_FAILURE,
+                    false);
+        }
+        return status;
     }
 
     private boolean sendFollowonMessageLocal(short transactionId, int clientId, int sessionId,
@@ -2012,24 +2281,31 @@
 
         mUsageEnabled = true;
         queryCapabilities();
-        createAllDataPathInterfaces();
         sendAwareStateChangedBroadcast(true);
+
+        mAwareMetrics.recordEnableUsage();
     }
 
-    private void disableUsageLocal() {
-        if (VDBG) Log.v(TAG, "disableUsageLocal: mUsageEnabled=" + mUsageEnabled);
+    private boolean disableUsageLocal(short transactionId) {
+        if (VDBG) {
+            Log.v(TAG, "disableUsageLocal: transactionId=" + transactionId + ", mUsageEnabled="
+                    + mUsageEnabled);
+        }
 
         if (!mUsageEnabled) {
-            return;
+            return false;
         }
 
         onAwareDownLocal();
-        deleteAllDataPathInterfaces();
 
         mUsageEnabled = false;
-        mWifiAwareNativeApi.disable((short) 0);
+        boolean callDispatched = mWifiAwareNativeApi.disable(transactionId);
 
         sendAwareStateChangedBroadcast(false);
+
+        mAwareMetrics.recordDisableUsage();
+
+        return callDispatched;
     }
 
     private void startRangingLocal(int clientId, int sessionId, RttManager.RttParams[] params,
@@ -2057,10 +2333,13 @@
         for (RttManager.RttParams param : params) {
             String peerIdStr = param.bssid;
             try {
-                param.bssid = session.getMac(Integer.parseInt(peerIdStr), ":");
-                if (param.bssid == null) {
+                WifiAwareDiscoverySessionState.PeerInfo peerInfo = session.getPeerInfo(
+                        Integer.parseInt(peerIdStr));
+                if (peerInfo == null || peerInfo.mMac == null) {
                     Log.d(TAG, "startRangingLocal: no MAC address for peer ID=" + peerIdStr);
                     param.bssid = "";
+                } else {
+                    param.bssid = NativeUtil.macAddressFromByteArray(peerInfo.mMac);
                 }
             } catch (NumberFormatException e) {
                 Log.e(TAG, "startRangingLocal: invalid peer ID specification (in bssid field): '"
@@ -2074,19 +2353,22 @@
 
     private boolean initiateDataPathSetupLocal(short transactionId,
             WifiAwareNetworkSpecifier networkSpecifier, int peerId, int channelRequestType,
-            int channel, byte[] peer, String interfaceName, byte[] pmk, String passphrase) {
+            int channel, byte[] peer, String interfaceName, byte[] pmk, String passphrase,
+            boolean isOutOfBand) {
         if (VDBG) {
-            Log.v(TAG,
-                    "initiateDataPathSetupLocal(): transactionId=" + transactionId
-                            + ", networkSpecifier=" + networkSpecifier + ", peerId=" + peerId
-                            + ", channelRequestType=" + channelRequestType + ", channel=" + channel
-                            + ", peer=" + String.valueOf(HexEncoding.encode(peer))
-                            + ", interfaceName=" + interfaceName + ", pmk=" + pmk
-                            + ", passphrase=" + passphrase);
+            Log.v(TAG, "initiateDataPathSetupLocal(): transactionId=" + transactionId
+                    + ", networkSpecifier=" + networkSpecifier + ", peerId=" + peerId
+                    + ", channelRequestType=" + channelRequestType + ", channel=" + channel
+                    + ", peer="
+                    + String.valueOf(HexEncoding.encode(peer)) + ", interfaceName=" + interfaceName
+                    + ", pmk=" + ((pmk == null) ? "" : "*") + ", passphrase=" + (
+                    (passphrase == null) ? "" : "*") + ", isOutOfBand="
+                    + isOutOfBand);
         }
 
         boolean success = mWifiAwareNativeApi.initiateDataPath(transactionId, peerId,
-                channelRequestType, channel, peer, interfaceName, pmk, passphrase, mCapabilities);
+                channelRequestType, channel, peer, interfaceName, pmk, passphrase, isOutOfBand,
+                mCapabilities);
         if (!success) {
             mDataPathMgr.onDataPathInitiateFail(networkSpecifier, NanStatusType.INTERNAL_FAILURE);
         }
@@ -2095,18 +2377,19 @@
     }
 
     private boolean respondToDataPathRequestLocal(short transactionId, boolean accept,
-            int ndpId, String interfaceName, byte[] pmk, String passphrase) {
+            int ndpId, String interfaceName, byte[] pmk, String passphrase, boolean isOutOfBand) {
         if (VDBG) {
             Log.v(TAG,
                     "respondToDataPathRequestLocal(): transactionId=" + transactionId + ", accept="
                             + accept + ", ndpId=" + ndpId + ", interfaceName=" + interfaceName
-                            + ", pmk=" + pmk + ", passphrase=" + passphrase);
+                            + ", pmk=" + ((pmk == null) ? "" : "*") + ", passphrase="
+                            + ((passphrase == null) ? "" : "*") + ", isOutOfBand="
+                            + isOutOfBand);
         }
-
         boolean success = mWifiAwareNativeApi.respondToDataPathRequest(transactionId, accept, ndpId,
-                interfaceName, pmk, passphrase, mCapabilities);
+                interfaceName, pmk, passphrase, isOutOfBand, mCapabilities);
         if (!success) {
-            mDataPathMgr.onRespondToDataPathRequest(ndpId, false);
+            mDataPathMgr.onRespondToDataPathRequest(ndpId, false, NanStatusType.INTERNAL_FAILURE);
         }
         return success;
     }
@@ -2143,8 +2426,10 @@
             String callingPackage = data.getString(MESSAGE_BUNDLE_KEY_CALLING_PACKAGE);
 
             WifiAwareClientState client = new WifiAwareClientState(mContext, clientId, uid, pid,
-                    callingPackage, callback, configRequest, notifyIdentityChange);
+                    callingPackage, callback, configRequest, notifyIdentityChange,
+                    SystemClock.elapsedRealtime());
             mClients.put(clientId, client);
+            mAwareMetrics.recordAttachSession(uid, notifyIdentityChange, mClients);
             try {
                 callback.onConnectSuccess(clientId);
             } catch (RemoteException e) {
@@ -2156,11 +2441,18 @@
             /*
              * NOP (i.e. updated configuration after disconnecting a client)
              */
+        } else if (completedCommand.arg1 == COMMAND_TYPE_RECONFIGURE) {
+            /*
+             * NOP (i.e. updated configuration at power saving event)
+             */
         } else {
             Log.wtf(TAG, "onConfigCompletedLocal: unexpected completedCommand=" + completedCommand);
             return;
         }
 
+        if (mCurrentAwareConfiguration == null) { // enabled (as opposed to re-configured)
+            createAllDataPathInterfaces();
+        }
         mCurrentAwareConfiguration = mergeConfigRequests(null);
         if (mCurrentAwareConfiguration == null) {
             Log.wtf(TAG, "onConfigCompletedLocal: got a null merged configuration after config!?");
@@ -2179,6 +2471,7 @@
 
             try {
                 callback.onConnectFail(reason);
+                mAwareMetrics.recordAttachStatus(reason);
             } catch (RemoteException e) {
                 Log.w(TAG, "onConfigFailedLocal onConnectFail(): RemoteException (FYI): " + e);
             }
@@ -2187,15 +2480,39 @@
              * NOP (tried updating configuration after disconnecting a client -
              * shouldn't fail but there's nothing to do - the old configuration
              * is still up-and-running).
+             *
+             * OR: timed-out getting a response to a disable. Either way a NOP.
+             */
+        } else if (failedCommand.arg1 == COMMAND_TYPE_RECONFIGURE) {
+            /*
+             * NOP (configuration change as part of possibly power saving event - should not
+             * fail but there's nothing to do).
              */
         } else {
             Log.wtf(TAG, "onConfigFailedLocal: unexpected failedCommand=" + failedCommand);
             return;
         }
-
     }
 
-    private void onSessionConfigSuccessLocal(Message completedCommand, int pubSubId,
+    private void onDisableResponseLocal(Message command, int reason) {
+        if (VDBG) {
+            Log.v(TAG, "onDisableResponseLocal: command=" + command + ", reason=" + reason);
+        }
+
+        /*
+         * do nothing:
+         * - success: was waiting so that don't enable while disabling
+         * - fail: shouldn't happen (though can if already disabled for instance)
+         */
+        if (reason != NanStatusType.SUCCESS) {
+            Log.e(TAG, "onDisableResponseLocal: FAILED!? command=" + command + ", reason="
+                    + reason);
+        }
+
+        mAwareMetrics.recordDisableAware();
+    }
+
+    private void onSessionConfigSuccessLocal(Message completedCommand, byte pubSubId,
             boolean isPublish) {
         if (VDBG) {
             Log.v(TAG, "onSessionConfigSuccessLocal: completedCommand=" + completedCommand
@@ -2224,8 +2541,15 @@
             }
 
             WifiAwareDiscoverySessionState session = new WifiAwareDiscoverySessionState(
-                    mWifiAwareNativeApi, sessionId, pubSubId, callback, isPublish);
+                    mWifiAwareNativeApi, sessionId, pubSubId, callback, isPublish,
+                    SystemClock.elapsedRealtime());
             client.addSession(session);
+
+            mAwareMetrics.recordDiscoverySession(client.getUid(),
+                    completedCommand.arg1 == COMMAND_TYPE_PUBLISH, mClients);
+            mAwareMetrics.recordDiscoveryStatus(client.getUid(), NanStatusType.SUCCESS,
+                    completedCommand.arg1 == COMMAND_TYPE_PUBLISH);
+
         } else if (completedCommand.arg1 == COMMAND_TYPE_UPDATE_PUBLISH
                 || completedCommand.arg1 == COMMAND_TYPE_UPDATE_SUBSCRIBE) {
             int clientId = completedCommand.arg2;
@@ -2251,6 +2575,8 @@
                 Log.e(TAG, "onSessionConfigSuccessLocal: onSessionConfigSuccess() RemoteException="
                         + e);
             }
+            mAwareMetrics.recordDiscoveryStatus(client.getUid(), NanStatusType.SUCCESS,
+                    completedCommand.arg1 == COMMAND_TYPE_UPDATE_PUBLISH);
         } else {
             Log.wtf(TAG,
                     "onSessionConfigSuccessLocal: unexpected completedCommand=" + completedCommand);
@@ -2265,14 +2591,24 @@
 
         if (failedCommand.arg1 == COMMAND_TYPE_PUBLISH
                 || failedCommand.arg1 == COMMAND_TYPE_SUBSCRIBE) {
+            int clientId = failedCommand.arg2;
             IWifiAwareDiscoverySessionCallback callback =
                     (IWifiAwareDiscoverySessionCallback) failedCommand.obj;
+
+            WifiAwareClientState client = mClients.get(clientId);
+            if (client == null) {
+                Log.e(TAG, "onSessionConfigFailLocal: no client exists for clientId=" + clientId);
+                return;
+            }
+
             try {
                 callback.onSessionConfigFail(reason);
             } catch (RemoteException e) {
                 Log.w(TAG, "onSessionConfigFailLocal onSessionConfigFail(): RemoteException (FYI): "
                         + e);
             }
+            mAwareMetrics.recordDiscoveryStatus(client.getUid(), reason,
+                    failedCommand.arg1 == COMMAND_TYPE_PUBLISH);
         } else if (failedCommand.arg1 == COMMAND_TYPE_UPDATE_PUBLISH
                 || failedCommand.arg1 == COMMAND_TYPE_UPDATE_SUBSCRIBE) {
             int clientId = failedCommand.arg2;
@@ -2296,6 +2632,12 @@
             } catch (RemoteException e) {
                 Log.e(TAG, "onSessionConfigFailLocal: onSessionConfigFail() RemoteException=" + e);
             }
+            mAwareMetrics.recordDiscoveryStatus(client.getUid(), reason,
+                    failedCommand.arg1 == COMMAND_TYPE_UPDATE_PUBLISH);
+
+            if (reason == NanStatusType.INVALID_SESSION_ID) {
+                client.removeSession(sessionId);
+            }
         } else {
             Log.wtf(TAG, "onSessionConfigFailLocal: unexpected failedCommand=" + failedCommand);
         }
@@ -2435,7 +2777,7 @@
                     + ", success=" + success + ", reasonOnFailure=" + reasonOnFailure);
         }
 
-        mDataPathMgr.onRespondToDataPathRequest(command.arg2, success);
+        mDataPathMgr.onRespondToDataPathRequest(command.arg2, success, reasonOnFailure);
     }
 
     private void onEndPathEndResponseLocal(Message command, boolean success, int reasonOnFailure) {
@@ -2462,6 +2804,8 @@
             WifiAwareClientState client = mClients.valueAt(i);
             client.onInterfaceAddressChange(mac);
         }
+
+        mAwareMetrics.recordEnableAware();
     }
 
     private void onClusterChangeLocal(int flag, byte[] clusterId) {
@@ -2474,6 +2818,8 @@
             WifiAwareClientState client = mClients.valueAt(i);
             client.onClusterChange(flag, clusterId, mCurrentDiscoveryInterfaceMac);
         }
+
+        mAwareMetrics.recordEnableAware();
     }
 
     private void onMatchLocal(int pubSubId, int requestorInstanceId, byte[] peerMac,
@@ -2516,6 +2862,8 @@
                     "onSessionTerminatedLocal onSessionTerminated(): RemoteException (FYI): " + e);
         }
         data.first.removeSession(data.second.getSessionId());
+        mAwareMetrics.recordDiscoverySessionDuration(data.second.getCreationTime(),
+                data.second.isPublishSession());
     }
 
     private void onMessageReceivedLocal(int pubSubId, int requestorInstanceId, byte[] peerMac,
@@ -2542,11 +2890,23 @@
             Log.v(TAG, "onAwareDown");
         }
 
+        for (int i = 0; i < mClients.size(); ++i) {
+            mAwareMetrics.recordAttachSessionDuration(mClients.valueAt(i).getCreationTime());
+            SparseArray<WifiAwareDiscoverySessionState> sessions = mClients.valueAt(
+                    i).getSessions();
+            for (int j = 0; j < sessions.size(); ++j) {
+                mAwareMetrics.recordDiscoverySessionDuration(sessions.valueAt(i).getCreationTime(),
+                        sessions.valueAt(i).isPublishSession());
+            }
+        }
+        mAwareMetrics.recordDisableAware();
+
         mClients.clear();
         mCurrentAwareConfiguration = null;
         mSm.onAwareDownCleanupSendQueueState();
         mDataPathMgr.onAwareDownCleanupDataPaths();
         mCurrentDiscoveryInterfaceMac = ALL_ZERO_MAC;
+        deleteAllDataPathInterfaces();
     }
 
     /*
@@ -2699,6 +3059,7 @@
         for (int i = 0; i < mClients.size(); ++i) {
             mClients.valueAt(i).dump(fd, pw, args);
         }
+        pw.println("  mSettableParameters: " + mSettableParameters);
         mSm.dump(fd, pw, args);
         mRtt.dump(fd, pw, args);
         mDataPathMgr.dump(fd, pw, args);
diff --git a/service/java/com/android/server/wifi/hotspot2/ANQPNetworkKey.java b/service/java/com/android/server/wifi/hotspot2/ANQPNetworkKey.java
index aaaedb3..19590b3 100644
--- a/service/java/com/android/server/wifi/hotspot2/ANQPNetworkKey.java
+++ b/service/java/com/android/server/wifi/hotspot2/ANQPNetworkKey.java
@@ -96,10 +96,9 @@
         if (mHESSID != 0L) {
             return Utils.macToString(mHESSID) + ":" + mAnqpDomainID;
         } else if (mBSSID != 0L) {
-            return Utils.macToString(mBSSID)
-                    + ":<" + Utils.toUnicodeEscapedString(mSSID) + ">";
+            return Utils.macToString(mBSSID) + ":<" + mSSID + ">";
         } else {
-            return "<" + Utils.toUnicodeEscapedString(mSSID) + ">:" + mAnqpDomainID;
+            return "<" + mSSID + ">:" + mAnqpDomainID;
         }
     }
 }
diff --git a/service/java/com/android/server/wifi/hotspot2/NetworkDetail.java b/service/java/com/android/server/wifi/hotspot2/NetworkDetail.java
index 19af85f..0c77162 100644
--- a/service/java/com/android/server/wifi/hotspot2/NetworkDetail.java
+++ b/service/java/com/android/server/wifi/hotspot2/NetworkDetail.java
@@ -293,7 +293,6 @@
         } else {
             mWifiMode = 0;
             mMaxRate = 0;
-            Log.w("WifiMode", mSSID + ", Invalid SupportedRates!!!");
         }
         if (DBG) {
             Log.d(TAG, mSSID + "ChannelWidth is: " + mChannelWidth + " PrimaryFreq: " + mPrimaryFreq
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointConfigStoreData.java b/service/java/com/android/server/wifi/hotspot2/PasspointConfigStoreData.java
index 74a4760..38401d2 100644
--- a/service/java/com/android/server/wifi/hotspot2/PasspointConfigStoreData.java
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointConfigStoreData.java
@@ -66,6 +66,7 @@
     private static final String XML_TAG_CLIENT_PRIVATE_KEY_ALIAS = "ClientPrivateKeyAlias";
 
     private static final String XML_TAG_PROVIDER_INDEX = "ProviderIndex";
+    private static final String XML_TAG_HAS_EVER_CONNECTED = "HasEverConnected";
 
     private final WifiKeyStore mKeyStore;
     private final SIMAccessor mSimAccessor;
@@ -211,6 +212,7 @@
                 provider.getClientCertificateAlias());
         XmlUtil.writeNextValue(out, XML_TAG_CLIENT_PRIVATE_KEY_ALIAS,
                 provider.getClientPrivateKeyAlias());
+        XmlUtil.writeNextValue(out, XML_TAG_HAS_EVER_CONNECTED, provider.getHasEverConnected());
         if (provider.getConfig() != null) {
             XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_PASSPOINT_CONFIGURATION);
             PasspointXmlUtils.serializePasspointConfiguration(out, provider.getConfig());
@@ -304,6 +306,7 @@
         String caCertificateAlias = null;
         String clientCertificateAlias = null;
         String clientPrivateKeyAlias = null;
+        boolean hasEverConnected = false;
         PasspointConfiguration config = null;
         while (XmlUtils.nextElementWithin(in, outerTagDepth)) {
             if (in.getAttributeValue(null, "name") != null) {
@@ -326,6 +329,9 @@
                     case XML_TAG_CLIENT_PRIVATE_KEY_ALIAS:
                         clientPrivateKeyAlias = (String) value;
                         break;
+                    case XML_TAG_HAS_EVER_CONNECTED:
+                        hasEverConnected = (boolean) value;
+                        break;
                 }
             } else {
                 if (!TextUtils.equals(in.getName(),
@@ -344,7 +350,8 @@
             throw new XmlPullParserException("Missing Passpoint configuration");
         }
         return new PasspointProvider(config, mKeyStore, mSimAccessor, providerId, creatorUid,
-                caCertificateAlias, clientCertificateAlias, clientPrivateKeyAlias);
+                caCertificateAlias, clientCertificateAlias, clientPrivateKeyAlias,
+                hasEverConnected);
     }
 
     /**
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointManager.java b/service/java/com/android/server/wifi/hotspot2/PasspointManager.java
index 9000d43..5d79ba4 100644
--- a/service/java/com/android/server/wifi/hotspot2/PasspointManager.java
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointManager.java
@@ -18,9 +18,7 @@
 
 import static android.net.wifi.WifiManager.ACTION_PASSPOINT_DEAUTH_IMMINENT;
 import static android.net.wifi.WifiManager.ACTION_PASSPOINT_ICON;
-import static android.net.wifi.WifiManager.ACTION_PASSPOINT_OSU_PROVIDERS_LIST;
 import static android.net.wifi.WifiManager.ACTION_PASSPOINT_SUBSCRIPTION_REMEDIATION;
-import static android.net.wifi.WifiManager.EXTRA_ANQP_ELEMENT_DATA;
 import static android.net.wifi.WifiManager.EXTRA_BSSID_LONG;
 import static android.net.wifi.WifiManager.EXTRA_DELAY;
 import static android.net.wifi.WifiManager.EXTRA_ESS;
@@ -35,6 +33,7 @@
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiEnterpriseConfig;
+import android.net.wifi.hotspot2.OsuProvider;
 import android.net.wifi.hotspot2.PasspointConfiguration;
 import android.os.UserHandle;
 import android.text.TextUtils;
@@ -46,10 +45,12 @@
 import com.android.server.wifi.WifiConfigManager;
 import com.android.server.wifi.WifiConfigStore;
 import com.android.server.wifi.WifiKeyStore;
+import com.android.server.wifi.WifiMetrics;
 import com.android.server.wifi.WifiNative;
 import com.android.server.wifi.hotspot2.anqp.ANQPElement;
 import com.android.server.wifi.hotspot2.anqp.Constants;
-import com.android.server.wifi.hotspot2.anqp.RawByteElement;
+import com.android.server.wifi.hotspot2.anqp.HSOsuProvidersElement;
+import com.android.server.wifi.hotspot2.anqp.OsuProviderInfo;
 import com.android.server.wifi.util.InformationElementUtil;
 import com.android.server.wifi.util.ScanResultUtil;
 
@@ -97,6 +98,7 @@
     private final ANQPRequestManager mAnqpRequestManager;
     private final WifiConfigManager mWifiConfigManager;
     private final CertificateVerifier mCertVerifier;
+    private final WifiMetrics mWifiMetrics;
 
     // Counter used for assigning unique identifier to each provider.
     private long mProviderIndex;
@@ -121,18 +123,6 @@
 
             // Add new entry to the cache.
             mAnqpCache.addEntry(anqpKey, anqpElements);
-
-            // Broadcast OSU providers info.
-            if (anqpElements.containsKey(Constants.ANQPElementType.HSOSUProviders)) {
-                RawByteElement osuProviders = (RawByteElement) anqpElements.get(
-                        Constants.ANQPElementType.HSOSUProviders);
-                Intent intent = new Intent(ACTION_PASSPOINT_OSU_PROVIDERS_LIST);
-                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-                intent.putExtra(EXTRA_BSSID_LONG, bssid);
-                intent.putExtra(EXTRA_ANQP_ELEMENT_DATA, osuProviders.getPayload());
-                mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
-                        android.Manifest.permission.ACCESS_WIFI_STATE);
-            }
         }
 
         @Override
@@ -206,7 +196,8 @@
 
     public PasspointManager(Context context, WifiNative wifiNative, WifiKeyStore keyStore,
             Clock clock, SIMAccessor simAccessor, PasspointObjectFactory objectFactory,
-            WifiConfigManager wifiConfigManager, WifiConfigStore wifiConfigStore) {
+            WifiConfigManager wifiConfigManager, WifiConfigStore wifiConfigStore,
+            WifiMetrics wifiMetrics) {
         mHandler = objectFactory.makePasspointEventHandler(wifiNative,
                 new CallbackHandler(context));
         mKeyStore = keyStore;
@@ -217,6 +208,7 @@
         mAnqpRequestManager = objectFactory.makeANQPRequestManager(mHandler, clock);
         mCertVerifier = objectFactory.makeCertificateVerifier();
         mWifiConfigManager = wifiConfigManager;
+        mWifiMetrics = wifiMetrics;
         mProviderIndex = 0;
         wifiConfigStore.registerStoreData(objectFactory.makePasspointConfigStoreData(
                 mKeyStore, mSimAccessor, new DataSourceHandler()));
@@ -234,6 +226,7 @@
      * @return true if provider is added, false otherwise
      */
     public boolean addOrUpdateProvider(PasspointConfiguration config, int uid) {
+        mWifiMetrics.incrementNumPasspointProviderInstallation();
         if (config == null) {
             Log.e(TAG, "Configuration not provided");
             return false;
@@ -278,6 +271,7 @@
         mWifiConfigManager.saveToStore(true /* forceWrite */);
         Log.d(TAG, "Added/updated Passpoint configuration: " + config.getHomeSp().getFqdn()
                 + " by " + uid);
+        mWifiMetrics.incrementNumPasspointProviderInstallSuccess();
         return true;
     }
 
@@ -288,6 +282,7 @@
      * @return true if a provider is removed, false otherwise
      */
     public boolean removeProvider(String fqdn) {
+        mWifiMetrics.incrementNumPasspointProviderUninstallation();
         if (!mProviders.containsKey(fqdn)) {
             Log.e(TAG, "Config doesn't exist");
             return false;
@@ -297,6 +292,7 @@
         mProviders.remove(fqdn);
         mWifiConfigManager.saveToStore(true /* forceWrite */);
         Log.d(TAG, "Removed Passpoint configuration: " + fqdn);
+        mWifiMetrics.incrementNumPasspointProviderUninstallSuccess();
         return true;
     }
 
@@ -337,7 +333,13 @@
                 scanResult.informationElements);
 
         // Lookup ANQP data in the cache.
-        long bssid = Utils.parseMac(scanResult.BSSID);
+        long bssid;
+        try {
+            bssid = Utils.parseMac(scanResult.BSSID);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Invalid BSSID provided in the scan result: " + scanResult.BSSID);
+            return null;
+        }
         ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(scanResult.SSID, bssid, scanResult.hessid,
                 vsa.anqpDomainID);
         ANQPData anqpEntry = mAnqpCache.getEntry(anqpKey);
@@ -448,7 +450,13 @@
                 InformationElementUtil.getHS2VendorSpecificIE(scanResult.informationElements);
 
         // Lookup ANQP data in the cache.
-        long bssid = Utils.parseMac(scanResult.BSSID);
+        long bssid;
+        try {
+            bssid = Utils.parseMac(scanResult.BSSID);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Invalid BSSID provided in the scan result: " + scanResult.BSSID);
+            return new HashMap<Constants.ANQPElementType, ANQPElement>();
+        }
         ANQPData anqpEntry = mAnqpCache.getEntry(ANQPNetworkKey.buildKey(
                 scanResult.SSID, bssid, scanResult.hessid, vsa.anqpDomainID));
         if (anqpEntry != null) {
@@ -472,6 +480,10 @@
             Log.e(TAG, "Attempt to get matching config for a null ScanResult");
             return null;
         }
+        if (!scanResult.isPasspointNetwork()) {
+            Log.e(TAG, "Attempt to get matching config for a non-Passpoint AP");
+            return null;
+        }
         Pair<PasspointProvider, PasspointMatch> matchedProvider = matchProvider(scanResult);
         if (matchedProvider == null) {
             return null;
@@ -485,6 +497,80 @@
     }
 
     /**
+     * Return the list of Hosspot 2.0 OSU (Online Sign-Up) providers associated with the given
+     * AP.
+     *
+     * An empty list will be returned when an invalid scan result is provided or no match is found.
+     *
+     * @param scanResult The scan result of the AP
+     * @return List of {@link OsuProvider}
+     */
+    public List<OsuProvider> getMatchingOsuProviders(ScanResult scanResult) {
+        if (scanResult == null) {
+            Log.e(TAG, "Attempt to retrieve OSU providers for a null ScanResult");
+            return new ArrayList<OsuProvider>();
+        }
+        if (!scanResult.isPasspointNetwork()) {
+            Log.e(TAG, "Attempt to retrieve OSU providers for a non-Passpoint AP");
+            return new ArrayList<OsuProvider>();
+        }
+
+        // Lookup OSU Providers ANQP element.
+        Map<Constants.ANQPElementType, ANQPElement> anqpElements = getANQPElements(scanResult);
+        if (!anqpElements.containsKey(Constants.ANQPElementType.HSOSUProviders)) {
+            return new ArrayList<OsuProvider>();
+        }
+
+        HSOsuProvidersElement element =
+                (HSOsuProvidersElement) anqpElements.get(Constants.ANQPElementType.HSOSUProviders);
+        List<OsuProvider> providers = new ArrayList<>();
+        for (OsuProviderInfo info : element.getProviders()) {
+            // TODO(b/62256482): include icon data once the icon file retrieval and management
+            // support is added.
+            OsuProvider provider = new OsuProvider(element.getOsuSsid(), info.getFriendlyName(),
+                    info.getServiceDescription(), info.getServerUri(),
+                    info.getNetworkAccessIdentifier(), info.getMethodList(), null);
+            providers.add(provider);
+        }
+        return providers;
+    }
+
+    /**
+     * Invoked when a Passpoint network was successfully connected based on the credentials
+     * provided by the given Passpoint provider (specified by its FQDN).
+     *
+     * @param fqdn The FQDN of the Passpoint provider
+     */
+    public void onPasspointNetworkConnected(String fqdn) {
+        PasspointProvider provider = mProviders.get(fqdn);
+        if (provider == null) {
+            Log.e(TAG, "Passpoint network connected without provider: " + fqdn);
+            return;
+        }
+
+        if (!provider.getHasEverConnected()) {
+            // First successful connection using this provider.
+            provider.setHasEverConnected(true);
+        }
+    }
+
+    /**
+     * Update metrics related to installed Passpoint providers, this includes the number of
+     * installed providers and the number of those providers that results in a successful network
+     * connection.
+     */
+    public void updateMetrics() {
+        int numProviders = mProviders.size();
+        int numConnectedProviders = 0;
+        for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) {
+            if (entry.getValue().getHasEverConnected()) {
+                numConnectedProviders++;
+            }
+        }
+        mWifiMetrics.updateSavedPasspointProfiles(numProviders, numConnectedProviders);
+    }
+
+    /**
      * Dump the current state of PasspointManager to the provided output stream.
      *
      * @param pw The output stream to write to
@@ -544,7 +630,7 @@
                 mSimAccessor, mProviderIndex++, wifiConfig.creatorUid,
                 enterpriseConfig.getCaCertificateAlias(),
                 enterpriseConfig.getClientCertificateAlias(),
-                enterpriseConfig.getClientCertificateAlias());
+                enterpriseConfig.getClientCertificateAlias(), false);
         mProviders.put(passpointConfig.getHomeSp().getFqdn(), provider);
         return true;
     }
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointNetworkEvaluator.java b/service/java/com/android/server/wifi/hotspot2/PasspointNetworkEvaluator.java
index 132a2f2..6ff5ee9 100644
--- a/service/java/com/android/server/wifi/hotspot2/PasspointNetworkEvaluator.java
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointNetworkEvaluator.java
@@ -92,6 +92,10 @@
             Pair<PasspointProvider, PasspointMatch> bestProvider =
                     mPasspointManager.matchProvider(scanDetail.getScanResult());
             if (bestProvider != null) {
+                if (bestProvider.first.isSimCredential() && !mWifiConfigManager.isSimPresent()) {
+                    // Skip providers backed by SIM credential when SIM is not present.
+                    continue;
+                }
                 candidateList.add(new PasspointNetworkCandidate(
                         bestProvider.first, bestProvider.second, scanDetail));
             }
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointProvider.java b/service/java/com/android/server/wifi/hotspot2/PasspointProvider.java
index 33867bb..c7943ed 100644
--- a/service/java/com/android/server/wifi/hotspot2/PasspointProvider.java
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointProvider.java
@@ -87,14 +87,17 @@
     private final int mEAPMethodID;
     private final AuthParam mAuthParam;
 
+    private boolean mHasEverConnected;
+
     public PasspointProvider(PasspointConfiguration config, WifiKeyStore keyStore,
             SIMAccessor simAccessor, long providerId, int creatorUid) {
-        this(config, keyStore, simAccessor, providerId, creatorUid, null, null, null);
+        this(config, keyStore, simAccessor, providerId, creatorUid, null, null, null, false);
     }
 
     public PasspointProvider(PasspointConfiguration config, WifiKeyStore keyStore,
             SIMAccessor simAccessor, long providerId, int creatorUid, String caCertificateAlias,
-            String clientCertificateAlias, String clientPrivateKeyAlias) {
+            String clientCertificateAlias, String clientPrivateKeyAlias,
+            boolean hasEverConnected) {
         // Maintain a copy of the configuration to avoid it being updated by others.
         mConfig = new PasspointConfiguration(config);
         mKeyStore = keyStore;
@@ -103,6 +106,7 @@
         mCaCertificateAlias = caCertificateAlias;
         mClientCertificateAlias = clientCertificateAlias;
         mClientPrivateKeyAlias = clientPrivateKeyAlias;
+        mHasEverConnected = hasEverConnected;
 
         // Setup EAP method and authentication parameter based on the credential.
         if (mConfig.getCredential().getUserCredential() != null) {
@@ -150,6 +154,14 @@
         return mCreatorUid;
     }
 
+    public boolean getHasEverConnected() {
+        return mHasEverConnected;
+    }
+
+    public void setHasEverConnected(boolean hasEverConnected) {
+        mHasEverConnected = hasEverConnected;
+    }
+
     /**
      * Install certificates and key based on current configuration.
      * Note: the certificates and keys in the configuration will get cleared once
@@ -303,6 +315,13 @@
     }
 
     /**
+     * @return true if provider is backed by a SIM credential.
+     */
+    public boolean isSimCredential() {
+        return mConfig.getCredential().getSimCredential() != null;
+    }
+
+    /**
      * Convert a legacy {@link WifiConfiguration} representation of a Passpoint configuration to
      * a {@link PasspointConfiguration}.  This is used for migrating legacy Passpoint
      * configuration (release N and older).
diff --git a/service/java/com/android/server/wifi/hotspot2/Utils.java b/service/java/com/android/server/wifi/hotspot2/Utils.java
index dc04532..005af2a 100644
--- a/service/java/com/android/server/wifi/hotspot2/Utils.java
+++ b/service/java/com/android/server/wifi/hotspot2/Utils.java
@@ -44,7 +44,9 @@
     }
 
     public static long parseMac(String s) {
-
+        if (s == null) {
+            throw new IllegalArgumentException("Null MAC adddress");
+        }
         long mac = 0;
         int count = 0;
         for (int n = 0; n < s.length(); n++) {
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/ANQPParser.java b/service/java/com/android/server/wifi/hotspot2/anqp/ANQPParser.java
index 7a06ef4..89bcfcb 100644
--- a/service/java/com/android/server/wifi/hotspot2/anqp/ANQPParser.java
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/ANQPParser.java
@@ -96,7 +96,7 @@
             case HSConnCapability:
                 return HSConnectionCapabilityElement.parse(payload);
             case HSOSUProviders:
-                return RawByteElement.parse(infoID, payload);
+                return HSOsuProvidersElement.parse(payload);
             default:
                 throw new ProtocolException("Unknown element ID: " + infoID);
         }
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/Constants.java b/service/java/com/android/server/wifi/hotspot2/anqp/Constants.java
index 7cf34c7..c45b3c5 100644
--- a/service/java/com/android/server/wifi/hotspot2/anqp/Constants.java
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/Constants.java
@@ -48,6 +48,7 @@
     public static final int HS_NAI_HOME_REALM_QUERY = 6;
     public static final int HS_OSU_PROVIDERS = 8;
     public static final int HS_ICON_REQUEST = 10;
+    public static final int HS_ICON_FILE = 11;
 
     public enum ANQPElementType {
         ANQPQueryList,
@@ -64,7 +65,8 @@
         HSConnCapability,
         HSNAIHomeRealmQuery,
         HSOSUProviders,
-        HSIconRequest
+        HSIconRequest,
+        HSIconFile
     }
 
     private static final Map<Integer, ANQPElementType> sAnqpMap = new HashMap<>();
@@ -91,6 +93,7 @@
         sHs20Map.put(HS_NAI_HOME_REALM_QUERY, ANQPElementType.HSNAIHomeRealmQuery);
         sHs20Map.put(HS_OSU_PROVIDERS, ANQPElementType.HSOSUProviders);
         sHs20Map.put(HS_ICON_REQUEST, ANQPElementType.HSIconRequest);
+        sHs20Map.put(HS_ICON_FILE, ANQPElementType.HSIconFile);
 
         for (Map.Entry<Integer, ANQPElementType> entry : sAnqpMap.entrySet()) {
             sRevAnqpmap.put(entry.getValue(), entry.getKey());
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/HSIconFileElement.java b/service/java/com/android/server/wifi/hotspot2/anqp/HSIconFileElement.java
new file mode 100644
index 0000000..ed0c472
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/HSIconFileElement.java
@@ -0,0 +1,121 @@
+/*
+ * 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.anqp;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wifi.ByteBufferReader;
+
+import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * The Icon Binary File vendor specific ANQP Element,
+ * Wi-Fi Alliance Hotspot 2.0 (Release 2) Technical Specification - Version 5.00,
+ * section 4.10.
+ *
+ * Format:
+ *
+ * | Status Code | Type Length | Type | Data Length | Data |
+ *        1             1      variable      2      variable
+ *
+ */
+public class HSIconFileElement extends ANQPElement {
+    private static final String TAG = "HSIconFileElement";
+
+    /**
+     * Icon download status code.
+     */
+    public static final int STATUS_CODE_SUCCESS = 0;
+    public static final int STATUS_CODE_FILE_NOT_FOUND = 1;
+    public static final int STATUS_CODE_UNSPECIFIED_ERROR = 2;
+
+    private final int mStatusCode;
+    private final String mIconType;
+    private final byte[] mIconData;
+
+    @VisibleForTesting
+    public HSIconFileElement(int statusCode, String iconType, byte[] iconData) {
+        super(Constants.ANQPElementType.HSIconFile);
+        mStatusCode = statusCode;
+        mIconType = iconType;
+        mIconData = iconData;
+    }
+
+    /**
+     * Parse a HSIconFileElement from the given buffer.
+     *
+     * @param payload The buffer to read from
+     * @return {@link HSIconFileElement}
+     * @throws BufferUnderflowException
+     * @throws ProtocolException
+     */
+    public static HSIconFileElement parse(ByteBuffer payload)
+            throws ProtocolException {
+        // Parse status code.
+        int status = payload.get() & 0xFF;
+        if (status != STATUS_CODE_SUCCESS) {
+            // No more data if status code is not success.
+            Log.e(TAG, "Icon file download failed: " + status);
+            return new HSIconFileElement(status, null, null);
+        }
+
+        // Parse icon type.
+        String iconType =
+                ByteBufferReader.readStringWithByteLength(payload, StandardCharsets.US_ASCII);
+
+        // Parse icon data.
+        int iconDataLength =
+                (int) ByteBufferReader.readInteger(payload, ByteOrder.LITTLE_ENDIAN, 2) & 0xFFFF;
+        byte[] iconData = new byte[iconDataLength];
+        payload.get(iconData);
+
+        return new HSIconFileElement(status, iconType, iconData);
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        if (!(thatObject instanceof HSIconFileElement)) {
+            return false;
+        }
+        HSIconFileElement that = (HSIconFileElement) thatObject;
+        return mStatusCode == that.mStatusCode
+                && TextUtils.equals(mIconType, that.mIconType)
+                && Arrays.equals(mIconData, that.mIconData);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mStatusCode, mIconType, Arrays.hashCode(mIconData));
+    }
+
+    @Override
+    public String toString() {
+        return "HSIconFileElement{" + "mStatusCode=" + mStatusCode
+                + "mIconType=" + mIconType + "}";
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/HSOsuProvidersElement.java b/service/java/com/android/server/wifi/hotspot2/anqp/HSOsuProvidersElement.java
new file mode 100644
index 0000000..146a44c
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/HSOsuProvidersElement.java
@@ -0,0 +1,118 @@
+/*
+ * 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.anqp;
+
+import android.net.wifi.WifiSsid;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * The OSU Providers List vendor specific ANQP Element,
+ * Wi-Fi Alliance Hotspot 2.0 (Release 2) Technical Specification - Version 5.00,
+ * section 4.8.
+ *
+ * Format:
+ *
+ * | OSU SSID Length | OSU SSID | Number of OSU Providers | Provider #1 | ...
+ *          1          variable             1                 variable
+ *
+ */
+public class HSOsuProvidersElement extends ANQPElement {
+    /**
+     * Maximum length for a SSID.  Refer to IEEE 802.11-2012 Section 8.4.2.2
+     * for more info.
+     */
+    @VisibleForTesting
+    public static final int MAXIMUM_OSU_SSID_LENGTH = 32;
+
+    private final WifiSsid mOsuSsid;
+    private final List<OsuProviderInfo> mProviders;
+
+    @VisibleForTesting
+    public HSOsuProvidersElement(WifiSsid osuSsid, List<OsuProviderInfo> providers) {
+        super(Constants.ANQPElementType.HSOSUProviders);
+        mOsuSsid = osuSsid;
+        mProviders = providers;
+    }
+
+    /**
+     * Parse a HSOsuProvidersElement from the given buffer.
+     *
+     * @param payload The buffer to read from
+     * @return {@link HSOsuProvidersElement}
+     * @throws BufferUnderflowException
+     * @throws ProtocolException
+     */
+    public static HSOsuProvidersElement parse(ByteBuffer payload)
+            throws ProtocolException {
+        int ssidLength = payload.get() & 0xFF;
+        if (ssidLength > MAXIMUM_OSU_SSID_LENGTH) {
+            throw new ProtocolException("Invalid SSID length: " + ssidLength);
+        }
+        byte[] ssidBytes = new byte[ssidLength];
+        payload.get(ssidBytes);
+
+        int numProviders = payload.get() & 0xFF;
+        List<OsuProviderInfo> providers = new ArrayList<>();
+        while (numProviders > 0) {
+            providers.add(OsuProviderInfo.parse(payload));
+            numProviders--;
+        }
+
+        return new HSOsuProvidersElement(WifiSsid.createFromByteArray(ssidBytes), providers);
+    }
+
+    public WifiSsid getOsuSsid() {
+        return mOsuSsid;
+    }
+
+    public List<OsuProviderInfo> getProviders() {
+        return Collections.unmodifiableList(mProviders);
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        if (!(thatObject instanceof HSOsuProvidersElement)) {
+            return false;
+        }
+        HSOsuProvidersElement that = (HSOsuProvidersElement) thatObject;
+        return (mOsuSsid == null ? that.mOsuSsid == null : mOsuSsid.equals(that.mOsuSsid))
+                && (mProviders == null ? that.mProviders == null
+                        : mProviders.equals(that.mProviders));
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mOsuSsid, mProviders);
+    }
+
+    @Override
+    public String toString() {
+        return "OSUProviders{" + "mOsuSsid=" + mOsuSsid + ", mProviders=" + mProviders + "}";
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/IconInfo.java b/service/java/com/android/server/wifi/hotspot2/anqp/IconInfo.java
index c961bbe..e35ca45 100644
--- a/service/java/com/android/server/wifi/hotspot2/anqp/IconInfo.java
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/IconInfo.java
@@ -1,37 +1,83 @@
+/*
+ * 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.anqp;
 
-import java.net.ProtocolException;
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-import java.util.Locale;
+import android.text.TextUtils;
 
-import static com.android.server.wifi.hotspot2.anqp.Constants.SHORT_MASK;
-
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.wifi.ByteBufferReader;
 
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.util.Objects;
+
 /**
  * The Icons available OSU Providers sub field, as specified in
  * Wi-Fi Alliance Hotspot 2.0 (Release 2) Technical Specification - Version 5.00,
  * section 4.8.1.4
+ *
+ * Format:
+ *
+ * | Width | Height | Language | Type Length | Type | Filename Length | Filename |
+ *     2       2         3           1       variable        1          variable
  */
 public class IconInfo {
+    private static final int LANGUAGE_CODE_LENGTH = 3;
+
     private final int mWidth;
     private final int mHeight;
     private final String mLanguage;
     private final String mIconType;
     private final String mFileName;
 
-    public IconInfo(ByteBuffer payload) throws ProtocolException {
-        if (payload.remaining() < 9) {
-            throw new ProtocolException("Truncated icon meta data");
-        }
+    @VisibleForTesting
+    public IconInfo(int width, int height, String language, String iconType, String fileName) {
+        mWidth = width;
+        mHeight = height;
+        mLanguage = language;
+        mIconType = iconType;
+        mFileName = fileName;
+    }
 
-        mWidth = payload.getShort() & SHORT_MASK;
-        mHeight = payload.getShort() & SHORT_MASK;
-        mLanguage = ByteBufferReader.readString(
-                payload, Constants.LANG_CODE_LENGTH, StandardCharsets.US_ASCII).trim();
-        mIconType = ByteBufferReader.readStringWithByteLength(payload, StandardCharsets.US_ASCII);
-        mFileName = ByteBufferReader.readStringWithByteLength(payload, StandardCharsets.UTF_8);
+    /**
+     * Parse a IconInfo from the given buffer.
+     *
+     * @param payload The buffer to read from
+     * @return {@link IconInfo}
+     * @throws BufferUnderflowException
+     */
+    public static IconInfo parse(ByteBuffer payload) {
+        int width = (int) ByteBufferReader.readInteger(payload, ByteOrder.LITTLE_ENDIAN, 2)
+                & 0xFFFF;
+        int height = (int) ByteBufferReader.readInteger(payload, ByteOrder.LITTLE_ENDIAN, 2)
+                & 0xFFFF;
+
+        // Read the language string.
+        String language = ByteBufferReader.readString(
+                payload, LANGUAGE_CODE_LENGTH, StandardCharsets.US_ASCII).trim();
+
+        String iconType =
+                ByteBufferReader.readStringWithByteLength(payload, StandardCharsets.US_ASCII);
+        String fileName =
+                ByteBufferReader.readStringWithByteLength(payload, StandardCharsets.UTF_8);
+
+        return new IconInfo(width, height, language, iconType, fileName);
     }
 
     public int getWidth() {
@@ -59,36 +105,31 @@
         if (this == thatObject) {
             return true;
         }
-        if (thatObject == null || getClass() != thatObject.getClass()) {
+        if (!(thatObject instanceof IconInfo)) {
             return false;
         }
 
         IconInfo that = (IconInfo) thatObject;
-        return mHeight == that.mHeight &&
-                mWidth == that.mWidth &&
-                mFileName.equals(that.mFileName) &&
-                mIconType.equals(that.mIconType) &&
-                mLanguage.equals(that.mLanguage);
+        return mWidth == that.mWidth
+                && mHeight == that.mHeight
+                && TextUtils.equals(mLanguage, that.mLanguage)
+                && TextUtils.equals(mIconType, that.mIconType)
+                && TextUtils.equals(mFileName, that.mFileName);
     }
 
     @Override
     public int hashCode() {
-        int result = mWidth;
-        result = 31 * result + mHeight;
-        result = 31 * result + mLanguage.hashCode();
-        result = 31 * result + mIconType.hashCode();
-        result = 31 * result + mFileName.hashCode();
-        return result;
+        return Objects.hash(mWidth, mHeight, mLanguage, mIconType, mFileName);
     }
 
     @Override
     public String toString() {
-        return "IconInfo{" +
-                "Width=" + mWidth +
-                ", Height=" + mHeight +
-                ", Language=" + mLanguage +
-                ", IconType='" + mIconType + '\'' +
-                ", FileName='" + mFileName + '\'' +
-                '}';
+        return "IconInfo{"
+                + "Width=" + mWidth
+                + ", Height=" + mHeight
+                + ", Language=" + mLanguage
+                + ", IconType='" + mIconType + "\'"
+                + ", FileName='" + mFileName + "\'"
+                + "}";
     }
 }
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/OsuProviderInfo.java b/service/java/com/android/server/wifi/hotspot2/anqp/OsuProviderInfo.java
new file mode 100644
index 0000000..8952c5a
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/OsuProviderInfo.java
@@ -0,0 +1,301 @@
+/*
+ * 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.anqp;
+
+import android.net.Uri;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wifi.ByteBufferReader;
+
+import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * The OSU Provider subfield in the OSU Providers List ANQP Element,
+ * Wi-Fi Alliance Hotspot 2.0 (Release 2) Technical Specification - Version 5.00,
+ * section 4.8.1
+ *
+ * Format:
+ *
+ * | Length | Friendly Name Length | Friendly Name #1 | ... | Friendly Name #n |
+ *     2               2                variable                  variable
+ * | Server URI length | Server URI | Method List Length | Method List |
+ *          1             variable             1             variable
+ * | Icon Available Length | Icon Available | NAI Length | NAI | Description Length |
+ *            2                variable            1     variable      2
+ * | Description #1 | ... | Description #n |
+ *      variable               variable
+ *
+ * | Operator Name Duple #N (optional) |
+ *             variable
+ */
+public class OsuProviderInfo {
+    /**
+     * The raw payload should minimum include the following fields:
+     * - Friendly Name Length (2)
+     * - Server URI Length (1)
+     * - Method List Length (1)
+     * - Icon Available Length (2)
+     * - NAI Length (1)
+     * - Description Length (2)
+     */
+    @VisibleForTesting
+    public static final int MINIMUM_LENGTH = 9;
+
+    /**
+     * Maximum octets for a I18N string.
+     */
+    private static final int MAXIMUM_I18N_STRING_LENGTH = 252;
+
+    private final List<I18Name> mFriendlyNames;
+    private final Uri mServerUri;
+    private final List<Integer> mMethodList;
+    private final List<IconInfo> mIconInfoList;
+    private final String mNetworkAccessIdentifier;
+    private final List<I18Name> mServiceDescriptions;
+
+    @VisibleForTesting
+    public OsuProviderInfo(List<I18Name> friendlyNames, Uri serverUri, List<Integer> methodList,
+            List<IconInfo> iconInfoList, String nai, List<I18Name> serviceDescriptions) {
+        mFriendlyNames = friendlyNames;
+        mServerUri = serverUri;
+        mMethodList = methodList;
+        mIconInfoList = iconInfoList;
+        mNetworkAccessIdentifier = nai;
+        mServiceDescriptions = serviceDescriptions;
+    }
+
+    /**
+     * Parse a OsuProviderInfo from the given buffer.
+     *
+     * @param payload The buffer to read from
+     * @return {@link OsuProviderInfo}
+     * @throws BufferUnderflowException
+     * @throws ProtocolException
+     */
+    public static OsuProviderInfo parse(ByteBuffer payload)
+            throws ProtocolException {
+        // Parse length field.
+        int length = (int) ByteBufferReader.readInteger(payload, ByteOrder.LITTLE_ENDIAN, 2)
+                & 0xFFFF;
+        if (length < MINIMUM_LENGTH) {
+            throw new ProtocolException("Invalid length value: " + length);
+        }
+
+        // Parse friendly names.
+        int friendlyNameLength =
+                (int) ByteBufferReader.readInteger(payload, ByteOrder.LITTLE_ENDIAN, 2) & 0xFFFF;
+        ByteBuffer friendlyNameBuffer = getSubBuffer(payload, friendlyNameLength);
+        List<I18Name> friendlyNameList = parseI18Names(friendlyNameBuffer);
+
+        // Parse server URI.
+        Uri serverUri = Uri.parse(
+                ByteBufferReader.readStringWithByteLength(payload, StandardCharsets.UTF_8));
+
+        // Parse method list.
+        int methodListLength = payload.get() & 0xFF;
+        List<Integer> methodList = new ArrayList<>();
+        while (methodListLength > 0) {
+            methodList.add(payload.get() & 0xFF);
+            methodListLength--;
+        }
+
+        // Parse list of icon info.
+        int availableIconLength =
+                (int) ByteBufferReader.readInteger(payload, ByteOrder.LITTLE_ENDIAN, 2) & 0xFFFF;
+        ByteBuffer iconBuffer = getSubBuffer(payload, availableIconLength);
+        List<IconInfo> iconInfoList = new ArrayList<>();
+        while (iconBuffer.hasRemaining()) {
+            iconInfoList.add(IconInfo.parse(iconBuffer));
+        }
+
+        // Parse Network Access Identifier.
+        String nai = ByteBufferReader.readStringWithByteLength(payload, StandardCharsets.UTF_8);
+
+        // Parse service descriptions.
+        int serviceDescriptionLength =
+                (int) ByteBufferReader.readInteger(payload, ByteOrder.LITTLE_ENDIAN, 2) & 0xFFFF;
+        ByteBuffer descriptionsBuffer = getSubBuffer(payload, serviceDescriptionLength);
+        List<I18Name> serviceDescriptionList = parseI18Names(descriptionsBuffer);
+
+        return new OsuProviderInfo(friendlyNameList, serverUri, methodList, iconInfoList, nai,
+                serviceDescriptionList);
+    }
+
+    public List<I18Name> getFriendlyNames() {
+        return Collections.unmodifiableList(mFriendlyNames);
+    }
+
+    public Uri getServerUri() {
+        return mServerUri;
+    }
+
+    public List<Integer> getMethodList() {
+        return Collections.unmodifiableList(mMethodList);
+    }
+
+    public List<IconInfo> getIconInfoList() {
+        return Collections.unmodifiableList(mIconInfoList);
+    }
+
+    public String getNetworkAccessIdentifier() {
+        return mNetworkAccessIdentifier;
+    }
+
+    public List<I18Name> getServiceDescriptions() {
+        return Collections.unmodifiableList(mServiceDescriptions);
+    }
+
+    /**
+     * Return the friendly name string from the friendly name list.  The string matching
+     * the default locale will be returned if it is found, otherwise the first name in the list
+     * will be returned.  A null will be returned if the list is empty.
+     *
+     * @return friendly name string
+     */
+    public String getFriendlyName() {
+        return getI18String(mFriendlyNames);
+    }
+
+    /**
+     * Return the service description string from the service description list.  The string
+     * matching the default locale will be returned if it is found, otherwise the first element in
+     * the list will be returned.  A null will be returned if the list is empty.
+     *
+     * @return service description string
+     */
+    public String getServiceDescription() {
+        return getI18String(mServiceDescriptions);
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        if (!(thatObject instanceof OsuProviderInfo)) {
+            return false;
+        }
+        OsuProviderInfo that = (OsuProviderInfo) thatObject;
+        return (mFriendlyNames == null ? that.mFriendlyNames == null
+                        : mFriendlyNames.equals(that.mFriendlyNames))
+                && (mServerUri == null ? that.mServerUri == null
+                        : mServerUri.equals(that.mServerUri))
+                && (mMethodList == null ? that.mMethodList == null
+                        : mMethodList.equals(that.mMethodList))
+                && (mIconInfoList == null ? that.mIconInfoList == null
+                        : mIconInfoList.equals(that.mIconInfoList))
+                && TextUtils.equals(mNetworkAccessIdentifier, that.mNetworkAccessIdentifier)
+                && (mServiceDescriptions == null ? that.mServiceDescriptions == null
+                        : mServiceDescriptions.equals(that.mServiceDescriptions));
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mFriendlyNames, mServerUri, mMethodList, mIconInfoList,
+                mNetworkAccessIdentifier, mServiceDescriptions);
+    }
+
+    @Override
+    public String toString() {
+        return "OsuProviderInfo{"
+                + "mFriendlyNames=" + mFriendlyNames
+                + ", mServerUri=" + mServerUri
+                + ", mMethodList=" + mMethodList
+                + ", mIconInfoList=" + mIconInfoList
+                + ", mNetworkAccessIdentifier=" + mNetworkAccessIdentifier
+                + ", mServiceDescriptions=" + mServiceDescriptions
+                + "}";
+    }
+
+    /**
+     * Parse list of I18N string from the given payload.
+     *
+     * @param payload The payload to parse from
+     * @return List of {@link I18Name}
+     * @throws ProtocolException
+     */
+    private static List<I18Name> parseI18Names(ByteBuffer payload) throws ProtocolException {
+        List<I18Name> results = new ArrayList<>();
+        while (payload.hasRemaining()) {
+            I18Name name = I18Name.parse(payload);
+            // Verify that the number of bytes for the operator name doesn't exceed the max
+            // allowed.
+            int textBytes = name.getText().getBytes(StandardCharsets.UTF_8).length;
+            if (textBytes > MAXIMUM_I18N_STRING_LENGTH) {
+                throw new ProtocolException("I18Name string exceeds the maximum allowed "
+                        + textBytes);
+            }
+            results.add(name);
+        }
+        return results;
+    }
+
+    /**
+     * Creates a new byte buffer whose content is a shared subsequence of
+     * the given buffer's content.
+     *
+     * The sub buffer will starts from |payload|'s current position
+     * and ends at |payload|'s current position plus |length|.  The |payload|'s current
+     * position will advance pass |length| bytes.
+     *
+     * @param payload The original buffer
+     * @param length The length of the new buffer
+     * @return {@link ByteBuffer}
+     * @throws BufferUnderflowException
+     */
+    private static ByteBuffer getSubBuffer(ByteBuffer payload, int length) {
+        if (payload.remaining() < length) {
+            throw new BufferUnderflowException();
+        }
+        // Set the subBuffer's starting and ending position.
+        ByteBuffer subBuffer = payload.slice();
+        subBuffer.limit(length);
+        // Advance the original buffer's current position.
+        payload.position(payload.position() + length);
+        return subBuffer;
+    }
+
+    /**
+     * Return the appropriate I18 string value from the list of I18 string values.
+     * The string matching the default locale will be returned if it is found, otherwise the
+     * first string in the list will be returned.  A null will be returned if the list is empty.
+     *
+     * @param i18Strings List of I18 string values
+     * @return String matching the default locale, null otherwise
+     */
+    private static String getI18String(List<I18Name> i18Strings) {
+        for (I18Name name : i18Strings) {
+            if (name.getLanguage().equals(Locale.getDefault().getLanguage())) {
+                return name.getText();
+            }
+        }
+        if (i18Strings.size() > 0) {
+            return i18Strings.get(0).getText();
+        }
+        return null;
+    }
+}
diff --git a/service/java/com/android/server/wifi/p2p/SupplicantP2pIfaceHal.java b/service/java/com/android/server/wifi/p2p/SupplicantP2pIfaceHal.java
index 3e26828..50f2ccf 100644
--- a/service/java/com/android/server/wifi/p2p/SupplicantP2pIfaceHal.java
+++ b/service/java/com/android/server/wifi/p2p/SupplicantP2pIfaceHal.java
@@ -937,7 +937,8 @@
                             String ssidString = null;
                             if (ssid != null) {
                                 try {
-                                    ssidString = NativeUtil.encodeSsid(ssid);
+                                    ssidString = NativeUtil.removeEnclosingQuotes(
+                                            NativeUtil.encodeSsid(ssid));
                                 } catch (Exception e) {
                                     Log.e(TAG, "Could not encode SSID.", e);
                                 }
diff --git a/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java b/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
index 623a2f0..d70ce41 100644
--- a/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
+++ b/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
@@ -24,6 +24,7 @@
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.hardware.wifi.V1_0.IWifiP2pIface;
 import android.net.ConnectivityManager;
 import android.net.DhcpResults;
 import android.net.InterfaceConfiguration;
@@ -77,6 +78,7 @@
 import com.android.internal.util.Protocol;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
+import com.android.server.wifi.HalDeviceManager;
 import com.android.server.wifi.WifiInjector;
 import com.android.server.wifi.WifiStateMachine;
 import com.android.server.wifi.util.WifiAsyncChannel;
@@ -91,6 +93,7 @@
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * WifiP2pService includes a state machine to perform Wi-Fi p2p operations. Applications
@@ -228,6 +231,9 @@
     // the ranges defined in Tethering.java
     private static final String SERVER_ADDRESS = "192.168.49.1";
 
+    // The empty device address set by wpa_supplicant.
+    private static final String EMPTY_DEVICE_ADDRESS = "00:00:00:00:00:00";
+
     /**
      * Error code definition.
      * see the Table.8 in the WiFi Direct specification for the detail.
@@ -358,6 +364,25 @@
     }
     private ClientHandler mClientHandler;
 
+    private class DeathHandlerData {
+        DeathHandlerData(DeathRecipient dr, Messenger m) {
+            mDeathRecipient = dr;
+            mMessenger = m;
+        }
+
+        @Override
+        public String toString() {
+            return "deathRecipient=" + mDeathRecipient + ", messenger=" + mMessenger;
+        }
+
+        DeathRecipient mDeathRecipient;
+        Messenger mMessenger;
+    }
+    private Object mLock = new Object();
+    private final Map<IBinder, DeathHandlerData> mDeathDataByBinder = new HashMap<>();
+    private HalDeviceManager mHalDeviceManager;
+    private IWifiP2pIface mIWifiP2pIface;
+
     public WifiP2pServiceImpl(Context context) {
         mContext = context;
 
@@ -468,10 +493,47 @@
      * an AsyncChannel communication with WifiP2pService
      */
     @Override
-    public Messenger getMessenger() {
+    public Messenger getMessenger(final IBinder binder) {
         enforceAccessPermission();
         enforceChangePermission();
-        return new Messenger(mClientHandler);
+
+        synchronized (mLock) {
+            final Messenger messenger = new Messenger(mClientHandler);
+            if (DBG) {
+                Log.d(TAG, "getMessenger: uid=" + getCallingUid() + ", binder=" + binder
+                        + ", messenger=" + messenger);
+            }
+
+            IBinder.DeathRecipient dr = () -> {
+                if (DBG) Log.d(TAG, "binderDied: binder=" + binder);
+                close(binder);
+            };
+
+            try {
+                binder.linkToDeath(dr, 0);
+                mDeathDataByBinder.put(binder, new DeathHandlerData(dr, messenger));
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error on linkToDeath: e=" + e);
+                // fall-through here - won't clean up
+            }
+
+            if (mIWifiP2pIface == null) {
+                if (mHalDeviceManager == null) {
+                    if (mWifiInjector == null) {
+                        mWifiInjector = WifiInjector.getInstance();
+                    }
+                    mHalDeviceManager = mWifiInjector.getHalDeviceManager();
+                }
+                mIWifiP2pIface = mHalDeviceManager.createP2pIface(() -> {
+                    if (DBG) Log.d(TAG, "IWifiP2pIface destroyedListener");
+                    synchronized (mLock) {
+                        mIWifiP2pIface = null;
+                    }
+                }, mP2pStateMachine.getHandler().getLooper());
+            }
+
+            return messenger;
+        }
     }
 
     /**
@@ -487,6 +549,45 @@
         return new Messenger(mP2pStateMachine.getHandler());
     }
 
+    /**
+     * Clean-up the state and configuration requested by the closing app. Takes same action as
+     * when the app dies (binder death).
+     */
+    @Override
+    public void close(IBinder binder) {
+        enforceAccessPermission();
+        enforceChangePermission();
+
+        DeathHandlerData dhd;
+        synchronized (mLock) {
+            dhd = mDeathDataByBinder.get(binder);
+            if (dhd == null) {
+                Log.w(TAG, "close(): no death recipient for binder");
+                return;
+            }
+
+            binder.unlinkToDeath(dhd.mDeathRecipient, 0);
+            mDeathDataByBinder.remove(binder);
+
+            // clean-up if there are no more clients registered
+            // TODO: what does the WifiStateMachine client do? It isn't tracked through here!
+            if (dhd.mMessenger != null && mDeathDataByBinder.isEmpty()) {
+                try {
+                    dhd.mMessenger.send(
+                            mClientHandler.obtainMessage(WifiP2pManager.STOP_DISCOVERY));
+                    dhd.mMessenger.send(mClientHandler.obtainMessage(WifiP2pManager.REMOVE_GROUP));
+                } catch (RemoteException e) {
+                    Log.e(TAG, "close: Failed sending clean-up commands: e=" + e);
+                }
+
+                if (mIWifiP2pIface != null) {
+                    mHalDeviceManager.removeIface(mIWifiP2pIface);
+                    mIWifiP2pIface = null;
+                }
+            }
+        }
+    }
+
     /** This is used to provide information to drivers to optimize performance depending
      * on the current mode of operation.
      * 0 - disabled
@@ -538,6 +639,7 @@
         pw.println("mNetworkInfo " + mNetworkInfo);
         pw.println("mTemporarilyDisconnectedWifi " + mTemporarilyDisconnectedWifi);
         pw.println("mServiceDiscReqId " + mServiceDiscReqId);
+        pw.println("mDeathDataByBinder " + mDeathDataByBinder);
         pw.println();
 
         final IpManager ipManager = mIpManager;
@@ -1491,7 +1593,11 @@
                         }
                         mGroup = (WifiP2pGroup) message.obj;
                         if (DBG) logd(getName() + " group started");
-
+                        if (mGroup.isGroupOwner()
+                                && EMPTY_DEVICE_ADDRESS.equals(mGroup.getOwner().deviceAddress)) {
+                            // wpa_supplicant doesn't set own device address to go_dev_addr.
+                            mGroup.getOwner().deviceAddress = mThisDevice.deviceAddress;
+                        }
                         // We hit this scenario when a persistent group is reinvoked
                         if (mGroup.getNetworkId() == WifiP2pGroup.PERSISTENT_NET_ID) {
                             mAutonomousGroup = false;
@@ -1824,9 +1930,14 @@
                         }
                         mGroup = (WifiP2pGroup) message.obj;
                         if (DBG) logd(getName() + " group started");
+                        if (mGroup.isGroupOwner()
+                                && EMPTY_DEVICE_ADDRESS.equals(mGroup.getOwner().deviceAddress)) {
+                            // wpa_supplicant doesn't set own device address to go_dev_addr.
+                            mGroup.getOwner().deviceAddress = mThisDevice.deviceAddress;
+                        }
                         if (mGroup.getNetworkId() == WifiP2pGroup.PERSISTENT_NET_ID) {
                              // update cache information and set network id to mGroup.
-                            updatePersistentNetworks(NO_RELOAD);
+                            updatePersistentNetworks(RELOAD);
                             String devAddr = mGroup.getOwner().deviceAddress;
                             mGroup.setNetworkId(mGroups.getNetworkId(devAddr,
                                     mGroup.getNetworkName()));
@@ -2245,12 +2356,7 @@
                             int netId = mGroup.getNetworkId();
                             if (netId >= 0) {
                                 if (DBG) logd("Remove unknown client from the list");
-                                if (!removeClientFromList(netId,
-                                        mSavedPeerConfig.deviceAddress, false)) {
-                                    // not found the client on the list
-                                    loge("Already removed the client, ignore");
-                                    break;
-                                }
+                                removeClientFromList(netId, mSavedPeerConfig.deviceAddress, false);
                                 // try invitation.
                                 sendMessage(WifiP2pManager.CONNECT, mSavedPeerConfig);
                             }
diff --git a/service/java/com/android/server/wifi/scanner/HalWifiScannerImpl.java b/service/java/com/android/server/wifi/scanner/HalWifiScannerImpl.java
index 211e62a..0fadd80 100644
--- a/service/java/com/android/server/wifi/scanner/HalWifiScannerImpl.java
+++ b/service/java/com/android/server/wifi/scanner/HalWifiScannerImpl.java
@@ -27,6 +27,9 @@
 import com.android.server.wifi.WifiMonitor;
 import com.android.server.wifi.WifiNative;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
 /**
  * WifiScanner implementation that takes advantage of the gscan HAL API
  * The gscan API is used to perform background scans and wificond is used for oneshot scans.
@@ -151,4 +154,9 @@
             return mWificondScannerDelegate.shouldScheduleBackgroundScanForHwPno();
         }
     }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        mWificondScannerDelegate.dump(fd, pw, args);
+    }
 }
diff --git a/service/java/com/android/server/wifi/scanner/WifiScannerImpl.java b/service/java/com/android/server/wifi/scanner/WifiScannerImpl.java
index e0fb535..5281b3a 100644
--- a/service/java/com/android/server/wifi/scanner/WifiScannerImpl.java
+++ b/service/java/com/android/server/wifi/scanner/WifiScannerImpl.java
@@ -26,6 +26,8 @@
 import com.android.server.wifi.WifiMonitor;
 import com.android.server.wifi.WifiNative;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.Comparator;
 
 /**
@@ -158,4 +160,6 @@
      * @return true if background scan needs to be started, false otherwise.
      */
     public abstract boolean shouldScheduleBackgroundScanForHwPno();
+
+    protected abstract void dump(FileDescriptor fd, PrintWriter pw, String[] args);
 }
diff --git a/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java b/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
index af874b9..ab2a5dc 100644
--- a/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
+++ b/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
@@ -2156,6 +2156,9 @@
             }
             pw.println();
         }
+        if (mScannerImpl != null) {
+            mScannerImpl.dump(fd, pw, args);
+        }
     }
 
     void logScanRequest(String request, ClientInfo ci, int id, WorkSource workSource,
diff --git a/service/java/com/android/server/wifi/scanner/WificondScannerImpl.java b/service/java/com/android/server/wifi/scanner/WificondScannerImpl.java
index fd7fddb..84105ee 100644
--- a/service/java/com/android/server/wifi/scanner/WificondScannerImpl.java
+++ b/service/java/com/android/server/wifi/scanner/WificondScannerImpl.java
@@ -32,6 +32,8 @@
 import com.android.server.wifi.WifiNative;
 import com.android.server.wifi.scanner.ChannelHelper.ChannelCollection;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -81,6 +83,7 @@
     private boolean mBackgroundScanPaused = false;
     private ScanBuffer mBackgroundScanBuffer = new ScanBuffer(SCAN_BUFFER_CAPACITY);
 
+    private ArrayList<ScanDetail> mNativeScanResults;
     private WifiScanner.ScanData mLatestSingleScanResult =
             new WifiScanner.ScanData(0, 0, new ScanResult[0]);
 
@@ -535,11 +538,11 @@
                  // got a scan before we started scanning or after scan was canceled
                 return;
             }
-            ArrayList<ScanDetail> nativeResults = mWifiNative.getScanResults();
+            mNativeScanResults = mWifiNative.getScanResults();
             List<ScanResult> hwPnoScanResults = new ArrayList<>();
             int numFilteredScanResults = 0;
-            for (int i = 0; i < nativeResults.size(); ++i) {
-                ScanResult result = nativeResults.get(i).getScanResult();
+            for (int i = 0; i < mNativeScanResults.size(); ++i) {
+                ScanResult result = mNativeScanResults.get(i).getScanResult();
                 long timestamp_ms = result.timestamp / 1000; // convert us -> ms
                 if (timestamp_ms > mLastScanSettings.startTime) {
                     if (mLastScanSettings.hwPnoScanActive) {
@@ -556,11 +559,8 @@
 
             if (mLastScanSettings.hwPnoScanActive
                     && mLastScanSettings.pnoScanEventHandler != null) {
-                ScanResult[] pnoScanResultsArray = new ScanResult[hwPnoScanResults.size()];
-                for (int i = 0; i < pnoScanResultsArray.length; ++i) {
-                    ScanResult result = nativeResults.get(i).getScanResult();
-                    pnoScanResultsArray[i] = hwPnoScanResults.get(i);
-                }
+                ScanResult[] pnoScanResultsArray =
+                        hwPnoScanResults.toArray(new ScanResult[hwPnoScanResults.size()]);
                 mLastScanSettings.pnoScanEventHandler.onPnoNetworkFound(pnoScanResultsArray);
             }
             // On pno scan result event, we are expecting a mLastScanSettings for pno scan.
@@ -598,12 +598,12 @@
             }
 
             if (DBG) Log.d(TAG, "Polling scan data for scan: " + mLastScanSettings.scanId);
-            ArrayList<ScanDetail> nativeResults = mWifiNative.getScanResults();
+            mNativeScanResults = mWifiNative.getScanResults();
             List<ScanResult> singleScanResults = new ArrayList<>();
             List<ScanResult> backgroundScanResults = new ArrayList<>();
             int numFilteredScanResults = 0;
-            for (int i = 0; i < nativeResults.size(); ++i) {
-                ScanResult result = nativeResults.get(i).getScanResult();
+            for (int i = 0; i < mNativeScanResults.size(); ++i) {
+                ScanResult result = mNativeScanResults.get(i).getScanResult();
                 long timestamp_ms = result.timestamp / 1000; // convert us -> ms
                 if (timestamp_ms > mLastScanSettings.startTime) {
                     if (mLastScanSettings.backgroundScanActive) {
@@ -772,6 +772,40 @@
         return false;
     }
 
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        synchronized (mSettingsLock) {
+            pw.println("Latest native scan results:");
+            if (mNativeScanResults != null && mNativeScanResults.size() != 0) {
+                long nowMs = mClock.getElapsedSinceBootMillis();
+                pw.println("    BSSID              Frequency  RSSI  Age(sec)   SSID "
+                        + "                                Flags");
+                for (ScanDetail scanDetail : mNativeScanResults) {
+                    ScanResult r = scanDetail.getScanResult();
+                    long timeStampMs = r.timestamp / 1000;
+                    String age;
+                    if (timeStampMs <= 0) {
+                        age = "___?___";
+                    } else if (nowMs < timeStampMs) {
+                        age = "  0.000";
+                    } else if (timeStampMs < nowMs - 1000000) {
+                        age = ">1000.0";
+                    } else {
+                        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",
+                              r.BSSID,
+                              r.frequency,
+                              r.level,
+                              age,
+                              String.format("%1.32s", ssid),
+                              r.capabilities);
+                }
+            }
+        }
+    }
+
     private static class LastScanSettings {
         public long startTime;
 
diff --git a/service/java/com/android/server/wifi/util/ScanResultUtil.java b/service/java/com/android/server/wifi/util/ScanResultUtil.java
index 0e08701..4fcafb8 100644
--- a/service/java/com/android/server/wifi/util/ScanResultUtil.java
+++ b/service/java/com/android/server/wifi/util/ScanResultUtil.java
@@ -18,11 +18,9 @@
 
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
-import android.text.TextUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.wifi.ScanDetail;
-import com.android.server.wifi.WifiConfigurationUtil;
 import com.android.server.wifi.hotspot2.NetworkDetail;
 
 /**
@@ -89,35 +87,6 @@
     }
 
     /**
-     * Checks if the provided |scanResult| match with the provided |config|. Essentially checks
-     * if the network config and scan result have the same SSID and encryption type.
-     */
-    public static boolean doesScanResultMatchWithNetwork(
-            ScanResult scanResult, WifiConfiguration config) {
-        // Add the double quotes to the scan result SSID for comparison with the network configs.
-        String configSSID = createQuotedSSID(scanResult.SSID);
-        if (TextUtils.equals(config.SSID, configSSID)) {
-            if (ScanResultUtil.isScanResultForPskNetwork(scanResult)
-                    && WifiConfigurationUtil.isConfigForPskNetwork(config)) {
-                return true;
-            }
-            if (ScanResultUtil.isScanResultForEapNetwork(scanResult)
-                    && WifiConfigurationUtil.isConfigForEapNetwork(config)) {
-                return true;
-            }
-            if (ScanResultUtil.isScanResultForWepNetwork(scanResult)
-                    && WifiConfigurationUtil.isConfigForWepNetwork(config)) {
-                return true;
-            }
-            if (ScanResultUtil.isScanResultForOpenNetwork(scanResult)
-                    && WifiConfigurationUtil.isConfigForOpenNetwork(config)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
      * Creates a network configuration object using the provided |scanResult|.
      * This is used to create ephemeral network configurations.
      */
diff --git a/service/java/com/android/server/wifi/util/WifiPermissionsUtil.java b/service/java/com/android/server/wifi/util/WifiPermissionsUtil.java
index 90ec060..c5eea7d 100644
--- a/service/java/com/android/server/wifi/util/WifiPermissionsUtil.java
+++ b/service/java/com/android/server/wifi/util/WifiPermissionsUtil.java
@@ -76,6 +76,22 @@
     }
 
     /**
+     * Checks if the app has the permission to change Wi-Fi network configuration or not.
+     *
+     * @param uid uid of the app.
+     * @return true if the app does have the permission, false otherwise.
+     */
+    public boolean checkChangePermission(int uid) {
+        try {
+            int permission = mWifiPermissionsWrapper.getChangeWifiConfigPermission(uid);
+            return (permission == PackageManager.PERMISSION_GRANTED);
+        } catch (RemoteException e) {
+            mLog.err("Error checking for permission: %").r(e.getMessage()).flush();
+            return false;
+        }
+    }
+
+    /**
      * Check and enforce tether change permission.
      *
      * @param context Context object of the caller.
@@ -240,4 +256,13 @@
         return (mSettingsStore.getLocationModeSetting(mContext)
                  != Settings.Secure.LOCATION_MODE_OFF);
     }
+
+    /**
+     * Returns true if the |uid| holds NETWORK_SETTINGS permission.
+     */
+    public boolean checkNetworkSettingsPermission(int uid) {
+        return mWifiPermissionsWrapper.getUidPermission(
+                android.Manifest.permission.NETWORK_SETTINGS, uid)
+                == PackageManager.PERMISSION_GRANTED;
+    }
 }
diff --git a/service/java/com/android/server/wifi/util/WifiPermissionsWrapper.java b/service/java/com/android/server/wifi/util/WifiPermissionsWrapper.java
index 6ca2f02..6fde01e 100644
--- a/service/java/com/android/server/wifi/util/WifiPermissionsWrapper.java
+++ b/service/java/com/android/server/wifi/util/WifiPermissionsWrapper.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wifi.util;
 
+import android.Manifest;
 import android.app.ActivityManager;
 import android.app.AppGlobals;
 import android.app.admin.DevicePolicyManagerInternal;
@@ -95,4 +96,16 @@
         return AppGlobals.getPackageManager().checkUidPermission(
                 android.Manifest.permission.OVERRIDE_WIFI_CONFIG, uid);
     }
+
+    /**
+     * Determines if the caller has the change wifi config permission.
+     *
+     * @param uid to check the permission for
+     * @return int representation of success or denied
+     * @throws RemoteException
+     */
+    public int getChangeWifiConfigPermission(int uid) throws RemoteException {
+        return AppGlobals.getPackageManager().checkUidPermission(
+                Manifest.permission.CHANGE_WIFI_STATE, uid);
+    }
 }
diff --git a/tests/wifitests/Android.mk b/tests/wifitests/Android.mk
index 94f3435..1e64ddb 100644
--- a/tests/wifitests/Android.mk
+++ b/tests/wifitests/Android.mk
@@ -114,4 +114,6 @@
 
 LOCAL_PACKAGE_NAME := FrameworksWifiTests
 
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
 include $(BUILD_PACKAGE)
diff --git a/tests/wifitests/AndroidManifest.xml b/tests/wifitests/AndroidManifest.xml
index 63b9542..dcb61c7 100644
--- a/tests/wifitests/AndroidManifest.xml
+++ b/tests/wifitests/AndroidManifest.xml
@@ -30,7 +30,7 @@
         </activity>
     </application>
 
-    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+    <instrumentation android:name="com.android.server.wifi.CustomTestRunner"
         android:targetPackage="com.android.server.wifi.test"
         android:label="Frameworks Wifi Tests">
     </instrumentation>
diff --git a/tests/wifitests/AndroidTest.xml b/tests/wifitests/AndroidTest.xml
new file mode 100644
index 0000000..4fe21aa
--- /dev/null
+++ b/tests/wifitests/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs Frameworks Wifi Tests.">
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+        <option name="test-file-name" value="FrameworksWifiTests.apk" />
+    </target_preparer>
+
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-tag" value="FrameworksWifiTests" />
+    <test class="com.android.tradefed.testtype.InstrumentationTest" >
+        <option name="package" value="com.android.server.wifi.test" />
+        <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+    </test>
+</configuration>
diff --git a/tests/wifitests/coverage.sh b/tests/wifitests/coverage.sh
index c81af56..3ed71ad 100755
--- a/tests/wifitests/coverage.sh
+++ b/tests/wifitests/coverage.sh
@@ -49,7 +49,7 @@
 
 adb install -r -g "$OUT/data/app/FrameworksWifiTests/FrameworksWifiTests.apk"
 
-adb shell am instrument -e coverage true -w 'com.android.server.wifi.test/android.support.test.runner.AndroidJUnitRunner'
+adb shell am instrument -e coverage true -w 'com.android.server.wifi.test/com.android.server.wifi.CustomTestRunner'
 
 mkdir -p $OUTPUT_DIR
 
diff --git a/tests/wifitests/runtests.sh b/tests/wifitests/runtests.sh
index 529a535..7a34bf7 100755
--- a/tests/wifitests/runtests.sh
+++ b/tests/wifitests/runtests.sh
@@ -42,4 +42,4 @@
 
 adb shell am instrument -w "$@" \
   -e notAnnotation com.android.server.wifi.DisabledForUpdateToAnyMatcher \
-  'com.android.server.wifi.test/android.support.test.runner.AndroidJUnitRunner'
+  'com.android.server.wifi.test/com.android.server.wifi.CustomTestRunner'
diff --git a/tests/wifitests/src/com/android/server/wifi/ConfigurationMapTest.java b/tests/wifitests/src/com/android/server/wifi/ConfigurationMapTest.java
index 6827d95..e30a82f 100644
--- a/tests/wifitests/src/com/android/server/wifi/ConfigurationMapTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/ConfigurationMapTest.java
@@ -17,12 +17,14 @@
 package com.android.server.wifi;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.when;
 
 import android.app.test.MockAnswerUtil.AnswerWithArguments;
 import android.content.pm.UserInfo;
+import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -36,6 +38,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -89,19 +92,38 @@
         mConfigs = new ConfigurationMap(mUserManager);
     }
 
-    public void switchUser(int newUserId) {
+    private void switchUser(int newUserId) {
         mCurrentUserId = newUserId;
         mConfigs.setNewUser(newUserId);
         mConfigs.clear();
     }
 
-    public void addNetworks(List<WifiConfiguration> configs) {
+    private Collection<WifiConfiguration> getEnabledNetworksForCurrentUser() {
+        List<WifiConfiguration> list = new ArrayList<>();
+        for (WifiConfiguration config : mConfigs.valuesForCurrentUser()) {
+            if (config.status != WifiConfiguration.Status.DISABLED) {
+                list.add(config);
+            }
+        }
+        return list;
+    }
+
+    private WifiConfiguration getEphemeralForCurrentUser(String ssid) {
+        for (WifiConfiguration config : mConfigs.valuesForCurrentUser()) {
+            if (ssid.equals(config.SSID) && config.ephemeral) {
+                return config;
+            }
+        }
+        return null;
+    }
+
+    private void addNetworks(List<WifiConfiguration> configs) {
         for (WifiConfiguration config : configs) {
             assertNull(mConfigs.put(config));
         }
     }
 
-    public void verifyGetters(List<WifiConfiguration> configs) {
+    private void verifyGetters(List<WifiConfiguration> configs) {
         final Set<WifiConfiguration> configsForCurrentUser = new HashSet<>();
         final Set<WifiConfiguration> enabledConfigsForCurrentUser = new HashSet<>();
         final List<WifiConfiguration> configsNotForCurrentUser = new ArrayList<>();
@@ -127,15 +149,12 @@
         // visible to the current user.
         for (WifiConfiguration config : configsForCurrentUser) {
             assertEquals(config, mConfigs.getForCurrentUser(config.networkId));
-            if (config.FQDN != null) {
-                assertEquals(config, mConfigs.getByFQDNForCurrentUser(config.FQDN));
-            }
             assertEquals(config, mConfigs.getByConfigKeyForCurrentUser(config.configKey()));
             final boolean wasEphemeral = config.ephemeral;
             config.ephemeral = false;
-            assertNull(mConfigs.getEphemeralForCurrentUser(config.SSID));
+            assertNull(getEphemeralForCurrentUser(config.SSID));
             config.ephemeral = true;
-            assertEquals(config, mConfigs.getEphemeralForCurrentUser(config.SSID));
+            assertEquals(config, getEphemeralForCurrentUser(config.SSID));
             config.ephemeral = wasEphemeral;
         }
 
@@ -143,15 +162,12 @@
         // visible to the current user.
         for (WifiConfiguration config : configsNotForCurrentUser) {
             assertNull(mConfigs.getForCurrentUser(config.networkId));
-            if (config.FQDN != null) {
-                assertNull(mConfigs.getByFQDNForCurrentUser(config.FQDN));
-            }
             assertNull(mConfigs.getByConfigKeyForCurrentUser(config.configKey()));
             final boolean wasEphemeral = config.ephemeral;
             config.ephemeral = false;
-            assertNull(mConfigs.getEphemeralForCurrentUser(config.SSID));
+            assertNull(getEphemeralForCurrentUser(config.SSID));
             config.ephemeral = true;
-            assertNull(mConfigs.getEphemeralForCurrentUser(config.SSID));
+            assertNull(getEphemeralForCurrentUser(config.SSID));
             config.ephemeral = wasEphemeral;
         }
 
@@ -160,11 +176,29 @@
         assertEquals(configs.size(), mConfigs.sizeForAllUsers());
         assertEquals(configsForCurrentUser.size(), mConfigs.sizeForCurrentUser());
         assertEquals(enabledConfigsForCurrentUser,
-                new HashSet<WifiConfiguration>(mConfigs.getEnabledNetworksForCurrentUser()));
+                new HashSet<WifiConfiguration>(getEnabledNetworksForCurrentUser()));
         assertEquals(new HashSet<>(configs),
                 new HashSet<WifiConfiguration>(mConfigs.valuesForAllUsers()));
     }
 
+    private ScanResult createScanResultForNetwork(WifiConfiguration config) {
+        return WifiConfigurationTestUtil.createScanDetailForNetwork(config, "", 0, 0, 0, 0)
+                .getScanResult();
+    }
+
+    /**
+     * Helper function to create a scan result matching the network and ensuring that
+     * {@link ConfigurationMap#getByScanResultForCurrentUser(ScanResult)} can match that network.
+     */
+    private void verifyScanResultMatchWithNetwork(WifiConfiguration config) {
+        mConfigs.put(config);
+        ScanResult scanResult = createScanResultForNetwork(config);
+        WifiConfiguration retrievedConfig =
+                mConfigs.getByScanResultForCurrentUser(scanResult);
+        assertNotNull(retrievedConfig);
+        assertEquals(config.configKey(), retrievedConfig.configKey());
+    }
+
     /**
      * Verifies that all getters return the correct network configurations, taking into account the
      * current user. Also verifies that handleUserSwitch() returns the list of network
@@ -230,4 +264,67 @@
         configs.clear();
         verifyGetters(configs);
     }
+
+    /**
+     * Verifies that {@link ConfigurationMap#getByScanResultForCurrentUser(ScanResult)} can
+     * positively match the corresponding networks.
+     */
+    @Test
+    public void testScanResultDoesMatchCorrespondingNetworks() {
+        verifyScanResultMatchWithNetwork(WifiConfigurationTestUtil.createOpenNetwork());
+        verifyScanResultMatchWithNetwork(WifiConfigurationTestUtil.createPskNetwork());
+        verifyScanResultMatchWithNetwork(WifiConfigurationTestUtil.createWepNetwork());
+        verifyScanResultMatchWithNetwork(WifiConfigurationTestUtil.createEapNetwork());
+    }
+
+    /**
+     * Verifies that {@link ConfigurationMap#getByScanResultForCurrentUser(ScanResult)} does not
+     * match other networks.
+     */
+    @Test
+    public void testScanResultDoesNotMatchWithOtherNetworks() {
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+        ScanResult scanResult = createScanResultForNetwork(config);
+        // Change the network security type and the old scan result should not match now.
+        config.allowedKeyManagement.clear();
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+        mConfigs.put(config);
+        assertNull(mConfigs.getByScanResultForCurrentUser(scanResult));
+    }
+
+    /**
+     * Verifies that {@link ConfigurationMap#getByScanResultForCurrentUser(ScanResult)} does not
+     * match networks which have been removed.
+     */
+    @Test
+    public void testScanResultDoesNotMatchAfterNetworkRemove() {
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+        ScanResult scanResult = createScanResultForNetwork(config);
+        config.networkId = 5;
+        mConfigs.put(config);
+        // Create another network in the map.
+        mConfigs.put(WifiConfigurationTestUtil.createPskNetwork());
+        assertNotNull(mConfigs.getByScanResultForCurrentUser(scanResult));
+
+        mConfigs.remove(config.networkId);
+        assertNull(mConfigs.getByScanResultForCurrentUser(scanResult));
+    }
+
+    /**
+     * Verifies that {@link ConfigurationMap#getByScanResultForCurrentUser(ScanResult)} does not
+     * match networks after clear.
+     */
+    @Test
+    public void testScanResultDoesNotMatchAfterClear() {
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+        ScanResult scanResult = createScanResultForNetwork(config);
+        config.networkId = 5;
+        mConfigs.put(config);
+        // Create another network in the map.
+        mConfigs.put(WifiConfigurationTestUtil.createPskNetwork());
+        assertNotNull(mConfigs.getByScanResultForCurrentUser(scanResult));
+
+        mConfigs.clear();
+        assertNull(mConfigs.getByScanResultForCurrentUser(scanResult));
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/CustomTestRunner.java b/tests/wifitests/src/com/android/server/wifi/CustomTestRunner.java
new file mode 100644
index 0000000..d51f16e
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/CustomTestRunner.java
@@ -0,0 +1,33 @@
+/*
+ * 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.os.Bundle;
+import android.support.test.runner.AndroidJUnitRunner;
+import android.util.Log;
+
+import static org.mockito.Mockito.mock;
+
+public class CustomTestRunner extends AndroidJUnitRunner {
+    @Override
+    public void onCreate(Bundle arguments) {
+        // Override the default TerribleFailureHandler, as that handler might terminate
+        // the process (if we're on an eng build).
+        Log.setWtfHandler(mock(Log.TerribleFailureHandler.class));
+        super.onCreate(arguments);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/HalDeviceManagerTest.java b/tests/wifitests/src/com/android/server/wifi/HalDeviceManagerTest.java
index 3e203a6..7a25e17 100644
--- a/tests/wifitests/src/com/android/server/wifi/HalDeviceManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/HalDeviceManagerTest.java
@@ -120,6 +120,9 @@
                 anyLong())).thenReturn(true);
         when(mServiceManagerMock.registerForNotifications(anyString(), anyString(),
                 any(IServiceNotification.Stub.class))).thenReturn(true);
+        when(mServiceManagerMock.getTransport(
+                eq(IWifi.kInterfaceName), eq(HalDeviceManager.HAL_INSTANCE_NAME)))
+                .thenReturn(IServiceManager.Transport.HWBINDER);
         when(mWifiMock.linkToDeath(any(IHwBinder.DeathRecipient.class), anyLong())).thenReturn(
                 true);
         when(mWifiMock.registerEventCallback(any(IWifiEventCallback.class))).thenReturn(mStatusOk);
@@ -164,6 +167,22 @@
     }
 
     /**
+     * Test the service manager notification coming in after
+     * {@link HalDeviceManager#initIWifiIfNecessary()} is already invoked as a part of
+     * {@link HalDeviceManager#initialize()}.
+     */
+    @Test
+    public void testServiceRegisterationAfterInitialize() throws Exception {
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+
+        // This should now be ignored since IWifi is already non-null.
+        mServiceNotificationCaptor.getValue().onRegistration(IWifi.kInterfaceName, "", true);
+
+        verifyNoMoreInteractions(mManagerStatusListenerMock, mWifiMock, mServiceManagerMock);
+    }
+
+    /**
      * Validate that multiple callback registrations are called and that duplicate ones are
      * only called once.
      */
@@ -221,7 +240,7 @@
 
         // verify: service and callback calls
         mInOrder.verify(mWifiMock).start();
-        mInOrder.verify(mManagerStatusListenerMock, times(3)).onStatusChanged();
+        mInOrder.verify(mManagerStatusListenerMock, times(2)).onStatusChanged();
 
         verifyNoMoreInteractions(mManagerStatusListenerMock);
     }
@@ -1058,17 +1077,14 @@
      */
     @Test
     public void testIsSupportedTrue() throws Exception {
-        when(mServiceManagerMock.getTransport(
-                eq(IWifi.kInterfaceName), eq(HalDeviceManager.HAL_INSTANCE_NAME)))
-                .thenReturn(IServiceManager.Transport.HWBINDER);
         mInOrder = inOrder(mServiceManagerMock, mWifiMock);
         executeAndValidateInitializationSequence();
         assertTrue(mDut.isSupported());
     }
 
     /**
-     * Validate that isSupported() returns true when IServiceManager finds the vendor HAL daemon in
-     * the VINTF.
+     * Validate that isSupported() returns false when IServiceManager does not find the vendor HAL
+     * daemon in the VINTF.
      */
     @Test
     public void testIsSupportedFalse() throws Exception {
@@ -1076,7 +1092,7 @@
                 eq(IWifi.kInterfaceName), eq(HalDeviceManager.HAL_INSTANCE_NAME)))
                 .thenReturn(IServiceManager.Transport.EMPTY);
         mInOrder = inOrder(mServiceManagerMock, mWifiMock);
-        executeAndValidateInitializationSequence();
+        executeAndValidateInitializationSequence(false);
         assertFalse(mDut.isSupported());
     }
 
@@ -1088,6 +1104,10 @@
     }
 
     private void executeAndValidateInitializationSequence() throws Exception {
+        executeAndValidateInitializationSequence(true);
+    }
+
+    private void executeAndValidateInitializationSequence(boolean isSupported) throws Exception {
         // act:
         mDut.initialize();
 
@@ -1097,13 +1117,20 @@
         mInOrder.verify(mServiceManagerMock).registerForNotifications(eq(IWifi.kInterfaceName),
                 eq(""), mServiceNotificationCaptor.capture());
 
-        // act: get the service started (which happens even when service was already up)
-        mServiceNotificationCaptor.getValue().onRegistration(IWifi.kInterfaceName, "", true);
+        // The service should already be up at this point.
+        mInOrder.verify(mServiceManagerMock).getTransport(eq(IWifi.kInterfaceName),
+                eq(HalDeviceManager.HAL_INSTANCE_NAME));
 
-        // verify: wifi initialization sequence
-        mInOrder.verify(mWifiMock).linkToDeath(mDeathRecipientCaptor.capture(), anyLong());
-        mInOrder.verify(mWifiMock).registerEventCallback(mWifiEventCallbackCaptor.capture());
-        collector.checkThat("isReady is true", mDut.isReady(), equalTo(true));
+        // verify: wifi initialization sequence if vendor HAL is supported.
+        if (isSupported) {
+            mInOrder.verify(mWifiMock).linkToDeath(mDeathRecipientCaptor.capture(), anyLong());
+            mInOrder.verify(mWifiMock).registerEventCallback(mWifiEventCallbackCaptor.capture());
+            // verify: onStop called as a part of initialize.
+            mInOrder.verify(mWifiMock).stop();
+            collector.checkThat("isReady is true", mDut.isReady(), equalTo(true));
+        } else {
+            collector.checkThat("isReady is false", mDut.isReady(), equalTo(false));
+        }
     }
 
     private void executeAndValidateStartupSequence()throws Exception {
diff --git a/tests/wifitests/src/com/android/server/wifi/ScanResultMatchInfoTest.java b/tests/wifitests/src/com/android/server/wifi/ScanResultMatchInfoTest.java
new file mode 100644
index 0000000..e2905a6
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/ScanResultMatchInfoTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.*;
+
+import android.net.wifi.WifiConfiguration;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.ScanResultMatchInfoTest}.
+ */
+@SmallTest
+public class ScanResultMatchInfoTest {
+    /**
+     * Tests that equivalent ScanResultMatchInfo objects are created for WifiConfigurations and
+     * their associated ScanResult
+     */
+    @Test
+    public void testScanResultMatchesWifiConfiguration() {
+        WifiConfiguration conf =
+                WifiConfigurationTestUtil.createPskNetwork("\"PrettyFlyForAWifi\"");
+        ScanDetail scan = createScanDetailForNetwork(conf, "AA:AA:AA:AA:AA:AA");
+        assertEquals(ScanResultMatchInfo.fromWifiConfiguration(conf),
+                ScanResultMatchInfo.fromScanResult(scan.getScanResult()));
+
+        conf = WifiConfigurationTestUtil.createOpenNetwork("\"WIFIght the inevitable\"");
+        scan = createScanDetailForNetwork(conf, "BB:BB:BB:BB:BB:BB");
+        assertEquals(ScanResultMatchInfo.fromWifiConfiguration(conf),
+                ScanResultMatchInfo.fromScanResult(scan.getScanResult()));
+    }
+
+    /**
+     * Tests that multiple ScanResults with different BSSIDs will produce equivalent
+     * ScanResultMatchInfo objects to their associated WifiConfiguration
+     */
+    @Test
+    public void testDifferentBssidScanResultsMatch() {
+        WifiConfiguration conf =
+                WifiConfigurationTestUtil.createPskNetwork("\"PrettyFlyForAWifi-5G\"");
+        ScanDetail scan1 = createScanDetailForNetwork(conf, "AA:AA:AA:AA:AA:AA");
+        ScanDetail scan2 = createScanDetailForNetwork(conf, "BB:BB:BB:BB:BB:BB");
+        assertFalse(scan1.getScanResult().BSSID.equals(scan2.getScanResult().BSSID));
+        assertEquals(ScanResultMatchInfo.fromScanResult(scan1.getScanResult()),
+                ScanResultMatchInfo.fromScanResult(scan2.getScanResult()));
+    }
+
+    /**
+     * Tests that ScanResultMatchInfo objects created for different SSIDs or security types are not
+     * equivalent
+     */
+    @Test
+    public void testDifferentNetworkScanResultsDontMatch() {
+        WifiConfiguration psk =
+                WifiConfigurationTestUtil.createPskNetwork("\"Series Of Tubes\"");
+        WifiConfiguration open1 =
+                WifiConfigurationTestUtil.createOpenNetwork("\"Series Of Tubes\"");
+        WifiConfiguration open2 =
+                WifiConfigurationTestUtil.createOpenNetwork("\"Mom, Click Here For Internet\"");
+        ScanDetail scanOpen1 = createScanDetailForNetwork(open1, "AA:AA:AA:AA:AA:AA");
+        ScanDetail scanOpen2 = createScanDetailForNetwork(open2, "BB:BB:BB:BB:BB:BB");
+        ScanDetail scanPsk =   createScanDetailForNetwork(psk,   "CC:CC:CC:CC:CC:CC");
+        assertTrue(ScanResultMatchInfo.fromScanResult(scanOpen1.getScanResult())
+                != ScanResultMatchInfo.fromScanResult(scanOpen2.getScanResult()));
+        assertTrue(ScanResultMatchInfo.fromScanResult(scanOpen1.getScanResult())
+                != ScanResultMatchInfo.fromScanResult(scanPsk.getScanResult()));
+    }
+
+    /**
+     * Creates a scan detail corresponding to the provided network and given BSSID
+     */
+    private ScanDetail createScanDetailForNetwork(
+            WifiConfiguration configuration, String bssid) {
+        return WifiConfigurationTestUtil.createScanDetailForNetwork(configuration, bssid, -40,
+                2402, 0, 0);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/ScoredNetworkEvaluatorTest.java b/tests/wifitests/src/com/android/server/wifi/ScoredNetworkEvaluatorTest.java
index 55b00c9..c9c2625 100644
--- a/tests/wifitests/src/com/android/server/wifi/ScoredNetworkEvaluatorTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/ScoredNetworkEvaluatorTest.java
@@ -231,7 +231,7 @@
                 scanDetails, scores, meteredHints);
 
         // No saved networks.
-        when(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(any(ScanDetail.class)))
+        when(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(any(ScanDetail.class)))
                 .thenReturn(null);
 
         ScanResult scanResult = scanDetails.get(1).getScanResult();
@@ -270,7 +270,7 @@
                 scanDetails, scores, meteredHints);
 
         // No saved networks.
-        when(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(any(ScanDetail.class)))
+        when(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(any(ScanDetail.class)))
                 .thenReturn(null);
 
         for (int i = 0; i < 2; i++) {
@@ -308,7 +308,7 @@
                 scanDetails, scores, meteredHints);
 
         // No saved networks.
-        when(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(any(ScanDetail.class)))
+        when(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(any(ScanDetail.class)))
                 .thenReturn(null);
 
         WifiNetworkSelectorTestUtil.setupEphemeralNetwork(
@@ -524,7 +524,7 @@
                 mScoreCache, scanDetails, null, meteredHints);
 
         // No saved networks.
-        when(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(any(ScanDetail.class)))
+        when(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(any(ScanDetail.class)))
                 .thenReturn(null);
 
         for (int i = 0; i < 2; i++) {
diff --git a/tests/wifitests/src/com/android/server/wifi/SelfRecoveryTest.java b/tests/wifitests/src/com/android/server/wifi/SelfRecoveryTest.java
index 0e55b72..c8b93e9 100644
--- a/tests/wifitests/src/com/android/server/wifi/SelfRecoveryTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/SelfRecoveryTest.java
@@ -32,11 +32,12 @@
 public class SelfRecoveryTest {
     SelfRecovery mSelfRecovery;
     @Mock WifiController mWifiController;
+    @Mock Clock mClock;
 
     @Before
     public void setUp() throws Exception {
         initMocks(this);
-        mSelfRecovery = new SelfRecovery(mWifiController);
+        mSelfRecovery = new SelfRecovery(mWifiController, mClock);
     }
 
     /**
@@ -49,10 +50,14 @@
         verify(mWifiController).sendMessage(eq(WifiController.CMD_RESTART_WIFI));
         reset(mWifiController);
 
+        when(mClock.getElapsedSinceBootMillis())
+                .thenReturn(SelfRecovery.MAX_RESTARTS_TIME_WINDOW_MILLIS + 1);
         mSelfRecovery.trigger(SelfRecovery.REASON_HAL_CRASH);
         verify(mWifiController).sendMessage(eq(WifiController.CMD_RESTART_WIFI));
         reset(mWifiController);
 
+        when(mClock.getElapsedSinceBootMillis())
+                .thenReturn(2 * (SelfRecovery.MAX_RESTARTS_TIME_WINDOW_MILLIS + 1));
         mSelfRecovery.trigger(SelfRecovery.REASON_WIFICOND_CRASH);
         verify(mWifiController).sendMessage(eq(WifiController.CMD_RESTART_WIFI));
         reset(mWifiController);
@@ -71,4 +76,73 @@
         mSelfRecovery.trigger(8);
         verify(mWifiController, never()).sendMessage(anyInt());
     }
+
+    /**
+     * Verifies that invocations of {@link SelfRecovery#trigger(int)} for REASON_HAL_CRASH &
+     * REASON_WIFICOND_CRASH are limited to {@link SelfRecovery#MAX_RESTARTS_IN_TIME_WINDOW} in a
+     * {@link SelfRecovery#MAX_RESTARTS_TIME_WINDOW_MILLIS} millisecond time window.
+     */
+    @Test
+    public void testTimeWindowLimiting_typicalUse() {
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(0L);
+        // Fill up the SelfRecovery's restart time window buffer, ensure all the restart triggers
+        // aren't ignored
+        for (int i = 0; i < SelfRecovery.MAX_RESTARTS_IN_TIME_WINDOW / 2; i++) {
+            mSelfRecovery.trigger(SelfRecovery.REASON_HAL_CRASH);
+            verify(mWifiController).sendMessage(eq(WifiController.CMD_RESTART_WIFI));
+            reset(mWifiController);
+
+            mSelfRecovery.trigger(SelfRecovery.REASON_WIFICOND_CRASH);
+            verify(mWifiController).sendMessage(eq(WifiController.CMD_RESTART_WIFI));
+            reset(mWifiController);
+        }
+        if ((SelfRecovery.MAX_RESTARTS_IN_TIME_WINDOW % 2) == 1) {
+            mSelfRecovery.trigger(SelfRecovery.REASON_WIFICOND_CRASH);
+            verify(mWifiController).sendMessage(eq(WifiController.CMD_RESTART_WIFI));
+            reset(mWifiController);
+        }
+
+        // Verify that further attempts to trigger restarts for are ignored
+        mSelfRecovery.trigger(SelfRecovery.REASON_HAL_CRASH);
+        verify(mWifiController, never()).sendMessage(eq(WifiController.CMD_RESTART_WIFI));
+        reset(mWifiController);
+
+        mSelfRecovery.trigger(SelfRecovery.REASON_WIFICOND_CRASH);
+        verify(mWifiController, never()).sendMessage(eq(WifiController.CMD_RESTART_WIFI));
+        reset(mWifiController);
+
+        // Verify L.R.Watchdog can still restart things (It has its own complex limiter)
+        mSelfRecovery.trigger(SelfRecovery.REASON_LAST_RESORT_WATCHDOG);
+        verify(mWifiController).sendMessage(eq(WifiController.CMD_RESTART_WIFI));
+        reset(mWifiController);
+
+        // now TRAVEL FORWARDS IN TIME and ensure that more restarts can occur
+        when(mClock.getElapsedSinceBootMillis())
+                .thenReturn(SelfRecovery.MAX_RESTARTS_TIME_WINDOW_MILLIS + 1);
+        mSelfRecovery.trigger(SelfRecovery.REASON_LAST_RESORT_WATCHDOG);
+        verify(mWifiController).sendMessage(eq(WifiController.CMD_RESTART_WIFI));
+        reset(mWifiController);
+
+        when(mClock.getElapsedSinceBootMillis())
+                .thenReturn(SelfRecovery.MAX_RESTARTS_TIME_WINDOW_MILLIS + 1);
+        mSelfRecovery.trigger(SelfRecovery.REASON_HAL_CRASH);
+        verify(mWifiController).sendMessage(eq(WifiController.CMD_RESTART_WIFI));
+        reset(mWifiController);
+    }
+
+    /**
+     * Verifies that invocations of {@link SelfRecovery#trigger(int)} for
+     * REASON_LAST_RESORT_WATCHDOG are NOT limited to
+     * {@link SelfRecovery#MAX_RESTARTS_IN_TIME_WINDOW} in a
+     * {@link SelfRecovery#MAX_RESTARTS_TIME_WINDOW_MILLIS} millisecond time window.
+     */
+    @Test
+    public void testTimeWindowLimiting_lastResortWatchdog_noEffect() {
+        for (int i = 0; i < SelfRecovery.MAX_RESTARTS_IN_TIME_WINDOW * 2; i++) {
+            // Verify L.R.Watchdog can still restart things (It has it's own complex limiter)
+            mSelfRecovery.trigger(SelfRecovery.REASON_LAST_RESORT_WATCHDOG);
+            verify(mWifiController).sendMessage(eq(WifiController.CMD_RESTART_WIFI));
+            reset(mWifiController);
+        }
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java b/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java
index 900e6a6..892b597 100644
--- a/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java
@@ -137,6 +137,20 @@
         startSoftApAndVerifyEnabled(config);
     }
 
+
+    /**
+     * Verifies startSoftAp will start with the hiddenSSID param set when it is set to true in the
+     * supplied config.
+     */
+    @Test
+    public void startSoftApWithHiddenSsidTrueInConfig() throws Exception {
+        WifiConfiguration config = new WifiConfiguration();
+        config.apBand = WifiConfiguration.AP_BAND_2GHZ;
+        config.SSID = TEST_SSID;
+        config.hiddenSSID = true;
+        startSoftApAndVerifyEnabled(config);
+    }
+
     /** Tests softap startup if default config fails to load. **/
     @Test
     public void startSoftApDefaultConfigFailedToLoad() throws Exception {
@@ -200,6 +214,7 @@
     /** Starts soft AP and verifies that it is enabled successfully. */
     protected void startSoftApAndVerifyEnabled(WifiConfiguration config) throws Exception {
         String expectedSSID;
+        boolean expectedHiddenSsid;
         InOrder order = inOrder(mListener, mApInterfaceBinder, mApInterface, mNmService);
 
         when(mWifiNative.isHalStarted()).thenReturn(false);
@@ -210,16 +225,19 @@
         if (config == null) {
             when(mWifiApConfigStore.getApConfiguration()).thenReturn(mDefaultApConfig);
             expectedSSID = mDefaultApConfig.SSID;
+            expectedHiddenSsid = mDefaultApConfig.hiddenSSID;
         } else {
             expectedSSID = config.SSID;
+            expectedHiddenSsid = config.hiddenSSID;
         }
+
         mSoftApManager.start();
         mLooper.dispatchAll();
         order.verify(mListener).onStateChanged(WifiManager.WIFI_AP_STATE_ENABLING, 0);
         order.verify(mApInterfaceBinder).linkToDeath(mDeathListenerCaptor.capture(), eq(0));
         order.verify(mNmService).registerObserver(mNetworkObserverCaptor.capture());
         order.verify(mApInterface).writeHostapdConfig(
-                eq(expectedSSID.getBytes(StandardCharsets.UTF_8)), anyBoolean(),
+                eq(expectedSSID.getBytes(StandardCharsets.UTF_8)), eq(expectedHiddenSsid),
                 anyInt(), anyInt(), any());
         order.verify(mApInterface).startHostapd();
         mNetworkObserverCaptor.getValue().interfaceLinkStateChanged(TEST_INTERFACE_NAME, true);
diff --git a/tests/wifitests/src/com/android/server/wifi/SupplicantStaIfaceHalTest.java b/tests/wifitests/src/com/android/server/wifi/SupplicantStaIfaceHalTest.java
index 6c7b252..2deef52 100644
--- a/tests/wifitests/src/com/android/server/wifi/SupplicantStaIfaceHalTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/SupplicantStaIfaceHalTest.java
@@ -54,7 +54,6 @@
 import android.net.wifi.WifiSsid;
 import android.os.IHwBinder;
 import android.os.RemoteException;
-import android.text.TextUtils;
 import android.util.SparseArray;
 
 import com.android.server.wifi.hotspot2.AnqpEvent;
@@ -489,28 +488,6 @@
                 .addNetwork(any(ISupplicantStaIface.addNetworkCallback.class));
     }
 
-    @Test
-    public void connectToNetworkWithSameNetworkButDifferentBssidUpdatesNetworkFromSupplicant()
-            throws Exception {
-        executeAndValidateInitializationSequence();
-        WifiConfiguration config = executeAndValidateConnectSequence(SUPPLICANT_NETWORK_ID, false);
-        String testBssid = "11:22:33:44:55:66";
-        when(mSupplicantStaNetworkMock.setBssid(eq(testBssid))).thenReturn(true);
-
-        // Reset mocks for mISupplicantStaIfaceMock because we finished the first connection.
-        reset(mISupplicantStaIfaceMock);
-        setupMocksForConnectSequence(true /*haveExistingNetwork*/);
-        // Change the BSSID and connect to the same network.
-        assertFalse(TextUtils.equals(
-                testBssid, config.getNetworkSelectionStatus().getNetworkSelectionBSSID()));
-        config.getNetworkSelectionStatus().setNetworkSelectionBSSID(testBssid);
-        assertTrue(mDut.connectToNetwork(config));
-        verify(mSupplicantStaNetworkMock).setBssid(eq(testBssid));
-        verify(mISupplicantStaIfaceMock, never()).removeNetwork(anyInt());
-        verify(mISupplicantStaIfaceMock, never())
-                .addNetwork(any(ISupplicantStaIface.addNetworkCallback.class));
-    }
-
     /**
      * Tests connection to a specified network failure due to network add.
      */
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiApConfigStoreTest.java b/tests/wifitests/src/com/android/server/wifi/WifiApConfigStoreTest.java
index 2d3b066..02064d8 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiApConfigStoreTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiApConfigStoreTest.java
@@ -17,6 +17,7 @@
 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.verify;
 import static org.mockito.Mockito.when;
@@ -36,6 +37,7 @@
 
 import java.io.File;
 import java.lang.reflect.Method;
+import java.util.Random;
 
 /**
  * Unit tests for {@link com.android.server.wifi.WifiApConfigStore}.
@@ -50,12 +52,15 @@
     private static final String TEST_DEFAULT_AP_SSID = "TestAP";
     private static final String TEST_CONFIGURED_AP_SSID = "ConfiguredAP";
     private static final String TEST_DEFAULT_HOTSPOT_SSID = "TestShare";
+    private static final String TEST_DEFAULT_HOTSPOT_PSK = "TestPassword";
     private static final int RAND_SSID_INT_MIN = 1000;
     private static final int RAND_SSID_INT_MAX = 9999;
+    private static final String TEST_CHAR_SET_AS_STRING = "abcdefghijklmnopqrstuvwxyz0123456789";
 
     @Mock Context mContext;
     @Mock BackupManagerProxy mBackupManagerProxy;
     File mApConfigFile;
+    Random mRandom;
 
     @Before
     public void setUp() throws Exception {
@@ -73,6 +78,8 @@
         resources.setString(R.string.wifi_localhotspot_configure_ssid_default,
                             TEST_DEFAULT_HOTSPOT_SSID);
         when(mContext.getResources()).thenReturn(resources);
+
+        mRandom = new Random();
     }
 
     @After
@@ -195,6 +202,17 @@
     }
 
     /**
+     * Verify a proper WifiConfiguration is generate by getDefaultApConfiguration().
+     */
+    @Test
+    public void getDefaultApConfigurationIsValid() {
+        WifiApConfigStore store = new WifiApConfigStore(
+                mContext, mBackupManagerProxy, mApConfigFile.getPath());
+        WifiConfiguration config = store.getApConfiguration();
+        assertTrue(WifiApConfigStore.validateApWifiConfiguration(config));
+    }
+
+    /**
      * Verify a proper local only hotspot config is generated when called properly with the valid
      * context.
      */
@@ -204,5 +222,147 @@
         verifyDefaultApConfig(config, TEST_DEFAULT_HOTSPOT_SSID);
         // The LOHS config should also have a specific network id set - check that as well.
         assertEquals(WifiConfiguration.LOCAL_ONLY_NETWORK_ID, config.networkId);
+
+        // verify that the config passes the validateApWifiConfiguration check
+        assertTrue(WifiApConfigStore.validateApWifiConfiguration(config));
+    }
+
+    /**
+     * Helper method to generate random SSIDs.
+     *
+     * Note: this method has limited use as a random SSID generator.  The characters used in this
+     * method do no not cover all valid inputs.
+     * @param length number of characters to generate for the name
+     * @return String generated string of random characters
+     */
+    private String generateRandomString(int length) {
+
+        StringBuilder stringBuilder = new StringBuilder(length);
+        int index = -1;
+        while (stringBuilder.length() < length) {
+            index = mRandom.nextInt(TEST_CHAR_SET_AS_STRING.length());
+            stringBuilder.append(TEST_CHAR_SET_AS_STRING.charAt(index));
+        }
+        return stringBuilder.toString();
+    }
+
+    /**
+     * Verify the SSID checks in validateApWifiConfiguration.
+     *
+     * Cases to check and verify they trigger failed verification:
+     * null WifiConfiguration.SSID
+     * empty WifiConfiguration.SSID
+     * invalid WifiConfiguaration.SSID length
+     *
+     * Additionally check a valid SSID with a random (within valid ranges) length.
+     */
+    @Test
+    public void testSsidVerificationInValidateApWifiConfigurationCheck() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.SSID = null;
+        assertFalse(WifiApConfigStore.validateApWifiConfiguration(config));
+        config.SSID = "";
+        assertFalse(WifiApConfigStore.validateApWifiConfiguration(config));
+        // check a string that is too large
+        config.SSID = generateRandomString(WifiApConfigStore.SSID_MAX_LEN + 1);
+        assertFalse(WifiApConfigStore.validateApWifiConfiguration(config));
+
+        // now check a valid SSID with a random length
+        config.SSID = generateRandomString(mRandom.nextInt(WifiApConfigStore.SSID_MAX_LEN + 1));
+        assertTrue(WifiApConfigStore.validateApWifiConfiguration(config));
+    }
+
+    /**
+     * Verify the Open network checks in validateApWifiConfiguration.
+     *
+     * If the configured network is open, it should not have a password set.
+     *
+     * Additionally verify a valid open network passes verification.
+     */
+    @Test
+    public void testOpenNetworkConfigInValidateApWifiConfigurationCheck() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.SSID = TEST_DEFAULT_HOTSPOT_SSID;
+
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+        config.preSharedKey = TEST_DEFAULT_HOTSPOT_PSK;
+        assertFalse(WifiApConfigStore.validateApWifiConfiguration(config));
+
+        // open networks should not have a password set
+        config.preSharedKey = null;
+        assertTrue(WifiApConfigStore.validateApWifiConfiguration(config));
+        config.preSharedKey = "";
+        assertTrue(WifiApConfigStore.validateApWifiConfiguration(config));
+    }
+
+    /**
+     * Verify the WPA2_PSK network checks in validateApWifiConfiguration.
+     *
+     * If the configured network is configured with a preSharedKey, verify that the passwork is set
+     * and it meets length requirements.
+     */
+    @Test
+    public void testWpa2PskNetworkConfigInValidateApWifiConfigurationCheck() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.SSID = TEST_DEFAULT_HOTSPOT_SSID;
+
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA2_PSK);
+        config.preSharedKey = null;
+        assertFalse(WifiApConfigStore.validateApWifiConfiguration(config));
+        config.preSharedKey = "";
+        assertFalse(WifiApConfigStore.validateApWifiConfiguration(config));
+
+        // test too short
+        config.preSharedKey =
+                generateRandomString(WifiApConfigStore.PSK_MIN_LEN - 1);
+        assertFalse(WifiApConfigStore.validateApWifiConfiguration(config));
+
+        // test too long
+        config.preSharedKey =
+                generateRandomString(WifiApConfigStore.PSK_MAX_LEN + 1);
+        assertFalse(WifiApConfigStore.validateApWifiConfiguration(config));
+
+        // explicitly test min length
+        config.preSharedKey =
+            generateRandomString(WifiApConfigStore.PSK_MIN_LEN);
+        assertTrue(WifiApConfigStore.validateApWifiConfiguration(config));
+
+        // explicitly test max length
+        config.preSharedKey =
+                generateRandomString(WifiApConfigStore.PSK_MAX_LEN);
+        assertTrue(WifiApConfigStore.validateApWifiConfiguration(config));
+
+        // test random (valid length)
+        int maxLen = WifiApConfigStore.PSK_MAX_LEN;
+        int minLen = WifiApConfigStore.PSK_MIN_LEN;
+        config.preSharedKey =
+                generateRandomString(mRandom.nextInt(maxLen - minLen) + minLen);
+        assertTrue(WifiApConfigStore.validateApWifiConfiguration(config));
+    }
+
+    /**
+     * Verify an invalid AuthType setting (that would trigger an IllegalStateException)
+     * returns false when triggered in the validateApWifiConfiguration.
+     */
+    @Test
+    public void testInvalidAuthTypeInValidateApWifiConfigurationCheck() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.SSID = TEST_DEFAULT_HOTSPOT_SSID;
+
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA2_PSK);
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+        assertFalse(WifiApConfigStore.validateApWifiConfiguration(config));
+    }
+
+    /**
+     * Verify an unsupported authType returns false for validateApWifiConfigurationCheck.
+     */
+    @Test
+    public void testUnsupportedAuthTypeInValidateApWifiConfigurationCheck() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.SSID = TEST_DEFAULT_HOTSPOT_SSID;
+
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+        assertFalse(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 9fa67a0..7509c18 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java
@@ -34,7 +34,6 @@
 import android.net.wifi.WifiEnterpriseConfig;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiScanner;
-import android.net.wifi.WifiSsid;
 import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -177,7 +176,7 @@
 
         when(mDevicePolicyManagerInternal.isActiveAdminWithPolicy(anyInt(), anyInt()))
                 .thenReturn(false);
-        when(mWifiPermissionsUtil.checkConfigOverridePermission(anyInt())).thenReturn(true);
+        when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(true);
         when(mWifiPermissionsWrapper.getDevicePolicyManagerInternal())
                 .thenReturn(mDevicePolicyManagerInternal);
         createWifiConfigManager();
@@ -326,7 +325,7 @@
         // Now change BSSID of the network.
         assertAndSetNetworkBSSID(openNetwork, TEST_BSSID);
 
-        when(mWifiPermissionsUtil.checkConfigOverridePermission(anyInt())).thenReturn(false);
+        when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(false);
 
         // Update the same configuration and ensure that the operation failed.
         NetworkUpdateResult result = updateNetworkToWifiConfigManager(openNetwork);
@@ -782,7 +781,7 @@
         assertTrue(retrievedStatus.isNetworkEnabled());
         verifyUpdateNetworkStatus(retrievedNetwork, WifiConfiguration.Status.ENABLED);
 
-        when(mWifiPermissionsUtil.checkConfigOverridePermission(anyInt())).thenReturn(false);
+        when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(false);
 
         // Now try to set it disabled with |TEST_UPDATE_UID|, it should fail and the network
         // should remain enabled.
@@ -811,7 +810,7 @@
                 mWifiConfigManager.getConfiguredNetwork(result.getNetworkId());
         assertEquals(TEST_CREATOR_UID, retrievedNetwork.lastConnectUid);
 
-        when(mWifiPermissionsUtil.checkConfigOverridePermission(anyInt())).thenReturn(false);
+        when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(false);
 
         // Now try to update the last connect UID with |TEST_UPDATE_UID|, it should fail and
         // the lastConnectUid should remain the same.
@@ -927,6 +926,7 @@
         wepKeys[0] = "";
         wepTxKeyIdx = -1;
         assertAndSetNetworkWepKeysAndTxIndex(network, wepKeys, wepTxKeyIdx);
+        network.allowedKeyManagement.clear();
         network.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
         assertAndSetNetworkPreSharedKey(network, WifiConfigurationTestUtil.TEST_PSK);
 
@@ -982,7 +982,6 @@
         network.allowedKeyManagement.clear();
         network.allowedPairwiseCiphers.clear();
         network.allowedGroupCiphers.clear();
-        network.setIpConfiguration(null);
         network.enterpriseConfig = null;
 
         // Update the network.
@@ -1008,6 +1007,20 @@
     }
 
     /**
+     * Verifies the addition of a single network using
+     * {@link WifiConfigManager#addOrUpdateNetwork(WifiConfiguration, int)} by passing in null
+     * in IpConfiguraion fails.
+     */
+    @Test
+    public void testAddSingleNetworkWithNullIpConfigurationFails() {
+        WifiConfiguration network = WifiConfigurationTestUtil.createEapNetwork();
+        network.setIpConfiguration(null);
+        NetworkUpdateResult result =
+                mWifiConfigManager.addOrUpdateNetwork(network, TEST_CREATOR_UID);
+        assertFalse(result.isSuccess());
+    }
+
+    /**
      * Verifies that the modification of a single network using
      * {@link WifiConfigManager#addOrUpdateNetwork(WifiConfiguration, int)} does not modify
      * existing configuration if there is a failure.
@@ -1043,7 +1056,7 @@
     /**
      * Verifies the matching of networks with different encryption types with the
      * corresponding scan detail using
-     * {@link WifiConfigManager#getSavedNetworkForScanDetailAndCache(ScanDetail)}.
+     * {@link WifiConfigManager#getConfiguredNetworkForScanDetailAndCache(ScanDetail)}.
      * The test also verifies that the provided scan detail was cached,
      */
     @Test
@@ -1062,7 +1075,7 @@
 
     /**
      * Verifies that scan details with wrong SSID/authentication types are not matched using
-     * {@link WifiConfigManager#getSavedNetworkForScanDetailAndCache(ScanDetail)}
+     * {@link WifiConfigManager#getConfiguredNetworkForScanDetailAndCache(ScanDetail)}
      * to the added networks.
      */
     @Test
@@ -1096,10 +1109,14 @@
                 openNetworkScanDetail.getScanResult().capabilities;
 
         // Try to lookup a saved network using the modified scan details. All of these should fail.
-        assertNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(openNetworkScanDetail));
-        assertNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(wepNetworkScanDetail));
-        assertNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(pskNetworkScanDetail));
-        assertNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(eapNetworkScanDetail));
+        assertNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+                openNetworkScanDetail));
+        assertNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+                wepNetworkScanDetail));
+        assertNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+                pskNetworkScanDetail));
+        assertNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+                eapNetworkScanDetail));
 
         // All the cache's should be empty as well.
         assertNull(mWifiConfigManager.getScanDetailCacheForNetwork(openNetwork.networkId));
@@ -1154,7 +1171,7 @@
                     createScanDetailForNetwork(
                             openNetwork, String.format("%s%02x", testBssidPrefix, scanDetailNum));
             assertNotNull(
-                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(scanDetail));
+                    mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(scanDetail));
 
             // The size of scan detail cache should keep growing until it hits
             // |SCAN_CACHE_ENTRIES_MAX_SIZE|.
@@ -1167,7 +1184,7 @@
         ScanDetail scanDetail =
                 createScanDetailForNetwork(
                         openNetwork, String.format("%s%02x", testBssidPrefix, scanDetailNum));
-        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(scanDetail));
+        assertNotNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(scanDetail));
 
         // Retrieve the scan detail cache and ensure that the size was trimmed down to
         // |SCAN_CACHE_ENTRIES_TRIM_SIZE + 1|. The "+1" is to account for the new entry that
@@ -1216,8 +1233,9 @@
         verifyUpdateNetworkAfterConnectHasEverConnectedTrue(pskNetwork.networkId);
 
         // Now update the same network with a different psk.
-        assertFalse(pskNetwork.preSharedKey.equals("newpassword"));
-        pskNetwork.preSharedKey = "newpassword";
+        String newPsk = "\"newpassword\"";
+        assertFalse(pskNetwork.preSharedKey.equals(newPsk));
+        pskNetwork.preSharedKey = newPsk;
         verifyUpdateNetworkWithCredentialChangeHasEverConnectedFalse(pskNetwork);
     }
 
@@ -1262,6 +1280,7 @@
         verifyUpdateNetworkAfterConnectHasEverConnectedTrue(pskNetwork.networkId);
 
         assertFalse(pskNetwork.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X));
+        pskNetwork.allowedKeyManagement.clear();
         pskNetwork.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
         verifyUpdateNetworkWithCredentialChangeHasEverConnectedFalse(pskNetwork);
     }
@@ -1473,6 +1492,35 @@
     }
 
     /**
+     * Verifies that the list of PNO networks does not contain ephemeral or passpoint networks
+     * {@link WifiConfigManager#retrievePnoNetworkList()}.
+     */
+    @Test
+    public void testRetrievePnoListDoesNotContainEphemeralOrPasspointNetworks() throws Exception {
+        WifiConfiguration savedOpenNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        WifiConfiguration ephemeralNetwork = WifiConfigurationTestUtil.createEphemeralNetwork();
+        WifiConfiguration passpointNetwork = WifiConfigurationTestUtil.createPasspointNetwork();
+
+        verifyAddNetworkToWifiConfigManager(savedOpenNetwork);
+        verifyAddEphemeralNetworkToWifiConfigManager(ephemeralNetwork);
+        verifyAddPasspointNetworkToWifiConfigManager(passpointNetwork);
+
+        // Enable all of them.
+        assertTrue(mWifiConfigManager.enableNetwork(
+                savedOpenNetwork.networkId, false, TEST_CREATOR_UID));
+        assertTrue(mWifiConfigManager.enableNetwork(
+                ephemeralNetwork.networkId, false, TEST_CREATOR_UID));
+        assertTrue(mWifiConfigManager.enableNetwork(
+                passpointNetwork.networkId, false, TEST_CREATOR_UID));
+
+        // Retrieve the Pno network list & verify the order of the networks returned.
+        List<WifiScanner.PnoSettings.PnoNetwork> pnoNetworks =
+                mWifiConfigManager.retrievePnoNetworkList();
+        assertEquals(1, pnoNetworks.size());
+        assertEquals(savedOpenNetwork.SSID, pnoNetworks.get(0).ssid);
+    }
+
+    /**
      * Verifies the linking of networks when they have the same default GW Mac address in
      * {@link WifiConfigManager#getOrCreateScanDetailCacheForNetwork(WifiConfiguration)}.
      */
@@ -1500,9 +1548,12 @@
 
         // Now save all these scan details corresponding to each of this network and expect
         // all of these networks to be linked with each other.
-        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail1));
-        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail2));
-        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail3));
+        assertNotNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+                networkScanDetail1));
+        assertNotNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+                networkScanDetail2));
+        assertNotNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+                networkScanDetail3));
 
         List<WifiConfiguration> retrievedNetworks =
                 mWifiConfigManager.getConfiguredNetworks();
@@ -1538,9 +1589,12 @@
 
         // Now save all these scan details corresponding to each of this network and expect
         // all of these networks to be linked with each other.
-        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail1));
-        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail2));
-        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail3));
+        assertNotNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+                networkScanDetail1));
+        assertNotNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+                networkScanDetail2));
+        assertNotNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+                networkScanDetail3));
 
         List<WifiConfiguration> retrievedNetworks =
                 mWifiConfigManager.getConfiguredNetworks();
@@ -1571,8 +1625,10 @@
         ScanDetail networkScanDetail1 = createScanDetailForNetwork(network1, "af:89:56:34:56:67");
         ScanDetail networkScanDetail2 = createScanDetailForNetwork(network2, "af:89:56:34:56:68");
 
-        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail1));
-        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail2));
+        assertNotNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+                networkScanDetail1));
+        assertNotNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+                networkScanDetail2));
 
         List<WifiConfiguration> retrievedNetworks =
                 mWifiConfigManager.getConfiguredNetworks();
@@ -1603,7 +1659,8 @@
                     createScanDetailForNetwork(
                             network1, test_bssid_base + Integer.toString(scan_result_num));
             assertNotNull(
-                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail));
+                    mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+                            networkScanDetail));
         }
 
         // Now add 1 scan result to the other network with bssid which is different in only the
@@ -1611,7 +1668,8 @@
         ScanDetail networkScanDetail2 =
                 createScanDetailForNetwork(
                         network2, test_bssid_base + Integer.toString(scan_result_num++));
-        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail2));
+        assertNotNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+                networkScanDetail2));
 
         List<WifiConfiguration> retrievedNetworks =
                 mWifiConfigManager.getConfiguredNetworks();
@@ -1638,8 +1696,10 @@
 
         // Now save all these scan details corresponding to each of this network and expect
         // all of these networks to be linked with each other.
-        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail1));
-        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail2));
+        assertNotNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+                networkScanDetail1));
+        assertNotNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+                networkScanDetail2));
 
         List<WifiConfiguration> retrievedNetworks =
                 mWifiConfigManager.getConfiguredNetworks();
@@ -1660,9 +1720,9 @@
                 network2.networkId, "ad:de:fe:45:23:34"));
 
         // Add some dummy scan results again to re-evaluate the linking of networks.
-        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(
+        assertNotNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
                 createScanDetailForNetwork(network1, "af:89:56:34:45:67")));
-        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(
+        assertNotNull(mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
                 createScanDetailForNetwork(network1, "af:89:56:34:45:68")));
 
         retrievedNetworks = mWifiConfigManager.getConfiguredNetworks();
@@ -1687,7 +1747,8 @@
                     createScanDetailForNetwork(
                             network, test_bssid_base + Integer.toString(i), 0, TEST_FREQ_LIST[i]);
             assertNotNull(
-                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail));
+                    mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+                            networkScanDetail));
 
         }
         assertEquals(new HashSet<Integer>(Arrays.asList(TEST_FREQ_LIST)),
@@ -1713,7 +1774,8 @@
                     createScanDetailForNetwork(
                             network, test_bssid_base + Integer.toString(i), 0, TEST_FREQ_LIST[i]);
             assertNotNull(
-                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail));
+                    mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+                            networkScanDetail));
 
         }
 
@@ -1745,7 +1807,8 @@
                     createScanDetailForNetwork(
                             network, test_bssid_base + Integer.toString(i), 0, TEST_FREQ_LIST[i]);
             assertNotNull(
-                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail));
+                    mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+                            networkScanDetail));
 
         }
         int ageInMillis = 4;
@@ -1785,7 +1848,8 @@
                     createScanDetailForNetwork(
                             network, test_bssid_base + Integer.toString(i), 0, TEST_FREQ_LIST[i]);
             assertNotNull(
-                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail));
+                    mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+                            networkScanDetail));
 
         }
         // Ensure that the fetched list size is limited.
@@ -1815,7 +1879,8 @@
                             network1, test_bssid_base + Integer.toString(TEST_FREQ_LISTIdx), 0,
                             TEST_FREQ_LIST[TEST_FREQ_LISTIdx]);
             assertNotNull(
-                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail));
+                    mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+                            networkScanDetail));
 
         }
         // Create 3 scan results with different bssid's & frequencies for network 2.
@@ -1825,7 +1890,8 @@
                             network2, test_bssid_base + Integer.toString(TEST_FREQ_LISTIdx), 0,
                             TEST_FREQ_LIST[TEST_FREQ_LISTIdx]);
             assertNotNull(
-                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail));
+                    mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+                            networkScanDetail));
         }
 
         // Link the 2 configurations together using the GwMacAddress.
@@ -1873,7 +1939,8 @@
                             network1, test_bssid_base + Integer.toString(TEST_FREQ_LISTIdx), 0,
                             TEST_FREQ_LIST[TEST_FREQ_LISTIdx]);
             assertNotNull(
-                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail));
+                    mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+                            networkScanDetail));
 
         }
         // Create 3 scan results with different bssid's & frequencies for network 2.
@@ -1883,7 +1950,8 @@
                             network2, test_bssid_base + Integer.toString(TEST_FREQ_LISTIdx), 0,
                             TEST_FREQ_LIST[TEST_FREQ_LISTIdx]);
             assertNotNull(
-                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail));
+                    mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
+                            networkScanDetail));
         }
 
         // Link the 2 configurations together using the GwMacAddress.
@@ -2676,6 +2744,40 @@
     }
 
     /**
+     * Verifies that all the ephemeral and passpoint networks are removed when
+     * {@link WifiConfigManager#removeAllEphemeralOrPasspointConfiguredNetworks()} is invoked.
+     */
+    @Test
+    public void testRemoveAllEphemeralOrPasspointConfiguredNetworks() throws Exception {
+        WifiConfiguration savedOpenNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        WifiConfiguration ephemeralNetwork = WifiConfigurationTestUtil.createEphemeralNetwork();
+        WifiConfiguration passpointNetwork = WifiConfigurationTestUtil.createPasspointNetwork();
+
+        verifyAddNetworkToWifiConfigManager(savedOpenNetwork);
+        verifyAddEphemeralNetworkToWifiConfigManager(ephemeralNetwork);
+        verifyAddPasspointNetworkToWifiConfigManager(passpointNetwork);
+
+        List<WifiConfiguration> expectedConfigsBeforeRemove = new ArrayList<WifiConfiguration>() {{
+                add(savedOpenNetwork);
+                add(ephemeralNetwork);
+                add(passpointNetwork);
+            }};
+        WifiConfigurationTestUtil.assertConfigurationsEqualForConfigManagerAddOrUpdate(
+                expectedConfigsBeforeRemove, mWifiConfigManager.getConfiguredNetworks());
+
+        assertTrue(mWifiConfigManager.removeAllEphemeralOrPasspointConfiguredNetworks());
+
+        List<WifiConfiguration> expectedConfigsAfterRemove = new ArrayList<WifiConfiguration>() {{
+                add(savedOpenNetwork);
+            }};
+        WifiConfigurationTestUtil.assertConfigurationsEqualForConfigManagerAddOrUpdate(
+                expectedConfigsAfterRemove, mWifiConfigManager.getConfiguredNetworks());
+
+        // No more ephemeral or passpoint networks to remove now.
+        assertFalse(mWifiConfigManager.removeAllEphemeralOrPasspointConfiguredNetworks());
+    }
+
+    /**
      * Verifies that the modification of a single network using
      * {@link WifiConfigManager#addOrUpdateNetwork(WifiConfiguration, int)} and ensures that any
      * updates to the network config in
@@ -2799,7 +2901,7 @@
      */
     @Test
     public void testAddMultipleNetworksWithSameSSIDAndDefaultKeyMgmt() {
-        final String ssid = "test_blah";
+        final String ssid = "\"test_blah\"";
         // Add a network with the above SSID and default key mgmt and ensure it was added
         // successfully.
         WifiConfiguration network1 = new WifiConfiguration();
@@ -2835,12 +2937,13 @@
      */
     @Test
     public void testAddMultipleNetworksWithSameSSIDAndDifferentKeyMgmt() {
-        final String ssid = "test_blah";
+        final String ssid = "\"test_blah\"";
         // Add a network with the above SSID and WPA_PSK key mgmt and ensure it was added
         // successfully.
         WifiConfiguration network1 = new WifiConfiguration();
         network1.SSID = ssid;
         network1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+        network1.preSharedKey = "\"test_blah\"";
         NetworkUpdateResult result = addNetworkToWifiConfigManager(network1);
         assertTrue(result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID);
         assertTrue(result.isNewNetwork());
@@ -2874,14 +2977,14 @@
     @Test
     public void testAddNetworkWithProxyFails() {
         verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
-                false, // withConfOverride
+                false, // withNetworkSettings
                 false, // withProfileOwnerPolicy
                 false, // withDeviceOwnerPolicy
                 WifiConfigurationTestUtil.createDHCPIpConfigurationWithPacProxy(),
                 false, // assertSuccess
                 WifiConfiguration.INVALID_NETWORK_ID); // Update networkID
         verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
-                false, // withConfOverride
+                false, // withNetworkSettings
                 false, // withProfileOwnerPolicy
                 false, // withDeviceOwnerPolicy
                 WifiConfigurationTestUtil.createDHCPIpConfigurationWithStaticProxy(),
@@ -2896,14 +2999,14 @@
     @Test
     public void testAddNetworkWithProxyWithConfOverride() {
         verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
-                true,  // withConfOverride
+                true,  // withNetworkSettings
                 false, // withProfileOwnerPolicy
                 false, // withDeviceOwnerPolicy
                 WifiConfigurationTestUtil.createDHCPIpConfigurationWithPacProxy(),
                 true, // assertSuccess
                 WifiConfiguration.INVALID_NETWORK_ID); // Update networkID
         verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
-                true,  // withConfOverride
+                true,  // withNetworkSettings
                 false, // withProfileOwnerPolicy
                 false, // withDeviceOwnerPolicy
                 WifiConfigurationTestUtil.createDHCPIpConfigurationWithStaticProxy(),
@@ -2918,14 +3021,14 @@
     @Test
     public void testAddNetworkWithProxyAsProfileOwner() {
         verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
-                false,  // withConfOverride
+                false,  // withNetworkSettings
                 true, // withProfileOwnerPolicy
                 false, // withDeviceOwnerPolicy
                 WifiConfigurationTestUtil.createDHCPIpConfigurationWithPacProxy(),
                 true, // assertSuccess
                 WifiConfiguration.INVALID_NETWORK_ID); // Update networkID
         verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
-                false,  // withConfOverride
+                false,  // withNetworkSettings
                 true, // withProfileOwnerPolicy
                 false, // withDeviceOwnerPolicy
                 WifiConfigurationTestUtil.createDHCPIpConfigurationWithStaticProxy(),
@@ -2939,14 +3042,14 @@
     @Test
     public void testAddNetworkWithProxyAsDeviceOwner() {
         verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
-                false,  // withConfOverride
+                false,  // withNetworkSettings
                 false, // withProfileOwnerPolicy
                 true, // withDeviceOwnerPolicy
                 WifiConfigurationTestUtil.createDHCPIpConfigurationWithPacProxy(),
                 true, // assertSuccess
                 WifiConfiguration.INVALID_NETWORK_ID); // Update networkID
         verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
-                false,  // withConfOverride
+                false,  // withNetworkSettings
                 false, // withProfileOwnerPolicy
                 true, // withDeviceOwnerPolicy
                 WifiConfigurationTestUtil.createDHCPIpConfigurationWithStaticProxy(),
@@ -2962,14 +3065,14 @@
         WifiConfiguration network = WifiConfigurationTestUtil.createOpenHiddenNetwork();
         NetworkUpdateResult result = verifyAddNetworkToWifiConfigManager(network);
         verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
-                false, // withConfOverride
+                false, // withNetworkSettings
                 false, // withProfileOwnerPolicy
                 false, // withDeviceOwnerPolicy
                 WifiConfigurationTestUtil.createDHCPIpConfigurationWithPacProxy(),
                 false, // assertSuccess
                 result.getNetworkId()); // Update networkID
         verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
-                false, // withConfOverride
+                false, // withNetworkSettings
                 false, // withProfileOwnerPolicy
                 false, // withDeviceOwnerPolicy
                 WifiConfigurationTestUtil.createDHCPIpConfigurationWithStaticProxy(),
@@ -2989,7 +3092,7 @@
         NetworkUpdateResult result = addNetworkToWifiConfigManager(network, TEST_CREATOR_UID);
         assertTrue(result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID);
         verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
-                true, // withConfOverride
+                true, // withNetworkSettings
                 false, // withProfileOwnerPolicy
                 false, // withDeviceOwnerPolicy
                 WifiConfigurationTestUtil.createDHCPIpConfigurationWithPacProxy(),
@@ -3001,7 +3104,7 @@
         result = addNetworkToWifiConfigManager(network, TEST_NO_PERM_UID);
         assertTrue(result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID);
         verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
-                false, // withConfOverride
+                false, // withNetworkSettings
                 true, // withProfileOwnerPolicy
                 false, // withDeviceOwnerPolicy
                 WifiConfigurationTestUtil.createDHCPIpConfigurationWithPacProxy(),
@@ -3013,7 +3116,7 @@
         result = addNetworkToWifiConfigManager(network, TEST_NO_PERM_UID);
         assertTrue(result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID);
         verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
-                false, // withConfOverride
+                false, // withNetworkSettings
                 false, // withProfileOwnerPolicy
                 true, // withDeviceOwnerPolicy
                 WifiConfigurationTestUtil.createDHCPIpConfigurationWithPacProxy(),
@@ -3030,7 +3133,7 @@
         IpConfiguration ipConf = WifiConfigurationTestUtil.createDHCPIpConfigurationWithPacProxy();
         // First create a WifiConfiguration with proxy
         NetworkUpdateResult result = verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
-                        false, // withConfOverride
+                        false, // withNetworkSettings
                         true, // withProfileOwnerPolicy
                         false, // withDeviceOwnerPolicy
                         ipConf,
@@ -3038,7 +3141,7 @@
                         WifiConfiguration.INVALID_NETWORK_ID); // Update networkID
         // Update the network while using the same ipConf, and no proxy specific permissions
         verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
-                        false, // withConfOverride
+                        false, // withNetworkSettings
                         false, // withProfileOwnerPolicy
                         false, // withDeviceOwnerPolicy
                         ipConf,
@@ -3072,14 +3175,14 @@
 
         // Update with Conf Override
         NetworkUpdateResult result = verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
-                true, // withConfOverride
+                true, // withNetworkSettings
                 false, // withProfileOwnerPolicy
                 false, // withDeviceOwnerPolicy
                 ipConf1,
                 true, // assertSuccess
                 WifiConfiguration.INVALID_NETWORK_ID); // Update networkID
         verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
-                true, // withConfOverride
+                true, // withNetworkSettings
                 false, // withProfileOwnerPolicy
                 false, // withDeviceOwnerPolicy
                 ipConf2,
@@ -3088,14 +3191,14 @@
 
         // Update as Device Owner
         result = verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
-                false, // withConfOverride
+                false, // withNetworkSettings
                 false, // withProfileOwnerPolicy
                 true, // withDeviceOwnerPolicy
                 ipConf1,
                 true, // assertSuccess
                 WifiConfiguration.INVALID_NETWORK_ID); // Update networkID
         verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
-                false, // withConfOverride
+                false, // withNetworkSettings
                 false, // withProfileOwnerPolicy
                 true, // withDeviceOwnerPolicy
                 ipConf2,
@@ -3104,14 +3207,14 @@
 
         // Update as Profile Owner
         result = verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
-                false, // withConfOverride
+                false, // withNetworkSettings
                 true, // withProfileOwnerPolicy
                 false, // withDeviceOwnerPolicy
                 ipConf1,
                 true, // assertSuccess
                 WifiConfiguration.INVALID_NETWORK_ID); // Update networkID
         verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
-                false, // withConfOverride
+                false, // withNetworkSettings
                 true, // withProfileOwnerPolicy
                 false, // withDeviceOwnerPolicy
                 ipConf2,
@@ -3120,14 +3223,14 @@
 
         // Update with no permissions (should fail)
         result = verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
-                false, // withConfOverride
+                false, // withNetworkSettings
                 true, // withProfileOwnerPolicy
                 false, // withDeviceOwnerPolicy
                 ipConf1,
                 true, // assertSuccess
                 WifiConfiguration.INVALID_NETWORK_ID); // Update networkID
         verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
-                false, // withConfOverride
+                false, // withNetworkSettings
                 false, // withProfileOwnerPolicy
                 false, // withDeviceOwnerPolicy
                 ipConf2,
@@ -3160,14 +3263,14 @@
 
         // Update with Conf Override
         NetworkUpdateResult result = verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
-                true, // withConfOverride
+                true, // withNetworkSettings
                 false, // withProfileOwnerPolicy
                 false, // withDeviceOwnerPolicy
                 ipConf1,
                 true, // assertSuccess
                 WifiConfiguration.INVALID_NETWORK_ID); // Update networkID
         verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
-                true, // withConfOverride
+                true, // withNetworkSettings
                 false, // withProfileOwnerPolicy
                 false, // withDeviceOwnerPolicy
                 ipConf2,
@@ -3176,14 +3279,14 @@
 
         // Update as Device Owner
         result = verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
-                false, // withConfOverride
+                false, // withNetworkSettings
                 false, // withProfileOwnerPolicy
                 true, // withDeviceOwnerPolicy
                 ipConf1,
                 true, // assertSuccess
                 WifiConfiguration.INVALID_NETWORK_ID); // Update networkID
         verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
-                false, // withConfOverride
+                false, // withNetworkSettings
                 false, // withProfileOwnerPolicy
                 true, // withDeviceOwnerPolicy
                 ipConf2,
@@ -3192,14 +3295,14 @@
 
         // Update as Profile Owner
         result = verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
-                false, // withConfOverride
+                false, // withNetworkSettings
                 true, // withProfileOwnerPolicy
                 false, // withDeviceOwnerPolicy
                 ipConf1,
                 true, // assertSuccess
                 WifiConfiguration.INVALID_NETWORK_ID); // Update networkID
         verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
-                false, // withConfOverride
+                false, // withNetworkSettings
                 true, // withProfileOwnerPolicy
                 false, // withDeviceOwnerPolicy
                 ipConf2,
@@ -3208,14 +3311,14 @@
 
         // Update with no permissions (should fail)
         result = verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
-                false, // withConfOverride
+                false, // withNetworkSettings
                 true, // withProfileOwnerPolicy
                 false, // withDeviceOwnerPolicy
                 ipConf1,
                 true, // assertSuccess
                 WifiConfiguration.INVALID_NETWORK_ID); // Update networkID
         verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
-                false, // withConfOverride
+                false, // withNetworkSettings
                 false, // withProfileOwnerPolicy
                 false, // withDeviceOwnerPolicy
                 ipConf2,
@@ -3242,7 +3345,7 @@
     }
 
     private NetworkUpdateResult verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
-            boolean withConfOverride,
+            boolean withNetworkSettings,
             boolean withProfileOwnerPolicy,
             boolean withDeviceOwnerPolicy,
             IpConfiguration ipConfiguration,
@@ -3261,9 +3364,9 @@
         when(mDevicePolicyManagerInternal.isActiveAdminWithPolicy(anyInt(),
                 eq(DeviceAdminInfo.USES_POLICY_DEVICE_OWNER)))
                 .thenReturn(withDeviceOwnerPolicy);
-        when(mWifiPermissionsUtil.checkConfigOverridePermission(anyInt()))
-                .thenReturn(withConfOverride);
-        int uid = withConfOverride ? TEST_CREATOR_UID : TEST_NO_PERM_UID;
+        when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt()))
+                .thenReturn(withNetworkSettings);
+        int uid = withNetworkSettings ? TEST_CREATOR_UID : TEST_NO_PERM_UID;
         NetworkUpdateResult result = addNetworkToWifiConfigManager(network, uid);
         assertEquals(assertSuccess, result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID);
         return result;
@@ -3586,6 +3689,7 @@
      */
     private NetworkUpdateResult addNetworkToWifiConfigManager(WifiConfiguration configuration,
                                                               int uid) {
+        clearInvocations(mContext, mWifiConfigStore, mNetworkListStoreData);
         triggerStoreReadIfNeeded();
         when(mClock.getWallClockMillis()).thenReturn(TEST_WALLCLOCK_CREATION_TIME_MILLIS);
         NetworkUpdateResult result =
@@ -3657,6 +3761,7 @@
      * to modify the configuration before we compare the added network with the retrieved network.
      */
     private NetworkUpdateResult updateNetworkToWifiConfigManager(WifiConfiguration configuration) {
+        clearInvocations(mContext, mWifiConfigStore, mNetworkListStoreData);
         when(mClock.getWallClockMillis()).thenReturn(TEST_WALLCLOCK_UPDATE_TIME_MILLIS);
         NetworkUpdateResult result =
                 mWifiConfigManager.addOrUpdateNetwork(configuration, TEST_UPDATE_UID);
@@ -3820,25 +3925,9 @@
      */
     private ScanDetail createScanDetailForNetwork(
             WifiConfiguration configuration, String bssid, int level, int frequency) {
-        String caps;
-        if (configuration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
-            caps = "[WPA2-PSK-CCMP]";
-        } else if (configuration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP)
-                || configuration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X)) {
-            caps = "[WPA2-EAP-CCMP]";
-        } else if (configuration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)
-                && WifiConfigurationUtil.hasAnyValidWepKey(configuration.wepKeys)) {
-            caps = "[WEP]";
-        } else {
-            caps = "[]";
-        }
-        WifiSsid ssid = WifiSsid.createFromAsciiEncoded(configuration.getPrintableSsid());
-        // Fill in 0's in the fields we don't care about.
-        return new ScanDetail(
-                ssid, bssid, caps, level, frequency, mClock.getUptimeSinceBootMillis(),
-                mClock.getWallClockMillis());
+        return WifiConfigurationTestUtil.createScanDetailForNetwork(configuration, bssid, level,
+                frequency, mClock.getUptimeSinceBootMillis(), mClock.getWallClockMillis());
     }
-
     /**
      * Creates a scan detail corresponding to the provided network and BSSID value.
      */
@@ -3857,7 +3946,7 @@
      * Adds the provided network and then creates a scan detail corresponding to the network. The
      * method then creates a ScanDetail corresponding to the network and ensures that the network
      * is properly matched using
-     * {@link WifiConfigManager#getSavedNetworkForScanDetailAndCache(ScanDetail)} and also
+     * {@link WifiConfigManager#getConfiguredNetworkForScanDetailAndCache(ScanDetail)} and also
      * verifies that the provided scan detail was cached,
      */
     private void verifyAddSingleNetworkAndMatchScanDetailToNetworkAndCache(
@@ -3870,7 +3959,7 @@
         ScanResult scanResult = scanDetail.getScanResult();
 
         WifiConfiguration retrievedNetwork =
-                mWifiConfigManager.getSavedNetworkForScanDetailAndCache(scanDetail);
+                mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(scanDetail);
         // Retrieve the network with password data for comparison.
         retrievedNetwork =
                 mWifiConfigManager.getConfiguredNetworkWithPassword(retrievedNetwork.networkId);
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConfigurationTestUtil.java b/tests/wifitests/src/com/android/server/wifi/WifiConfigurationTestUtil.java
index f7bf5b0..16c9f30 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiConfigurationTestUtil.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiConfigurationTestUtil.java
@@ -26,6 +26,7 @@
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
 import android.net.wifi.WifiEnterpriseConfig;
+import android.net.wifi.WifiSsid;
 import android.text.TextUtils;
 
 import java.net.InetAddress;
@@ -224,10 +225,20 @@
      * use a static index to avoid duplicate configurations.
      */
     public static WifiConfiguration createOpenNetwork() {
-        return generateWifiConfig(TEST_NETWORK_ID, TEST_UID, createNewSSID(), true, true, null,
+        return createOpenNetwork(createNewSSID());
+    }
+
+    public static WifiConfiguration createOpenNetwork(String ssid) {
+        return generateWifiConfig(TEST_NETWORK_ID, TEST_UID, ssid, true, true, null,
                 null, SECURITY_NONE);
     }
 
+    public static WifiConfiguration createEphemeralNetwork() {
+        WifiConfiguration configuration = createOpenNetwork();
+        configuration.ephemeral = true;
+        return configuration;
+    }
+
     public static WifiConfiguration createOpenHiddenNetwork() {
         WifiConfiguration configuration = createOpenNetwork();
         configuration.hiddenSSID = true;
@@ -415,6 +426,29 @@
     }
 
     /**
+     * Creates a scan detail corresponding to the provided network and given BSSID, level &frequency
+     * values.
+     */
+    public static ScanDetail createScanDetailForNetwork(
+            WifiConfiguration configuration, String bssid, int level, int frequency,
+            long tsf, long seen) {
+        String caps;
+        if (configuration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
+            caps = "[WPA2-PSK-CCMP]";
+        } else if (configuration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP)
+                || configuration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X)) {
+            caps = "[WPA2-EAP-CCMP]";
+        } else if (configuration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)
+                && WifiConfigurationUtil.hasAnyValidWepKey(configuration.wepKeys)) {
+            caps = "[WEP]";
+        } else {
+            caps = "[]";
+        }
+        WifiSsid ssid = WifiSsid.createFromAsciiEncoded(configuration.getPrintableSsid());
+        return new ScanDetail(ssid, bssid, caps, level, frequency, tsf, seen);
+    }
+
+    /**
      * Asserts that the 2 WifiConfigurations are equal in the elements saved for both backup/restore
      * and config store.
      */
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConfigurationUtilTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConfigurationUtilTest.java
index 2b3de3f..2925273 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiConfigurationUtilTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiConfigurationUtilTest.java
@@ -19,6 +19,7 @@
 import static org.junit.Assert.*;
 
 import android.content.pm.UserInfo;
+import android.net.IpConfiguration;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiEnterpriseConfig;
 import android.net.wifi.WifiScanner;
@@ -43,8 +44,6 @@
     static final int OTHER_USER_ID = 11;
     static final String TEST_SSID = "test_ssid";
     static final String TEST_SSID_1 = "test_ssid_1";
-    static final String TEST_BSSID = "aa:aa:11:22:cc:dd";
-    static final String TEST_BSSID_1 = "11:22:11:22:cc:dd";
     static final List<UserInfo> PROFILES = Arrays.asList(
             new UserInfo(CURRENT_USER_ID, "owner", 0),
             new UserInfo(CURRENT_USER_MANAGED_PROFILE_USER_ID, "managed profile", 0));
@@ -191,6 +190,227 @@
     }
 
     /**
+     * Verify that the validate method successfully validates good WifiConfigurations with ASCII
+     * values.
+     */
+    @Test
+    public void testValidatePositiveCases_Ascii() {
+        assertTrue(WifiConfigurationUtil.validate(
+                WifiConfigurationTestUtil.createOpenNetwork(),
+                WifiConfigurationUtil.VALIDATE_FOR_ADD));
+        assertTrue(WifiConfigurationUtil.validate(
+                WifiConfigurationTestUtil.createPskNetwork(),
+                WifiConfigurationUtil.VALIDATE_FOR_ADD));
+        assertTrue(WifiConfigurationUtil.validate(
+                WifiConfigurationTestUtil.createWepNetwork(),
+                WifiConfigurationUtil.VALIDATE_FOR_ADD));
+        assertTrue(WifiConfigurationUtil.validate(
+                WifiConfigurationTestUtil.createEapNetwork(),
+                WifiConfigurationUtil.VALIDATE_FOR_ADD));
+    }
+
+    /**
+     * Verify that the validate method successfully validates good WifiConfigurations with hex
+     * values.
+     */
+    @Test
+    public void testValidatePositiveCases_Hex() {
+        WifiConfiguration config = WifiConfigurationTestUtil.createPskNetwork();
+        config.SSID = "abcd1234555a";
+        config.preSharedKey = "abcd123455151234556788990034556667332345667322344556676743233445";
+        assertTrue(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+    }
+
+    /**
+     * Verify that the validate method validates WifiConfiguration with masked psk string only for
+     * an update.
+     */
+    @Test
+    public void testValidatePositiveCases_MaskedPskString() {
+        WifiConfiguration config = WifiConfigurationTestUtil.createPskNetwork();
+        assertTrue(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+
+        config.preSharedKey = WifiConfigurationUtil.PASSWORD_MASK;
+        assertFalse(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+        assertTrue(WifiConfigurationUtil.validate(
+                config, WifiConfigurationUtil.VALIDATE_FOR_UPDATE));
+    }
+
+    /**
+     * Verify that the validate method validates WifiConfiguration with null ssid only for an
+     * update.
+     */
+    @Test
+    public void testValidatePositiveCases_OnlyUpdateIgnoresNullSsid() {
+        WifiConfiguration config = new WifiConfiguration();
+        assertFalse(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+        assertTrue(WifiConfigurationUtil.validate(
+                config, WifiConfigurationUtil.VALIDATE_FOR_UPDATE));
+    }
+
+    /**
+     * Verify that the validate method fails to validate WifiConfiguration with bad ssid length.
+     */
+    @Test
+    public void testValidateNegativeCases_BadAsciiSsidLength() {
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+        assertTrue(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+
+        config.SSID = "\"abcdfefeeretretyetretetetetetrertertrsreqwrwe\"";
+        assertFalse(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+        config.SSID = "\"\"";
+        assertFalse(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+    }
+
+    /**
+     * Verify that the validate method fails to validate WifiConfiguration with malformed ssid
+     * string.
+     */
+    @Test
+    public void testValidateNegativeCases_MalformedAsciiSsidString() {
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+        assertTrue(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+
+        config.SSID = "\"ab";
+        assertFalse(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+    }
+
+    /**
+     * Verify that the validate method fails to validate WifiConfiguration with bad ssid length.
+     */
+    @Test
+    public void testValidateNegativeCases_BadHexSsidLength() {
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+        assertTrue(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+
+        config.SSID = "abcdfe012345632423343543453456464545656464545646454ace34534545634535";
+        assertFalse(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+        config.SSID = "";
+        assertFalse(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+    }
+
+    /**
+     * Verify that the validate method fails to validate WifiConfiguration with malformed ssid
+     * string.
+     */
+    @Test
+    public void testValidateNegativeCases_MalformedHexSsidString() {
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+        assertTrue(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+
+        config.SSID = "hello";
+        assertFalse(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+    }
+
+    /**
+     * Verify that the validate method fails to validate WifiConfiguration with bad psk length.
+     */
+    @Test
+    public void testValidateNegativeCases_BadAsciiPskLength() {
+        WifiConfiguration config = WifiConfigurationTestUtil.createPskNetwork();
+        assertTrue(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+
+        config.preSharedKey = "\"abcdffeeretretyetreteteteabe34tetrertertrsraaaaaaaaaaa345eqwrweewq"
+                + "weqe\"";
+        assertFalse(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+        config.preSharedKey = "\"454\"";
+        assertFalse(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+    }
+
+    /**
+     * Verify that the validate method fails to validate WifiConfiguration with malformed psk
+     * string.
+     */
+    @Test
+    public void testValidateNegativeCases_MalformedAsciiPskString() {
+        WifiConfiguration config = WifiConfigurationTestUtil.createPskNetwork();
+        assertTrue(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+
+        config.preSharedKey = "\"abcdfefeeretrety";
+        assertFalse(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+    }
+
+    /**
+     * Verify that the validate method fails to validate WifiConfiguration with bad psk length.
+     */
+    @Test
+    public void testValidateNegativeCases_BadHexPskLength() {
+        WifiConfiguration config = WifiConfigurationTestUtil.createPskNetwork();
+        assertTrue(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+
+        config.preSharedKey = "abcd123456788990013453445345465465476546";
+        assertFalse(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+        config.preSharedKey = "";
+        assertFalse(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+    }
+
+    /**
+     * Verify that the validate method fails to validate WifiConfiguration with malformed psk
+     * string.
+     */
+    @Test
+    public void testValidateNegativeCases_MalformedHexPskString() {
+        WifiConfiguration config = WifiConfigurationTestUtil.createPskNetwork();
+        assertTrue(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+
+        config.preSharedKey = "adbdfgretrtyrtyrty";
+        assertFalse(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+    }
+
+    /**
+     * Verify that the validate method fails to validate WifiConfiguration with bad key mgmt values.
+     */
+    @Test
+    public void testValidateNegativeCases_BadKeyMgmtPskEap() {
+        WifiConfiguration config = WifiConfigurationTestUtil.createPskNetwork();
+        assertTrue(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
+        assertFalse(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+    }
+
+    /**
+     * Verify that the validate method fails to validate WifiConfiguration with bad key mgmt values.
+     */
+    @Test
+    public void testValidateNegativeCases_BadKeyMgmtOpenPsk() {
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+        assertTrue(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+        assertFalse(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+    }
+
+    /**
+     * Verify that the validate method fails to validate WifiConfiguration with bad key mgmt values.
+     */
+    @Test
+    public void testValidateNegativeCases_BadKeyMgmt() {
+        WifiConfiguration config = WifiConfigurationTestUtil.createPskNetwork();
+        assertTrue(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
+        assertFalse(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+    }
+
+    /**
+     * Verify that the validate method fails to validate WifiConfiguration with bad ipconfiguration
+     * values.
+     */
+    @Test
+    public void testValidateNegativeCases_BadIpconfiguration() {
+        WifiConfiguration config = WifiConfigurationTestUtil.createPskNetwork();
+        IpConfiguration ipConfig =
+                WifiConfigurationTestUtil.createStaticIpConfigurationWithPacProxy();
+        config.setIpConfiguration(ipConfig);
+        assertTrue(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+
+        ipConfig.setStaticIpConfiguration(null);
+        config.setIpConfiguration(ipConfig);
+        assertFalse(WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD));
+    }
+
+    /**
      * Verify the instance of {@link android.net.wifi.WifiScanner.PnoSettings.PnoNetwork} created
      * for an open network using {@link WifiConfigurationUtil#createPnoNetwork(
      * WifiConfiguration, int)}.
@@ -254,19 +474,6 @@
     }
 
     /**
-     * Verify that WifiConfigurationUtil.isSameNetwork returns true when two WifiConfiguration
-     * objects have the same parameters but different network selection BSSID's.
-     */
-    @Test
-    public void testIsSameNetworkReturnsTrueOnSameNetworkWithDifferentBSSID() {
-        WifiConfiguration network = WifiConfigurationTestUtil.createPskNetwork(TEST_SSID);
-        network.getNetworkSelectionStatus().setNetworkSelectionBSSID(TEST_BSSID);
-        WifiConfiguration network1 = WifiConfigurationTestUtil.createPskNetwork(TEST_SSID);
-        network1.getNetworkSelectionStatus().setNetworkSelectionBSSID(TEST_BSSID_1);
-        assertTrue(WifiConfigurationUtil.isSameNetwork(network, network1));
-    }
-
-    /**
      * Verify that WifiConfigurationUtil.isSameNetwork returns false when two WifiConfiguration
      * objects have the different SSIDs.
      */
@@ -288,6 +495,31 @@
         assertFalse(WifiConfigurationUtil.isSameNetwork(network, network1));
     }
 
+    /**
+     * Verify that WifiConfigurationUtil.isSameNetwork returns false when two WifiConfiguration
+     * objects have the different EAP identity.
+     */
+    @Test
+    public void testIsSameNetworkReturnsFalseOnDifferentEapIdentity() {
+        WifiConfiguration network1 = WifiConfigurationTestUtil.createEapNetwork(TEST_SSID);
+        WifiConfiguration network2 = WifiConfigurationTestUtil.createEapNetwork(TEST_SSID);
+        network1.enterpriseConfig.setIdentity("Identity1");
+        network2.enterpriseConfig.setIdentity("Identity2");
+        assertFalse(WifiConfigurationUtil.isSameNetwork(network1, network2));
+    }
+
+    /**
+     * Verify that WifiConfigurationUtil.isSameNetwork returns false when two WifiConfiguration
+     * objects have the different EAP anonymous identity.
+     */
+    @Test
+    public void testIsSameNetworkReturnsFalseOnDifferentEapAnonymousIdentity() {
+        WifiConfiguration network1 = WifiConfigurationTestUtil.createEapNetwork(TEST_SSID);
+        WifiConfiguration network2 = WifiConfigurationTestUtil.createEapNetwork(TEST_SSID);
+        network1.enterpriseConfig.setAnonymousIdentity("Identity1");
+        network2.enterpriseConfig.setAnonymousIdentity("Identity2");
+        assertFalse(WifiConfigurationUtil.isSameNetwork(network1, network2));
+    }
 
     /**
      * Verify the instance of {@link android.net.wifi.WifiScanner.PnoSettings.PnoNetwork} created
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java
index 68c2ca3..bdb6b14 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java
@@ -25,6 +25,7 @@
 import android.app.test.MockAnswerUtil.AnswerWithArguments;
 import android.app.test.TestAlarmManager;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.net.NetworkScoreManager;
 import android.net.wifi.ScanResult;
@@ -50,7 +51,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
@@ -63,6 +63,7 @@
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.List;
 
 /**
  * Unit tests for {@link com.android.server.wifi.WifiConnectivityManager}.
@@ -90,6 +91,10 @@
         verify(mWifiConfigManager).setOnSavedNetworkUpdateListener(anyObject());
         mWifiConnectivityManager.setWifiEnabled(true);
         when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime());
+        mFullScanMaxTxPacketRate = mResource.getInteger(
+                R.integer.config_wifi_framework_max_tx_rate_for_full_scan);
+        mFullScanMaxRxPacketRate = mResource.getInteger(
+                R.integer.config_wifi_framework_max_rx_rate_for_full_scan);
     }
 
     /**
@@ -118,12 +123,15 @@
     @Mock private NetworkScoreManager mNetworkScoreManager;
     @Mock private Clock mClock;
     @Mock private WifiLastResortWatchdog mWifiLastResortWatchdog;
+    @Mock private WifiNotificationController mWifiNotificationController;
     @Mock private WifiMetrics mWifiMetrics;
     @Mock private WifiNetworkScoreCache mScoreCache;
     @Captor ArgumentCaptor<ScanResult> mCandidateScanResultCaptor;
     @Captor ArgumentCaptor<ArrayList<String>> mBssidBlacklistCaptor;
     @Captor ArgumentCaptor<ArrayList<String>> mSsidWhitelistCaptor;
     private MockResources mResources;
+    private int mFullScanMaxTxPacketRate;
+    private int mFullScanMaxRxPacketRate;
 
     private static final int CANDIDATE_NETWORK_ID = 0;
     private static final String CANDIDATE_SSID = "\"AnSsid\"";
@@ -145,6 +153,10 @@
                 .thenReturn(-60);
         when(resource.getInteger(
                 R.integer.config_wifi_framework_current_network_boost)).thenReturn(16);
+        when(resource.getInteger(
+                R.integer.config_wifi_framework_max_tx_rate_for_full_scan)).thenReturn(8);
+        when(resource.getInteger(
+                R.integer.config_wifi_framework_max_rx_rate_for_full_scan)).thenReturn(16);
         return resource;
     }
 
@@ -154,6 +166,7 @@
         when(context.getResources()).thenReturn(mResource);
         when(context.getSystemService(Context.ALARM_SERVICE)).thenReturn(
                 mAlarmManager.getAlarmManager());
+        when(context.getPackageManager()).thenReturn(mock(PackageManager.class));
 
         return context;
     }
@@ -281,8 +294,8 @@
     WifiConnectivityManager createConnectivityManager() {
         return new WifiConnectivityManager(mContext, mWifiStateMachine, mWifiScanner,
                 mWifiConfigManager, mWifiInfo, mWifiNS, mWifiConnectivityHelper,
-                mWifiLastResortWatchdog, mWifiMetrics, mLooper.getLooper(), mClock,
-                mLocalLog, true, mFrameworkFacade, null, null, null);
+                mWifiLastResortWatchdog, mWifiNotificationController, mWifiMetrics,
+                mLooper.getLooper(), mClock, mLocalLog, true, mFrameworkFacade, null, null, null);
     }
 
     /**
@@ -521,7 +534,6 @@
      * because of their low RSSI values.
      */
     @Test
-    @Ignore("b/32977707")
     public void pnoRetryForLowRssiNetwork() {
         when(mWifiNS.selectNetwork(anyObject(), anyObject(), anyObject(), anyBoolean(),
                 anyBoolean(), anyBoolean())).thenReturn(null);
@@ -553,7 +565,6 @@
      * a candidate while watchdog single scan did.
      */
     @Test
-    @Ignore("b/32977707")
     public void watchdogBitePnoBadIncrementsMetrics() {
         // Set screen to off
         mWifiConnectivityManager.handleScreenStateChanged(false);
@@ -577,7 +588,6 @@
      * a candidate which was the same with watchdog single scan.
      */
     @Test
-    @Ignore("b/32977707")
     public void watchdogBitePnoGoodIncrementsMetrics() {
         // Qns returns no candidate after watchdog single scan.
         when(mWifiNS.selectNetwork(anyObject(), anyObject(), anyObject(), anyBoolean(),
@@ -599,6 +609,90 @@
     }
 
     /**
+     * {@link WifiNotificationController} handles scan results on network selection.
+     *
+     * Expected behavior: ONA handles scan results
+     */
+    @Test
+    public void wifiDisconnected_noConnectionCandidate_openNetworkNotificationScanResultsHandled() {
+        // no connection candidate selected
+        when(mWifiNS.selectNetwork(anyObject(), anyObject(), anyObject(), anyBoolean(),
+                anyBoolean(), anyBoolean())).thenReturn(null);
+
+        List<ScanDetail> expectedOpenNetworks = new ArrayList<>();
+        expectedOpenNetworks.add(
+                new ScanDetail(
+                        new ScanResult(WifiSsid.createFromAsciiEncoded(CANDIDATE_SSID),
+                                CANDIDATE_SSID, CANDIDATE_BSSID, 1245, 0, "some caps", -78, 2450,
+                                1025, 22, 33, 20, 0, 0, true), null));
+
+        when(mWifiNS.getFilteredScanDetailsForOpenUnsavedNetworks())
+                .thenReturn(expectedOpenNetworks);
+
+        // Set WiFi to disconnected state to trigger PNO scan
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
+
+        verify(mWifiNotificationController).handleScanResults(expectedOpenNetworks);
+    }
+
+    /**
+     * When wifi is connected, {@link WifiNotificationController} tries to clear the pending
+     * notification and does not reset notification repeat delay.
+     *
+     * Expected behavior: ONA clears pending notification and does not reset repeat delay.
+     */
+    @Test
+    public void wifiConnected_openNetworkNotificationClearsPendingNotification() {
+        // Set WiFi to connected state
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                WifiConnectivityManager.WIFI_STATE_CONNECTED);
+
+        verify(mWifiNotificationController).clearPendingNotification(false /* isRepeatDelayReset*/);
+    }
+
+    /**
+     * When wifi is connected, {@link WifiNotificationController} handles connection state
+     * change.
+     *
+     * Expected behavior: ONA does not clear pending notification.
+     */
+    @Test
+    public void wifiDisconnected_openNetworkNotificationDoesNotClearPendingNotification() {
+        // Set WiFi to disconnected state
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
+
+        verify(mWifiNotificationController, never()).clearPendingNotification(anyBoolean());
+    }
+
+    /**
+     * When Wi-Fi is disabled, clear the pending notification and reset notification repeat delay.
+     *
+     * Expected behavior: clear pending notification and reset notification repeat delay
+     * */
+    @Test
+    public void openNetworkNotificationControllerToggledOnWifiStateChanges() {
+        mWifiConnectivityManager.setWifiEnabled(false);
+
+        verify(mWifiNotificationController).clearPendingNotification(true /* isRepeatDelayReset */);
+    }
+
+    /**
+     * Verify that the ONA controller tracks screen state changes.
+     */
+    @Test
+    public void openNetworkNotificationControllerTracksScreenStateChanges() {
+        mWifiConnectivityManager.handleScreenStateChanged(false);
+
+        verify(mWifiNotificationController).handleScreenStateChanged(false);
+
+        mWifiConnectivityManager.handleScreenStateChanged(true);
+
+        verify(mWifiNotificationController).handleScreenStateChanged(true);
+    }
+
+    /**
      *  Verify that scan interval for screen on and wifi disconnected scenario
      *  is in the exponential backoff fashion.
      *
@@ -906,8 +1000,8 @@
      */
     @Test
     public void checkSingleScanSettingsWhenConnectedWithHighDataRate() {
-        mWifiInfo.txSuccessRate = WifiConnectivityManager.MAX_TX_PACKET_FOR_FULL_SCANS * 2;
-        mWifiInfo.rxSuccessRate = WifiConnectivityManager.MAX_RX_PACKET_FOR_FULL_SCANS * 2;
+        mWifiInfo.txSuccessRate = mFullScanMaxTxPacketRate * 2;
+        mWifiInfo.rxSuccessRate = mFullScanMaxRxPacketRate * 2;
 
         final HashSet<Integer> channelList = new HashSet<>();
         channelList.add(1);
@@ -948,8 +1042,8 @@
      */
     @Test
     public void checkSingleScanSettingsWhenConnectedWithHighDataRateNotInCache() {
-        mWifiInfo.txSuccessRate = WifiConnectivityManager.MAX_TX_PACKET_FOR_FULL_SCANS * 2;
-        mWifiInfo.rxSuccessRate = WifiConnectivityManager.MAX_RX_PACKET_FOR_FULL_SCANS * 2;
+        mWifiInfo.txSuccessRate = mFullScanMaxTxPacketRate * 2;
+        mWifiInfo.rxSuccessRate = mFullScanMaxRxPacketRate * 2;
 
         final HashSet<Integer> channelList = new HashSet<>();
 
@@ -1554,4 +1648,19 @@
         mWifiConnectivityManager.dump(new FileDescriptor(), pw, new String[]{});
         assertTrue(sw.toString().contains(localLogMessage));
     }
+
+    /**
+     *  Dump ONA controller.
+     *
+     * Expected behavior: {@link WifiNotificationController#dump(FileDescriptor, PrintWriter,
+     * String[])} is invoked.
+     */
+    @Test
+    public void dumpNotificationController() {
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        mWifiConnectivityManager.dump(new FileDescriptor(), pw, new String[]{});
+
+        verify(mWifiNotificationController).dump(any(), any(), any());
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiDiagnosticsTest.java b/tests/wifitests/src/com/android/server/wifi/WifiDiagnosticsTest.java
index f4b710e..6b93e05 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiDiagnosticsTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiDiagnosticsTest.java
@@ -246,7 +246,7 @@
     /**
      * Verifies that we discard extraneous ring-buffer data.
      */
-    @Test
+    // TODO(b/36811399): re-enabled this @Test
     public void loggerDiscardsExtraneousData() throws Exception {
         final boolean verbosityToggle = false;
         setBuildPropertiesToEnableRingBuffers();
@@ -616,8 +616,19 @@
         mWifiDiagnostics.dump(new FileDescriptor(), pw, new String[]{"bogus", "args"});
     }
 
+    /** Verifies that the default size of our ring buffers is small. */
+    // TODO(b/36811399): re-enable this @Test
+    public void ringBufferSizeIsSmallByDefault() throws Exception {
+        final boolean verbosityToggle = false;
+        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.onRingBufferData(
+                mFakeRbs, new byte[SMALL_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE + 1]);
+        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
+        assertEquals(0, getLoggerRingBufferData().length);
+    }
+
     /** Verifies that we use small ring buffers by default, on userdebug builds. */
-    @Test
+    // TODO(b/36811399): re-enable this @Test
     public void ringBufferSizeIsSmallByDefaultOnUserdebugBuilds() throws Exception {
         final boolean verbosityToggle = false;
         when(mBuildProperties.isUserdebugBuild()).thenReturn(true);
@@ -631,7 +642,7 @@
     }
 
     /** Verifies that we use small ring buffers by default, on eng builds. */
-    @Test
+    // TODO(b/36811399): re-enable this @Test
     public void ringBufferSizeIsSmallByDefaultOnEngBuilds() throws Exception {
         final boolean verbosityToggle = false;
         when(mBuildProperties.isEngBuild()).thenReturn(true);
@@ -671,7 +682,7 @@
     }
 
     /** Verifies that we use small ring buffers when switched from verbose to normal mode. */
-    @Test
+    // TODO(b/36811399): re-enabled this @Test
     public void startLoggingShrinksRingBuffersIfNeeded() throws Exception {
         setBuildPropertiesToEnableRingBuffers();
 
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java b/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
index 1c09cac..5a13928 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
@@ -29,9 +29,13 @@
 import android.os.test.TestLooper;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Base64;
+import android.util.Pair;
 
-
+import com.android.server.wifi.aware.WifiAwareMetrics;
 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.nano.WifiMetricsProto;
 import com.android.server.wifi.nano.WifiMetricsProto.StaEvent;
 
@@ -57,17 +61,24 @@
 public class WifiMetricsTest {
 
     WifiMetrics mWifiMetrics;
-    WifiMetricsProto.WifiLog mDeserializedWifiMetrics;
+    WifiMetricsProto.WifiLog mDecodedProto;
     TestLooper mTestLooper;
     @Mock Clock mClock;
+    @Mock WifiConfigManager mWcm;
+    @Mock PasspointManager mPpm;
+    @Mock WifiNetworkSelector mWns;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mDeserializedWifiMetrics = null;
+        mDecodedProto = null;
         when(mClock.getElapsedSinceBootMillis()).thenReturn((long) 0);
         mTestLooper = new TestLooper();
-        mWifiMetrics = new WifiMetrics(mClock, mTestLooper.getLooper());
+        mWifiMetrics = new WifiMetrics(mClock, mTestLooper.getLooper(),
+                new WifiAwareMetrics(mClock));
+        mWifiMetrics.setWifiConfigManager(mWcm);
+        mWifiMetrics.setPasspointManager(mPpm);
+        mWifiMetrics.setWifiNetworkSelector(mWns);
     }
 
     /**
@@ -95,10 +106,9 @@
 
     private static final long TEST_RECORD_DURATION_SEC = 12 * 60 * 60;
     private static final long TEST_RECORD_DURATION_MILLIS = TEST_RECORD_DURATION_SEC * 1000;
-
     /**
      * Simulate how dumpsys gets the proto from mWifiMetrics, filter the proto bytes out and
-     * deserialize them into mDeserializedWifiMetrics
+     * deserialize them into mDecodedProto
      */
     public void dumpProtoAndDeserialize() throws Exception {
         ByteArrayOutputStream stream = new ByteArrayOutputStream();
@@ -117,12 +127,12 @@
                 matcher.find());
         String protoByteString = matcher.group(1);
         byte[] protoBytes = Base64.decode(protoByteString, Base64.DEFAULT);
-        mDeserializedWifiMetrics = WifiMetricsProto.WifiLog.parseFrom(protoBytes);
+        mDecodedProto = WifiMetricsProto.WifiLog.parseFrom(protoBytes);
     }
 
     /**
      * Gets the 'clean dump' proto bytes from mWifiMetrics & deserializes it into
-     * mDeserializedWifiMetrics
+     * mDecodedProto
      */
     public void cleanDumpProtoAndDeserialize() throws Exception {
         ByteArrayOutputStream stream = new ByteArrayOutputStream();
@@ -136,7 +146,7 @@
         writer.flush();
         String protoByteString = stream.toString();
         byte[] protoBytes = Base64.decode(protoByteString, Base64.DEFAULT);
-        mDeserializedWifiMetrics = WifiMetricsProto.WifiLog.parseFrom(protoBytes);
+        mDecodedProto = WifiMetricsProto.WifiLog.parseFrom(protoBytes);
     }
 
     /** Verifies that dump() includes the expected header */
@@ -235,6 +245,13 @@
     private static final int NUM_WIFICOND_CRASHES = 12;
     private static final int NUM_WIFI_ON_FAILURE_DUE_TO_HAL = 13;
     private static final int NUM_WIFI_ON_FAILURE_DUE_TO_WIFICOND = 14;
+    private static final int NUM_PASSPOINT_PROVIDERS = 4;
+    private static final int NUM_PASSPOINT_PROVIDER_INSTALLATION = 5;
+    private static final int NUM_PASSPOINT_PROVIDER_INSTALL_SUCCESS = 4;
+    private static final int NUM_PASSPOINT_PROVIDER_UNINSTALLATION = 3;
+    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 ScanDetail buildMockScanDetail(boolean hidden, NetworkDetail.HSRelease hSRelease,
             String capabilities) {
@@ -249,6 +266,30 @@
         return mockScanDetail;
     }
 
+    private ScanDetail buildMockScanDetail(String ssid, String bssid, boolean isOpen,
+            boolean isSaved, boolean isProvider, boolean isWeakRssi) {
+        ScanDetail mockScanDetail = mock(ScanDetail.class);
+        NetworkDetail mockNetworkDetail = mock(NetworkDetail.class);
+        ScanResult scanResult = new ScanResult();
+        scanResult.SSID = ssid;
+        scanResult.BSSID = bssid;
+        when(mockScanDetail.getNetworkDetail()).thenReturn(mockNetworkDetail);
+        when(mockScanDetail.getScanResult()).thenReturn(scanResult);
+        when(mWns.isSignalTooWeak(eq(scanResult))).thenReturn(isWeakRssi);
+        scanResult.capabilities = isOpen ? "" : "PSK";
+        if (isSaved) {
+            when(mWcm.getConfiguredNetworkForScanDetail(eq(mockScanDetail)))
+                    .thenReturn(mock(WifiConfiguration.class));
+        }
+        if (isProvider) {
+            PasspointProvider provider = mock(PasspointProvider.class);
+            Pair<PasspointProvider, PasspointMatch> providerMatch = Pair.create(provider, null);
+            when(mockNetworkDetail.isInterworking()).thenReturn(true);
+            when(mPpm.matchProvider(eq(scanResult))).thenReturn(providerMatch);
+        }
+        return mockScanDetail;
+    }
+
     private List<ScanDetail> buildMockScanDetailList() {
         List<ScanDetail> mockScanDetails = new ArrayList<ScanDetail>();
         mockScanDetails.add(buildMockScanDetail(true, null, "[ESS]"));
@@ -286,6 +327,8 @@
      */
     public void setAndIncrementMetrics() throws Exception {
         mWifiMetrics.updateSavedNetworks(buildSavedNetworkList());
+        mWifiMetrics.updateSavedPasspointProfiles(NUM_PASSPOINT_PROVIDERS,
+                NUM_PASSPOINT_PROVIDERS_SUCCESSFULLY_CONNECTED);
         mWifiMetrics.setIsLocationEnabled(TEST_VAL_IS_LOCATION_ENABLED);
         mWifiMetrics.setIsScanningAlwaysEnabled(IS_SCANNING_ALWAYS_ENABLED);
 
@@ -419,43 +462,48 @@
         for (int i = 0; i < NUM_WIFI_ON_FAILURE_DUE_TO_WIFICOND; i++) {
             mWifiMetrics.incrementNumWifiOnFailureDueToWificond();
         }
+        for (int i = 0; i < NUM_PASSPOINT_PROVIDER_INSTALLATION; i++) {
+            mWifiMetrics.incrementNumPasspointProviderInstallation();
+        }
+        for (int i = 0; i < NUM_PASSPOINT_PROVIDER_INSTALL_SUCCESS; i++) {
+            mWifiMetrics.incrementNumPasspointProviderInstallSuccess();
+        }
+        for (int i = 0; i < NUM_PASSPOINT_PROVIDER_UNINSTALLATION; i++) {
+            mWifiMetrics.incrementNumPasspointProviderUninstallation();
+        }
+        for (int i = 0; i < NUM_PASSPOINT_PROVIDER_UNINSTALL_SUCCESS; i++) {
+            mWifiMetrics.incrementNumPasspointProviderUninstallSuccess();
+        }
     }
 
     /**
      * Assert that values in deserializedWifiMetrics match those set in 'setAndIncrementMetrics'
      */
     public void assertDeserializedMetricsCorrect() throws Exception {
-        assertEquals("mDeserializedWifiMetrics.numSavedNetworks == NUM_SAVED_NETWORKS",
-                mDeserializedWifiMetrics.numSavedNetworks, NUM_SAVED_NETWORKS);
-        assertEquals("mDeserializedWifiMetrics.numOpenNetworks == NUM_OPEN_NETWORKS",
-                mDeserializedWifiMetrics.numOpenNetworks, NUM_OPEN_NETWORKS);
-        assertEquals("mDeserializedWifiMetrics.numPersonalNetworks == NUM_PERSONAL_NETWORKS",
-                mDeserializedWifiMetrics.numPersonalNetworks, NUM_PERSONAL_NETWORKS);
-        assertEquals("mDeserializedWifiMetrics.numEnterpriseNetworks "
-                        + "== NUM_ENTERPRISE_NETWORKS",
-                mDeserializedWifiMetrics.numEnterpriseNetworks, NUM_ENTERPRISE_NETWORKS);
-        assertEquals("mDeserializedWifiMetrics.numNetworksAddedByUser "
-                        + "== NUM_NETWORKS_ADDED_BY_USER",
-                mDeserializedWifiMetrics.numNetworksAddedByUser, NUM_NETWORKS_ADDED_BY_USER);
-        assertEquals(NUM_HIDDEN_NETWORKS, mDeserializedWifiMetrics.numHiddenNetworks);
-        assertEquals(NUM_PASSPOINT_NETWORKS, mDeserializedWifiMetrics.numPasspointNetworks);
-        assertEquals("mDeserializedWifiMetrics.numNetworksAddedByApps "
-                        + "== NUM_NETWORKS_ADDED_BY_APPS",
-                mDeserializedWifiMetrics.numNetworksAddedByApps, NUM_NETWORKS_ADDED_BY_APPS);
-        assertEquals("mDeserializedWifiMetrics.isLocationEnabled == TEST_VAL_IS_LOCATION_ENABLED",
-                mDeserializedWifiMetrics.isLocationEnabled, TEST_VAL_IS_LOCATION_ENABLED);
-        assertEquals("mDeserializedWifiMetrics.isScanningAlwaysEnabled "
-                        + "== IS_SCANNING_ALWAYS_ENABLED",
-                mDeserializedWifiMetrics.isScanningAlwaysEnabled, IS_SCANNING_ALWAYS_ENABLED);
-        assertEquals("mDeserializedWifiMetrics.numEmptyScanResults == NUM_EMPTY_SCAN_RESULTS",
-                mDeserializedWifiMetrics.numEmptyScanResults, NUM_EMPTY_SCAN_RESULTS);
-        assertEquals("mDeserializedWifiMetrics.numNonEmptyScanResults == "
-                        + "NUM_NON_EMPTY_SCAN_RESULTS",
-                mDeserializedWifiMetrics.numNonEmptyScanResults, NUM_NON_EMPTY_SCAN_RESULTS);
-        assertScanReturnEntryEquals(WifiMetricsProto.WifiLog.SCAN_UNKNOWN,
-                NUM_SCAN_UNKNOWN);
-        assertScanReturnEntryEquals(WifiMetricsProto.WifiLog.SCAN_SUCCESS,
-                NUM_SCAN_SUCCESS);
+        assertEquals("mDecodedProto.numSavedNetworks == NUM_SAVED_NETWORKS",
+                mDecodedProto.numSavedNetworks, NUM_SAVED_NETWORKS);
+        assertEquals("mDecodedProto.numOpenNetworks == NUM_OPEN_NETWORKS",
+                mDecodedProto.numOpenNetworks, NUM_OPEN_NETWORKS);
+        assertEquals("mDecodedProto.numPersonalNetworks == NUM_PERSONAL_NETWORKS",
+                mDecodedProto.numPersonalNetworks, NUM_PERSONAL_NETWORKS);
+        assertEquals("mDecodedProto.numEnterpriseNetworks == NUM_ENTERPRISE_NETWORKS",
+                mDecodedProto.numEnterpriseNetworks, NUM_ENTERPRISE_NETWORKS);
+        assertEquals("mDecodedProto.numNetworksAddedByUser == NUM_NETWORKS_ADDED_BY_USER",
+                mDecodedProto.numNetworksAddedByUser, NUM_NETWORKS_ADDED_BY_USER);
+        assertEquals(NUM_HIDDEN_NETWORKS, mDecodedProto.numHiddenNetworks);
+        assertEquals(NUM_PASSPOINT_NETWORKS, mDecodedProto.numPasspointNetworks);
+        assertEquals("mDecodedProto.numNetworksAddedByApps == NUM_NETWORKS_ADDED_BY_APPS",
+                mDecodedProto.numNetworksAddedByApps, NUM_NETWORKS_ADDED_BY_APPS);
+        assertEquals("mDecodedProto.isLocationEnabled == TEST_VAL_IS_LOCATION_ENABLED",
+                mDecodedProto.isLocationEnabled, TEST_VAL_IS_LOCATION_ENABLED);
+        assertEquals("mDecodedProto.isScanningAlwaysEnabled == IS_SCANNING_ALWAYS_ENABLED",
+                mDecodedProto.isScanningAlwaysEnabled, IS_SCANNING_ALWAYS_ENABLED);
+        assertEquals("mDecodedProto.numEmptyScanResults == NUM_EMPTY_SCAN_RESULTS",
+                mDecodedProto.numEmptyScanResults, NUM_EMPTY_SCAN_RESULTS);
+        assertEquals("mDecodedProto.numNonEmptyScanResults == NUM_NON_EMPTY_SCAN_RESULTS",
+                mDecodedProto.numNonEmptyScanResults, NUM_NON_EMPTY_SCAN_RESULTS);
+        assertScanReturnEntryEquals(WifiMetricsProto.WifiLog.SCAN_UNKNOWN, NUM_SCAN_UNKNOWN);
+        assertScanReturnEntryEquals(WifiMetricsProto.WifiLog.SCAN_SUCCESS, NUM_SCAN_SUCCESS);
         assertScanReturnEntryEquals(WifiMetricsProto.WifiLog.SCAN_FAILURE_INTERRUPTED,
                 NUM_SCAN_FAILURE_INTERRUPTED);
         assertScanReturnEntryEquals(WifiMetricsProto.WifiLog.SCAN_FAILURE_INVALID_CONFIGURATION,
@@ -468,108 +516,117 @@
                 WifiMetricsProto.WifiLog.WIFI_ASSOCIATED, false, NUM_WIFI_ASSOCIATED_SCREEN_OFF);
         assertSystemStateEntryEquals(WifiMetricsProto.WifiLog.WIFI_ASSOCIATED, true,
                 NUM_WIFI_ASSOCIATED_SCREEN_ON);
-        assertEquals(mDeserializedWifiMetrics.numConnectivityWatchdogPnoGood,
+        assertEquals(mDecodedProto.numConnectivityWatchdogPnoGood,
                 NUM_CONNECTIVITY_WATCHDOG_PNO_GOOD);
-        assertEquals(mDeserializedWifiMetrics.numConnectivityWatchdogPnoBad,
+        assertEquals(mDecodedProto.numConnectivityWatchdogPnoBad,
                 NUM_CONNECTIVITY_WATCHDOG_PNO_BAD);
-        assertEquals(mDeserializedWifiMetrics.numConnectivityWatchdogBackgroundGood,
+        assertEquals(mDecodedProto.numConnectivityWatchdogBackgroundGood,
                 NUM_CONNECTIVITY_WATCHDOG_BACKGROUND_GOOD);
-        assertEquals(mDeserializedWifiMetrics.numConnectivityWatchdogBackgroundBad,
+        assertEquals(mDecodedProto.numConnectivityWatchdogBackgroundBad,
                 NUM_CONNECTIVITY_WATCHDOG_BACKGROUND_BAD);
         assertEquals(NUM_LAST_RESORT_WATCHDOG_TRIGGERS,
-                mDeserializedWifiMetrics.numLastResortWatchdogTriggers);
+                mDecodedProto.numLastResortWatchdogTriggers);
         assertEquals(NUM_LAST_RESORT_WATCHDOG_BAD_ASSOCIATION_NETWORKS_TOTAL,
-                mDeserializedWifiMetrics.numLastResortWatchdogBadAssociationNetworksTotal);
+                mDecodedProto.numLastResortWatchdogBadAssociationNetworksTotal);
         assertEquals(NUM_LAST_RESORT_WATCHDOG_BAD_AUTHENTICATION_NETWORKS_TOTAL,
-                mDeserializedWifiMetrics.numLastResortWatchdogBadAuthenticationNetworksTotal);
+                mDecodedProto.numLastResortWatchdogBadAuthenticationNetworksTotal);
         assertEquals(NUM_LAST_RESORT_WATCHDOG_BAD_DHCP_NETWORKS_TOTAL,
-                mDeserializedWifiMetrics.numLastResortWatchdogBadDhcpNetworksTotal);
+                mDecodedProto.numLastResortWatchdogBadDhcpNetworksTotal);
         assertEquals(NUM_LAST_RESORT_WATCHDOG_BAD_OTHER_NETWORKS_TOTAL,
-                mDeserializedWifiMetrics.numLastResortWatchdogBadOtherNetworksTotal);
+                mDecodedProto.numLastResortWatchdogBadOtherNetworksTotal);
         assertEquals(NUM_LAST_RESORT_WATCHDOG_AVAILABLE_NETWORKS_TOTAL,
-                mDeserializedWifiMetrics.numLastResortWatchdogAvailableNetworksTotal);
+                mDecodedProto.numLastResortWatchdogAvailableNetworksTotal);
         assertEquals(NUM_LAST_RESORT_WATCHDOG_TRIGGERS_WITH_BAD_ASSOCIATION,
-                mDeserializedWifiMetrics.numLastResortWatchdogTriggersWithBadAssociation);
+                mDecodedProto.numLastResortWatchdogTriggersWithBadAssociation);
         assertEquals(NUM_LAST_RESORT_WATCHDOG_TRIGGERS_WITH_BAD_AUTHENTICATION,
-                mDeserializedWifiMetrics.numLastResortWatchdogTriggersWithBadAuthentication);
+                mDecodedProto.numLastResortWatchdogTriggersWithBadAuthentication);
         assertEquals(NUM_LAST_RESORT_WATCHDOG_TRIGGERS_WITH_BAD_DHCP,
-                mDeserializedWifiMetrics.numLastResortWatchdogTriggersWithBadDhcp);
+                mDecodedProto.numLastResortWatchdogTriggersWithBadDhcp);
         assertEquals(NUM_LAST_RESORT_WATCHDOG_TRIGGERS_WITH_BAD_OTHER,
-                mDeserializedWifiMetrics.numLastResortWatchdogTriggersWithBadOther);
+                mDecodedProto.numLastResortWatchdogTriggersWithBadOther);
         assertEquals(NUM_LAST_RESORT_WATCHDOG_SUCCESSES,
-                mDeserializedWifiMetrics.numLastResortWatchdogSuccesses);
+                mDecodedProto.numLastResortWatchdogSuccesses);
         assertEquals(TEST_RECORD_DURATION_SEC,
-                mDeserializedWifiMetrics.recordDurationSec);
+                mDecodedProto.recordDurationSec);
         for (int i = 0; i < NUM_RSSI_LEVELS_TO_INCREMENT; i++) {
-            assertEquals(MIN_RSSI_LEVEL + i, mDeserializedWifiMetrics.rssiPollRssiCount[i].rssi);
-            assertEquals(i + 1, mDeserializedWifiMetrics.rssiPollRssiCount[i].count);
+            assertEquals(MIN_RSSI_LEVEL + i, mDecodedProto.rssiPollRssiCount[i].rssi);
+            assertEquals(i + 1, mDecodedProto.rssiPollRssiCount[i].count);
         }
         StringBuilder sb_rssi = new StringBuilder();
-        sb_rssi.append("Number of RSSIs = " + mDeserializedWifiMetrics.rssiPollRssiCount.length);
-        assertTrue(sb_rssi.toString(), (mDeserializedWifiMetrics.rssiPollRssiCount.length
+        sb_rssi.append("Number of RSSIs = " + mDecodedProto.rssiPollRssiCount.length);
+        assertTrue(sb_rssi.toString(), (mDecodedProto.rssiPollRssiCount.length
                      <= (MAX_RSSI_LEVEL - MIN_RSSI_LEVEL + 1)));
-        assertEquals(2, mDeserializedWifiMetrics.alertReasonCount[0].count);  // Clamped reasons.
-        assertEquals(3, mDeserializedWifiMetrics.alertReasonCount[1].count);
-        assertEquals(1, mDeserializedWifiMetrics.alertReasonCount[2].count);
-        assertEquals(3, mDeserializedWifiMetrics.alertReasonCount.length);
+        assertEquals(2, mDecodedProto.alertReasonCount[0].count);  // Clamped reasons.
+        assertEquals(3, mDecodedProto.alertReasonCount[1].count);
+        assertEquals(1, mDecodedProto.alertReasonCount[2].count);
+        assertEquals(3, mDecodedProto.alertReasonCount.length);
         assertEquals(NUM_TOTAL_SCAN_RESULTS * NUM_SCANS,
-                mDeserializedWifiMetrics.numTotalScanResults);
+                mDecodedProto.numTotalScanResults);
         assertEquals(NUM_OPEN_NETWORK_SCAN_RESULTS * NUM_SCANS,
-                mDeserializedWifiMetrics.numOpenNetworkScanResults);
+                mDecodedProto.numOpenNetworkScanResults);
         assertEquals(NUM_PERSONAL_NETWORK_SCAN_RESULTS * NUM_SCANS,
-                mDeserializedWifiMetrics.numPersonalNetworkScanResults);
+                mDecodedProto.numPersonalNetworkScanResults);
         assertEquals(NUM_ENTERPRISE_NETWORK_SCAN_RESULTS * NUM_SCANS,
-                mDeserializedWifiMetrics.numEnterpriseNetworkScanResults);
+                mDecodedProto.numEnterpriseNetworkScanResults);
         assertEquals(NUM_HIDDEN_NETWORK_SCAN_RESULTS * NUM_SCANS,
-                mDeserializedWifiMetrics.numHiddenNetworkScanResults);
+                mDecodedProto.numHiddenNetworkScanResults);
         assertEquals(NUM_HOTSPOT2_R1_NETWORK_SCAN_RESULTS * NUM_SCANS,
-                mDeserializedWifiMetrics.numHotspot2R1NetworkScanResults);
+                mDecodedProto.numHotspot2R1NetworkScanResults);
         assertEquals(NUM_HOTSPOT2_R2_NETWORK_SCAN_RESULTS * NUM_SCANS,
-                mDeserializedWifiMetrics.numHotspot2R2NetworkScanResults);
+                mDecodedProto.numHotspot2R2NetworkScanResults);
         assertEquals(NUM_SCANS,
-                mDeserializedWifiMetrics.numScans);
+                mDecodedProto.numScans);
         for (int score_index = 0; score_index < NUM_WIFI_SCORES_TO_INCREMENT; score_index++) {
             assertEquals(WIFI_SCORE_RANGE_MIN + score_index,
-                    mDeserializedWifiMetrics.wifiScoreCount[score_index].score);
+                    mDecodedProto.wifiScoreCount[score_index].score);
             assertEquals(score_index + 1,
-                    mDeserializedWifiMetrics.wifiScoreCount[score_index].count);
+                    mDecodedProto.wifiScoreCount[score_index].count);
         }
         StringBuilder sb_wifi_score = new StringBuilder();
-        sb_wifi_score.append("Number of wifi_scores = "
-                + mDeserializedWifiMetrics.wifiScoreCount.length);
-        assertTrue(sb_wifi_score.toString(), (mDeserializedWifiMetrics.wifiScoreCount.length
+        sb_wifi_score.append("Number of wifi_scores = " + mDecodedProto.wifiScoreCount.length);
+        assertTrue(sb_wifi_score.toString(), (mDecodedProto.wifiScoreCount.length
                 <= (WIFI_SCORE_RANGE_MAX - WIFI_SCORE_RANGE_MIN + 1)));
         StringBuilder sb_wifi_limits = new StringBuilder();
         sb_wifi_limits.append("Wifi Score limit is " +  NetworkAgent.WIFI_BASE_SCORE
                 + ">= " + WIFI_SCORE_RANGE_MAX);
         assertTrue(sb_wifi_limits.toString(), NetworkAgent.WIFI_BASE_SCORE <= WIFI_SCORE_RANGE_MAX);
-        assertEquals(MAX_NUM_SOFTAP_RETURN_CODES, mDeserializedWifiMetrics.softApReturnCode.length);
+        assertEquals(MAX_NUM_SOFTAP_RETURN_CODES, mDecodedProto.softApReturnCode.length);
         assertEquals(WifiMetricsProto.SoftApReturnCodeCount.SOFT_AP_STARTED_SUCCESSFULLY,
-                     mDeserializedWifiMetrics.softApReturnCode[0].startResult);
-        assertEquals(NUM_SOFTAP_START_SUCCESS, mDeserializedWifiMetrics.softApReturnCode[0].count);
+                     mDecodedProto.softApReturnCode[0].startResult);
+        assertEquals(NUM_SOFTAP_START_SUCCESS, mDecodedProto.softApReturnCode[0].count);
         assertEquals(WifiMetricsProto.SoftApReturnCodeCount.SOFT_AP_FAILED_GENERAL_ERROR,
-                     mDeserializedWifiMetrics.softApReturnCode[1].startResult);
+                     mDecodedProto.softApReturnCode[1].startResult);
         assertEquals(NUM_SOFTAP_FAILED_GENERAL_ERROR,
-                     mDeserializedWifiMetrics.softApReturnCode[1].count);
+                     mDecodedProto.softApReturnCode[1].count);
         assertEquals(WifiMetricsProto.SoftApReturnCodeCount.SOFT_AP_FAILED_NO_CHANNEL,
-                     mDeserializedWifiMetrics.softApReturnCode[2].startResult);
+                     mDecodedProto.softApReturnCode[2].startResult);
         assertEquals(NUM_SOFTAP_FAILED_NO_CHANNEL,
-                     mDeserializedWifiMetrics.softApReturnCode[2].count);
-        assertEquals(NUM_HAL_CRASHES, mDeserializedWifiMetrics.numHalCrashes);
-        assertEquals(NUM_WIFICOND_CRASHES, mDeserializedWifiMetrics.numWificondCrashes);
-        assertEquals(NUM_WIFI_ON_FAILURE_DUE_TO_HAL,
-                mDeserializedWifiMetrics.numWifiOnFailureDueToHal);
+                     mDecodedProto.softApReturnCode[2].count);
+        assertEquals(NUM_HAL_CRASHES, mDecodedProto.numHalCrashes);
+        assertEquals(NUM_WIFICOND_CRASHES, mDecodedProto.numWificondCrashes);
+        assertEquals(NUM_WIFI_ON_FAILURE_DUE_TO_HAL, mDecodedProto.numWifiOnFailureDueToHal);
         assertEquals(NUM_WIFI_ON_FAILURE_DUE_TO_WIFICOND,
-                mDeserializedWifiMetrics.numWifiOnFailureDueToWificond);
+                mDecodedProto.numWifiOnFailureDueToWificond);
+        assertEquals(NUM_PASSPOINT_PROVIDERS, mDecodedProto.numPasspointProviders);
+        assertEquals(NUM_PASSPOINT_PROVIDER_INSTALLATION,
+                mDecodedProto.numPasspointProviderInstallation);
+        assertEquals(NUM_PASSPOINT_PROVIDER_INSTALL_SUCCESS,
+                mDecodedProto.numPasspointProviderInstallSuccess);
+        assertEquals(NUM_PASSPOINT_PROVIDER_UNINSTALLATION,
+                mDecodedProto.numPasspointProviderUninstallation);
+        assertEquals(NUM_PASSPOINT_PROVIDER_UNINSTALL_SUCCESS,
+                mDecodedProto.numPasspointProviderUninstallSuccess);
+        assertEquals(NUM_PASSPOINT_PROVIDERS_SUCCESSFULLY_CONNECTED,
+                mDecodedProto.numPasspointProvidersSuccessfullyConnected);
     }
 
     /**
      *  Assert deserialized metrics Scan Return Entry equals count
      */
     public void assertScanReturnEntryEquals(int returnCode, int count) {
-        for (int i = 0; i < mDeserializedWifiMetrics.scanReturnEntries.length; i++) {
-            if (mDeserializedWifiMetrics.scanReturnEntries[i].scanReturnCode == returnCode) {
-                assertEquals(mDeserializedWifiMetrics.scanReturnEntries[i].scanResultsCount, count);
+        for (int i = 0; i < mDecodedProto.scanReturnEntries.length; i++) {
+            if (mDecodedProto.scanReturnEntries[i].scanReturnCode == returnCode) {
+                assertEquals(mDecodedProto.scanReturnEntries[i].scanResultsCount, count);
                 return;
             }
         }
@@ -580,10 +637,10 @@
      *  Assert deserialized metrics SystemState entry equals count
      */
     public void assertSystemStateEntryEquals(int state, boolean screenOn, int count) {
-        for (int i = 0; i < mDeserializedWifiMetrics.wifiSystemStateEntries.length; i++) {
-            if (mDeserializedWifiMetrics.wifiSystemStateEntries[i].wifiState == state
-                    && mDeserializedWifiMetrics.wifiSystemStateEntries[i].isScreenOn == screenOn) {
-                assertEquals(mDeserializedWifiMetrics.wifiSystemStateEntries[i].wifiStateCount,
+        for (int i = 0; i < mDecodedProto.wifiSystemStateEntries.length; i++) {
+            if (mDecodedProto.wifiSystemStateEntries[i].wifiState == state
+                    && mDecodedProto.wifiSystemStateEntries[i].isScreenOn == screenOn) {
+                assertEquals(mDecodedProto.wifiSystemStateEntries[i].wifiStateCount,
                         count);
                 return;
             }
@@ -600,8 +657,8 @@
         startAndEndConnectionEventSucceeds();
         dumpProtoAndDeserialize();
         assertDeserializedMetricsCorrect();
-        assertEquals("mDeserializedWifiMetrics.connectionEvent.length",
-                2, mDeserializedWifiMetrics.connectionEvent.length);
+        assertEquals("mDecodedProto.connectionEvent.length",
+                2, mDecodedProto.connectionEvent.length);
         //<TODO> test individual connectionEvents for correctness,
         // check scanReturnEntries & wifiSystemStateEntries counts and individual elements
         // pending their implementation</TODO>
@@ -650,19 +707,19 @@
                 WifiMetrics.ConnectionEvent.FAILURE_NONE,
                 WifiMetricsProto.ConnectionEvent.HLF_NONE);
 
-        //Dump proto from mWifiMetrics and deserialize it to mDeserializedWifiMetrics
+        //Dump proto from mWifiMetrics and deserialize it to mDecodedProto
         dumpProtoAndDeserialize();
 
         //Check that the correct values are being flowed through
-        assertEquals(mDeserializedWifiMetrics.connectionEvent.length, 2);
-        assertEquals(mDeserializedWifiMetrics.connectionEvent[0].routerFingerprint.dtim,
+        assertEquals(mDecodedProto.connectionEvent.length, 2);
+        assertEquals(mDecodedProto.connectionEvent[0].routerFingerprint.dtim,
                 CONFIG_DTIM);
-        assertEquals(mDeserializedWifiMetrics.connectionEvent[0].signalStrength, SCAN_RESULT_LEVEL);
-        assertEquals(mDeserializedWifiMetrics.connectionEvent[1].routerFingerprint.dtim,
+        assertEquals(mDecodedProto.connectionEvent[0].signalStrength, SCAN_RESULT_LEVEL);
+        assertEquals(mDecodedProto.connectionEvent[1].routerFingerprint.dtim,
                 NETWORK_DETAIL_DTIM);
-        assertEquals(mDeserializedWifiMetrics.connectionEvent[1].signalStrength,
+        assertEquals(mDecodedProto.connectionEvent[1].signalStrength,
                 SCAN_RESULT_LEVEL);
-        assertEquals(mDeserializedWifiMetrics.connectionEvent[1].routerFingerprint.routerTechnology,
+        assertEquals(mDecodedProto.connectionEvent[1].routerFingerprint.routerTechnology,
                 NETWORK_DETAIL_WIFIMODE);
     }
 
@@ -697,9 +754,9 @@
         //This should clear all the metrics in mWifiMetrics,
         dumpProtoAndDeserialize();
         //Check there are only 3 connection events
-        assertEquals(mDeserializedWifiMetrics.connectionEvent.length, 4);
-        assertEquals(mDeserializedWifiMetrics.rssiPollRssiCount.length, 0);
-        assertEquals(mDeserializedWifiMetrics.alertReasonCount.length, 0);
+        assertEquals(mDecodedProto.connectionEvent.length, 4);
+        assertEquals(mDecodedProto.rssiPollRssiCount.length, 0);
+        assertEquals(mDecodedProto.alertReasonCount.length, 0);
 
         // Create 2 ConnectionEvents
         mWifiMetrics.startConnectionEvent(null,  "BLUE",
@@ -716,7 +773,7 @@
         //Dump proto and deserialize
         dumpProtoAndDeserialize();
         //Check there are only 2 connection events
-        assertEquals(mDeserializedWifiMetrics.connectionEvent.length, 2);
+        assertEquals(mDecodedProto.connectionEvent.length, 2);
     }
 
     /**
@@ -729,8 +786,8 @@
         startAndEndConnectionEventSucceeds();
         cleanDumpProtoAndDeserialize();
         assertDeserializedMetricsCorrect();
-        assertEquals("mDeserializedWifiMetrics.connectionEvent.length",
-                2, mDeserializedWifiMetrics.connectionEvent.length);
+        assertEquals("mDecodedProto.connectionEvent.length",
+                2, mDecodedProto.connectionEvent.length);
     }
 
     private static final int NUM_REPEATED_DELTAS = 7;
@@ -758,13 +815,13 @@
         generateRssiDelta(MIN_RSSI_LEVEL, SINGLE_GOOD_DELTA,
                 WifiMetrics.TIMEOUT_RSSI_DELTA_MILLIS);
         dumpProtoAndDeserialize();
-        assertEquals(2, mDeserializedWifiMetrics.rssiPollDeltaCount.length);
+        assertEquals(2, mDecodedProto.rssiPollDeltaCount.length);
         // Check the repeated deltas
-        assertEquals(NUM_REPEATED_DELTAS, mDeserializedWifiMetrics.rssiPollDeltaCount[0].count);
-        assertEquals(REPEATED_DELTA, mDeserializedWifiMetrics.rssiPollDeltaCount[0].rssi);
+        assertEquals(NUM_REPEATED_DELTAS, mDecodedProto.rssiPollDeltaCount[0].count);
+        assertEquals(REPEATED_DELTA, mDecodedProto.rssiPollDeltaCount[0].rssi);
         // Check the single delta
-        assertEquals(1, mDeserializedWifiMetrics.rssiPollDeltaCount[1].count);
-        assertEquals(SINGLE_GOOD_DELTA, mDeserializedWifiMetrics.rssiPollDeltaCount[1].rssi);
+        assertEquals(1, mDecodedProto.rssiPollDeltaCount[1].count);
+        assertEquals(SINGLE_GOOD_DELTA, mDecodedProto.rssiPollDeltaCount[1].rssi);
     }
 
     /**
@@ -779,7 +836,7 @@
         generateRssiDelta(MIN_RSSI_LEVEL, SINGLE_TIMEOUT_DELTA,
                 WifiMetrics.TIMEOUT_RSSI_DELTA_MILLIS + 1);
         dumpProtoAndDeserialize();
-        assertEquals(0, mDeserializedWifiMetrics.rssiPollDeltaCount.length);
+        assertEquals(0, mDecodedProto.rssiPollDeltaCount.length);
     }
 
     /**
@@ -792,11 +849,11 @@
         generateRssiDelta(MAX_RSSI_LEVEL, MIN_DELTA_LEVEL,
                 WifiMetrics.TIMEOUT_RSSI_DELTA_MILLIS);
         dumpProtoAndDeserialize();
-        assertEquals(2, mDeserializedWifiMetrics.rssiPollDeltaCount.length);
-        assertEquals(MIN_DELTA_LEVEL, mDeserializedWifiMetrics.rssiPollDeltaCount[0].rssi);
-        assertEquals(1, mDeserializedWifiMetrics.rssiPollDeltaCount[0].count);
-        assertEquals(MAX_DELTA_LEVEL, mDeserializedWifiMetrics.rssiPollDeltaCount[1].rssi);
-        assertEquals(1, mDeserializedWifiMetrics.rssiPollDeltaCount[1].count);
+        assertEquals(2, mDecodedProto.rssiPollDeltaCount.length);
+        assertEquals(MIN_DELTA_LEVEL, mDecodedProto.rssiPollDeltaCount[0].rssi);
+        assertEquals(1, mDecodedProto.rssiPollDeltaCount[0].count);
+        assertEquals(MAX_DELTA_LEVEL, mDecodedProto.rssiPollDeltaCount[1].rssi);
+        assertEquals(1, mDecodedProto.rssiPollDeltaCount[1].count);
     }
 
     /**
@@ -810,7 +867,7 @@
         generateRssiDelta(MAX_RSSI_LEVEL, MIN_DELTA_LEVEL - 1,
                 WifiMetrics.TIMEOUT_RSSI_DELTA_MILLIS);
         dumpProtoAndDeserialize();
-        assertEquals(0, mDeserializedWifiMetrics.rssiPollDeltaCount.length);
+        assertEquals(0, mDecodedProto.rssiPollDeltaCount.length);
     }
 
     /**
@@ -827,7 +884,7 @@
         );
 
         dumpProtoAndDeserialize();
-        assertEquals(0, mDeserializedWifiMetrics.rssiPollDeltaCount.length);
+        assertEquals(0, mDecodedProto.rssiPollDeltaCount.length);
     }
 
     /**
@@ -843,9 +900,9 @@
                 true // dontDeserializeBeforePoll
         );
         dumpProtoAndDeserialize();
-        assertEquals(1, mDeserializedWifiMetrics.rssiPollDeltaCount.length);
-        assertEquals(ARBITRARY_DELTA_LEVEL, mDeserializedWifiMetrics.rssiPollDeltaCount[0].rssi);
-        assertEquals(1, mDeserializedWifiMetrics.rssiPollDeltaCount[0].count);
+        assertEquals(1, mDecodedProto.rssiPollDeltaCount.length);
+        assertEquals(ARBITRARY_DELTA_LEVEL, mDecodedProto.rssiPollDeltaCount[0].rssi);
+        assertEquals(1, mDecodedProto.rssiPollDeltaCount[0].count);
     }
 
     /**
@@ -861,7 +918,7 @@
                 true // dontDeserializeBeforePoll
         );
         dumpProtoAndDeserialize();
-        assertEquals(0, mDeserializedWifiMetrics.rssiPollDeltaCount.length);
+        assertEquals(0, mDecodedProto.rssiPollDeltaCount.length);
     }
 
     /**
@@ -877,7 +934,7 @@
                 false // dontDeserializeBeforePoll
         );
         dumpProtoAndDeserialize();
-        assertEquals(0, mDeserializedWifiMetrics.rssiPollDeltaCount.length);
+        assertEquals(0, mDecodedProto.rssiPollDeltaCount.length);
     }
 
     private static final int DEAUTH_REASON = 7;
@@ -1015,7 +1072,7 @@
     public void testStaEventsLogSerializeDeserialize() throws Exception {
         generateStaEvents(mWifiMetrics);
         dumpProtoAndDeserialize();
-        verifyDeserializedStaEvents(mDeserializedWifiMetrics);
+        verifyDeserializedStaEvents(mDecodedProto);
     }
 
     /**
@@ -1028,7 +1085,7 @@
             mWifiMetrics.logStaEvent(StaEvent.TYPE_CMD_START_CONNECT);
         }
         dumpProtoAndDeserialize();
-        assertEquals(WifiMetrics.MAX_STA_EVENTS, mDeserializedWifiMetrics.staEventList.length);
+        assertEquals(WifiMetrics.MAX_STA_EVENTS, mDecodedProto.staEventList.length);
     }
 
     /**
@@ -1040,6 +1097,95 @@
     }
 
     /**
+     * Test the generation of 'NumConnectableNetwork' histograms from two scans of different
+     * ScanDetails produces the correct histogram values, and relevant bounds are observed
+     */
+    @Test
+    public void testNumConnectableNetworksGeneration() throws Exception {
+        List<ScanDetail> scan = new ArrayList<ScanDetail>();
+        //                                ssid, bssid, isOpen, isSaved, isProvider, isWeakRssi)
+        scan.add(buildMockScanDetail("PASSPOINT_1", "bssid0", false, false, true, false));
+        scan.add(buildMockScanDetail("PASSPOINT_2", "bssid1", false, false, true, false));
+        scan.add(buildMockScanDetail("SSID_B", "bssid2", true, true, false, false));
+        scan.add(buildMockScanDetail("SSID_B", "bssid3", true, true, false, false));
+        scan.add(buildMockScanDetail("SSID_C", "bssid4", true, false, false, false));
+        scan.add(buildMockScanDetail("SSID_D", "bssid5", false, true, false, false));
+        scan.add(buildMockScanDetail("SSID_E", "bssid6", false, true, false, false));
+        scan.add(buildMockScanDetail("SSID_F", "bssid7", false, false, false, false));
+        scan.add(buildMockScanDetail("SSID_G_WEAK", "bssid9", false, false, false, true));
+        scan.add(buildMockScanDetail("SSID_H_WEAK", "bssid10", false, false, false, true));
+        mWifiMetrics.incrementAvailableNetworksHistograms(scan, true);
+        scan.add(buildMockScanDetail("SSID_B", "bssid8", true, true, false, false));
+        mWifiMetrics.incrementAvailableNetworksHistograms(scan, true);
+        for (int i = 0; i < NUM_PARTIAL_SCAN_RESULTS; i++) {
+            mWifiMetrics.incrementAvailableNetworksHistograms(scan, false);
+        }
+        dumpProtoAndDeserialize();
+        verifyHist(mDecodedProto.totalSsidsInScanHistogram, 1,                    a(7),    a(2));
+        verifyHist(mDecodedProto.totalBssidsInScanHistogram, 2,                   a(8, 9), a(1, 1));
+        verifyHist(mDecodedProto.availableOpenSsidsInScanHistogram, 1,            a(2),    a(2));
+        verifyHist(mDecodedProto.availableOpenBssidsInScanHistogram, 2,           a(3, 4), a(1, 1));
+        verifyHist(mDecodedProto.availableSavedSsidsInScanHistogram, 1,           a(3),    a(2));
+        verifyHist(mDecodedProto.availableSavedBssidsInScanHistogram, 2,          a(4, 5), a(1, 1));
+        verifyHist(mDecodedProto.availableOpenOrSavedSsidsInScanHistogram, 1,     a(4),    a(2));
+        verifyHist(mDecodedProto.availableOpenOrSavedBssidsInScanHistogram, 2,    a(5, 6), a(1, 1));
+        verifyHist(mDecodedProto.availableSavedPasspointProviderProfilesInScanHistogram, 1,
+                                                                                  a(2),    a(2));
+        verifyHist(mDecodedProto.availableSavedPasspointProviderBssidsInScanHistogram, 1,
+                                                                                  a(2),    a(2));
+        assertEquals(2, mDecodedProto.fullBandAllSingleScanListenerResults);
+        assertEquals(NUM_PARTIAL_SCAN_RESULTS, mDecodedProto.partialAllSingleScanListenerResults);
+
+        // Check Bounds
+        scan.clear();
+        int lotsOfSSids = Math.max(WifiMetrics.MAX_TOTAL_SCAN_RESULT_SSIDS_BUCKET,
+                WifiMetrics.MAX_CONNECTABLE_SSID_NETWORK_BUCKET) + 5;
+        for (int i = 0; i < lotsOfSSids; i++) {
+            scan.add(buildMockScanDetail("SSID_" + i, "bssid_" + i, true, true, false, false));
+        }
+        mWifiMetrics.incrementAvailableNetworksHistograms(scan, true);
+        dumpProtoAndDeserialize();
+        verifyHist(mDecodedProto.totalSsidsInScanHistogram, 1,
+                a(WifiMetrics.MAX_TOTAL_SCAN_RESULT_SSIDS_BUCKET), a(1));
+        verifyHist(mDecodedProto.availableOpenSsidsInScanHistogram, 1,
+                a(WifiMetrics.MAX_CONNECTABLE_SSID_NETWORK_BUCKET), a(1));
+        verifyHist(mDecodedProto.availableSavedSsidsInScanHistogram, 1,
+                a(WifiMetrics.MAX_CONNECTABLE_SSID_NETWORK_BUCKET), a(1));
+        verifyHist(mDecodedProto.availableOpenOrSavedSsidsInScanHistogram, 1,
+                a(WifiMetrics.MAX_CONNECTABLE_SSID_NETWORK_BUCKET), a(1));
+        scan.clear();
+        int lotsOfBssids = Math.max(WifiMetrics.MAX_TOTAL_SCAN_RESULTS_BUCKET,
+                WifiMetrics.MAX_CONNECTABLE_BSSID_NETWORK_BUCKET) + 5;
+        for (int i = 0; i < lotsOfBssids; i++) {
+            scan.add(buildMockScanDetail("SSID", "bssid_" + i, true, true, false, false));
+        }
+        mWifiMetrics.incrementAvailableNetworksHistograms(scan, true);
+        dumpProtoAndDeserialize();
+        verifyHist(mDecodedProto.totalBssidsInScanHistogram, 1,
+                a(WifiMetrics.MAX_TOTAL_SCAN_RESULTS_BUCKET), a(1));
+        verifyHist(mDecodedProto.availableOpenBssidsInScanHistogram, 1,
+                a(WifiMetrics.MAX_CONNECTABLE_BSSID_NETWORK_BUCKET), a(1));
+        verifyHist(mDecodedProto.availableSavedBssidsInScanHistogram, 1,
+                a(WifiMetrics.MAX_CONNECTABLE_BSSID_NETWORK_BUCKET), a(1));
+        verifyHist(mDecodedProto.availableOpenOrSavedBssidsInScanHistogram, 1,
+                a(WifiMetrics.MAX_CONNECTABLE_BSSID_NETWORK_BUCKET), a(1));
+    }
+
+    /** short hand for instantiating an anonymous int array, instead of 'new int[]{a1, a2, ...}' */
+    private int[] a(int... element) {
+        return element;
+    }
+
+    private void verifyHist(WifiMetricsProto.NumConnectableNetworksBucket[] hist, int size,
+            int[] keys, int[] counts) throws Exception {
+        assertEquals(size, hist.length);
+        for (int i = 0; i < keys.length; i++) {
+            assertEquals(keys[i], hist[i].numConnectableNetworks);
+            assertEquals(counts[i], hist[i].count);
+        }
+    }
+
+    /**
      * Generate an RSSI delta event by creating a connection event and an RSSI poll within
      * 'interArrivalTime' milliseconds of each other.
      * Event will not be logged if interArrivalTime > mWifiMetrics.TIMEOUT_RSSI_DELTA_MILLIS
@@ -1081,6 +1227,7 @@
         }
         mWifiMetrics.incrementRssiPollRssiCount(scanRssi + rssiDelta);
     }
+
     /**
      * Generate an RSSI delta event, with all extra conditions set to true.
      */
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiNativeTest.java b/tests/wifitests/src/com/android/server/wifi/WifiNativeTest.java
index 2f13baf..32d1daa 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiNativeTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiNativeTest.java
@@ -31,6 +31,7 @@
 import android.net.wifi.IClientInterface;
 import android.net.wifi.WifiConfiguration;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Pair;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -487,8 +488,9 @@
         IClientInterface clientInterface = mock(IClientInterface.class);
         when(mWificondControl.setupDriverForClientMode()).thenReturn(clientInterface);
 
-        IClientInterface returnedClientInterface = mWifiNative.setupForClientMode();
-        assertEquals(clientInterface, returnedClientInterface);
+        Pair<Integer, IClientInterface> statusAndClientInterface = mWifiNative.setupForClientMode();
+        assertTrue(WifiNative.SETUP_SUCCESS == statusAndClientInterface.first);
+        assertEquals(clientInterface, statusAndClientInterface.second);
         verify(mWifiVendorHal).startVendorHal(eq(true));
         verify(mWificondControl).setupDriverForClientMode();
     }
@@ -503,8 +505,9 @@
         IClientInterface clientInterface = mock(IClientInterface.class);
         when(mWificondControl.setupDriverForClientMode()).thenReturn(clientInterface);
 
-        IClientInterface returnedClientInterface = mWifiNative.setupForClientMode();
-        assertEquals(clientInterface, returnedClientInterface);
+        Pair<Integer, IClientInterface> statusAndClientInterface = mWifiNative.setupForClientMode();
+        assertTrue(WifiNative.SETUP_SUCCESS == statusAndClientInterface.first);
+        assertEquals(clientInterface, statusAndClientInterface.second);
         verify(mWifiVendorHal, never()).startVendorHal(anyBoolean());
         verify(mWificondControl).setupDriverForClientMode();
     }
@@ -517,8 +520,9 @@
     public void testSetupDriverForClientModeWificondError() {
         when(mWificondControl.setupDriverForClientMode()).thenReturn(null);
 
-        IClientInterface returnedClientInterface = mWifiNative.setupForClientMode();
-        assertEquals(null, returnedClientInterface);
+        Pair<Integer, IClientInterface> statusAndClientInterface = mWifiNative.setupForClientMode();
+        assertTrue(WifiNative.SETUP_FAILURE_WIFICOND == statusAndClientInterface.first);
+        assertEquals(null, statusAndClientInterface.second);
         verify(mWifiVendorHal).startVendorHal(eq(true));
         verify(mWificondControl).setupDriverForClientMode();
     }
@@ -530,8 +534,9 @@
     public void testSetupDriverForClientModeHalError() {
         when(mWifiVendorHal.startVendorHal(anyBoolean())).thenReturn(false);
 
-        IClientInterface returnedClientInterface = mWifiNative.setupForClientMode();
-        assertEquals(null, returnedClientInterface);
+        Pair<Integer, IClientInterface> statusAndClientInterface = mWifiNative.setupForClientMode();
+        assertTrue(WifiNative.SETUP_FAILURE_HAL == statusAndClientInterface.first);
+        assertEquals(null, statusAndClientInterface.second);
         verify(mWifiVendorHal).startVendorHal(eq(true));
         verify(mWificondControl, never()).setupDriverForClientMode();
     }
@@ -544,8 +549,9 @@
         IApInterface apInterface = mock(IApInterface.class);
         when(mWificondControl.setupDriverForSoftApMode()).thenReturn(apInterface);
 
-        IApInterface returnedApInterface = mWifiNative.setupForSoftApMode();
-        assertEquals(apInterface, returnedApInterface);
+        Pair<Integer, IApInterface> statusAndApInterface = mWifiNative.setupForSoftApMode();
+        assertTrue(WifiNative.SETUP_SUCCESS == statusAndApInterface.first);
+        assertEquals(apInterface, statusAndApInterface.second);
         verify(mWifiVendorHal).startVendorHal(eq(false));
         verify(mWificondControl).setupDriverForSoftApMode();
     }
@@ -560,8 +566,9 @@
         IApInterface apInterface = mock(IApInterface.class);
         when(mWificondControl.setupDriverForSoftApMode()).thenReturn(apInterface);
 
-        IApInterface returnedApInterface = mWifiNative.setupForSoftApMode();
-        assertEquals(apInterface, returnedApInterface);
+        Pair<Integer, IApInterface> statusAndApInterface = mWifiNative.setupForSoftApMode();
+        assertTrue(WifiNative.SETUP_SUCCESS == statusAndApInterface.first);
+        assertEquals(apInterface, statusAndApInterface.second);
         verify(mWifiVendorHal, never()).startVendorHal(anyBoolean());
         verify(mWificondControl).setupDriverForSoftApMode();
     }
@@ -573,9 +580,11 @@
     @Test
     public void testSetupDriverForSoftApModeWificondError() {
         when(mWificondControl.setupDriverForSoftApMode()).thenReturn(null);
-        IApInterface returnedApInterface = mWifiNative.setupForSoftApMode();
 
-        assertEquals(null, returnedApInterface);
+        Pair<Integer, IApInterface> statusAndApInterface = mWifiNative.setupForSoftApMode();
+        assertTrue(WifiNative.SETUP_FAILURE_WIFICOND == statusAndApInterface.first);
+        assertEquals(null, statusAndApInterface.second);
+
         verify(mWifiVendorHal).startVendorHal(eq(false));
         verify(mWificondControl).setupDriverForSoftApMode();
     }
@@ -587,8 +596,10 @@
     public void testSetupDriverForSoftApModeHalError() {
         when(mWifiVendorHal.startVendorHal(anyBoolean())).thenReturn(false);
 
-        IApInterface returnedApInterface = mWifiNative.setupForSoftApMode();
-        assertEquals(null, returnedApInterface);
+        Pair<Integer, IApInterface> statusAndApInterface = mWifiNative.setupForSoftApMode();
+        assertTrue(WifiNative.SETUP_FAILURE_HAL == statusAndApInterface.first);
+        assertEquals(null, statusAndApInterface.second);
+
         verify(mWifiVendorHal).startVendorHal(eq(false));
         verify(mWificondControl, never()).setupDriverForSoftApMode();
     }
@@ -616,13 +627,27 @@
     }
 
     /**
-     * Verifies that tearDownInterfaces() calls underlying WificondControl.
+     * Verifies that tearDownInterfaces() calls underlying WificondControl and WifiVendorHal
+     * methods.
      */
     @Test
     public void testTearDown() {
         when(mWificondControl.tearDownInterfaces()).thenReturn(true);
 
-        assertTrue(mWifiNative.tearDown());
+        mWifiNative.tearDown();
+        verify(mWificondControl).tearDownInterfaces();
+        verify(mWifiVendorHal).stopVendorHal();
+    }
+
+    /**
+     * Verifies that tearDownInterfaces() calls underlying WificondControl and WifiVendorHal
+     * methods even if wificond returns an error.
+     */
+    @Test
+    public void testTearDownWificondError() {
+        when(mWificondControl.tearDownInterfaces()).thenReturn(false);
+
+        mWifiNative.tearDown();
         verify(mWificondControl).tearDownInterfaces();
         verify(mWifiVendorHal).stopVendorHal();
     }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTest.java b/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTest.java
index 5eae11e..4965a35 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTest.java
@@ -42,6 +42,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 
@@ -64,6 +65,7 @@
         mWifiNetworkSelector = new WifiNetworkSelector(mContext, mWifiConfigManager, mClock,
                 mLocalLog);
         mWifiNetworkSelector.registerNetworkEvaluator(mDummyEvaluator, 1);
+        mDummyEvaluator.setEvaluatorToSelectCandidate(true);
         when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime());
 
         mThresholdMinimumRssi2G = mResource.getInteger(
@@ -74,6 +76,10 @@
                 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(
@@ -93,6 +99,8 @@
     public class DummyNetworkEvaluator implements WifiNetworkSelector.NetworkEvaluator {
         private static final String NAME = "DummyNetworkEvaluator";
 
+        private boolean mEvaluatorShouldSelectCandidate = true;
+
         @Override
         public String getName() {
             return NAME;
@@ -102,20 +110,34 @@
         public void update(List<ScanDetail> scanDetails) {}
 
         /**
-         * Always return the first network in the scan results for connection.
+         * Sets whether the evaluator should return a candidate for connection or null.
+         */
+        public void setEvaluatorToSelectCandidate(boolean shouldSelectCandidate) {
+            mEvaluatorShouldSelectCandidate = shouldSelectCandidate;
+        }
+
+        /**
+         * This NetworkEvaluator can be configured to return a candidate or null.  If returning a
+         * candidate, the first entry in the provided scanDetails will be selected. This requires
+         * that the mock WifiConfigManager be set up to return a WifiConfiguration for the first
+         * scanDetail entry, through
+         * {@link WifiNetworkSelectorTestUtil#setupScanDetailsAndConfigStore}.
          */
         @Override
         public WifiConfiguration evaluateNetworks(List<ScanDetail> scanDetails,
                     WifiConfiguration currentNetwork, String currentBssid, boolean connected,
                     boolean untrustedNetworkAllowed,
                     List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks) {
+            if (!mEvaluatorShouldSelectCandidate) {
+                return null;
+            }
             ScanDetail scanDetail = scanDetails.get(0);
             mWifiConfigManager.setNetworkCandidateScanResult(0, scanDetail.getScanResult(), 100);
 
             assertNotNull("Saved network must not be null",
-                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(scanDetail));
+                    mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(scanDetail));
 
-            return mWifiConfigManager.getSavedNetworkForScanDetailAndCache(scanDetail);
+            return mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(scanDetail);
         }
     }
 
@@ -131,6 +153,8 @@
     private int mThresholdMinimumRssi5G;
     private int mThresholdQualifiedRssi2G;
     private int mThresholdQualifiedRssi5G;
+    private int mStayOnNetworkMinimumTxRate;
+    private int mStayOnNetworkMinimumRxRate;
     private int mThresholdSaturatedRssi2G;
     private int mThresholdSaturatedRssi5G;
 
@@ -154,6 +178,12 @@
                 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(
@@ -200,6 +230,7 @@
         WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(scanDetails,
                 blacklist, mWifiInfo, false, true, false);
         assertEquals("Expect null configuration", null, candidate);
+        assertTrue(mWifiNetworkSelector.getConnectableScanDetails().isEmpty());
     }
 
 
@@ -229,6 +260,7 @@
         WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(scanDetails,
                 blacklist, mWifiInfo, false, true, false);
         assertEquals("Expect null configuration", null, candidate);
+        assertTrue(mWifiNetworkSelector.getConnectableScanDetails().isEmpty());
     }
 
     /**
@@ -267,6 +299,7 @@
                 blacklist, mWifiInfo, true, false, false);
 
         assertEquals("Expect null configuration", null, candidate);
+        assertTrue(mWifiNetworkSelector.getConnectableScanDetails().isEmpty());
     }
 
     /**
@@ -431,6 +464,7 @@
         WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(scanDetails,
                 blacklist, mWifiInfo, false, true, false);
         assertEquals("Expect null configuration", null, candidate);
+        assertTrue(mWifiNetworkSelector.getConnectableScanDetails().isEmpty());
     }
 
     /**
@@ -483,6 +517,7 @@
         // The second network selection is skipped since current connected network is
         // missing from the scan results.
         assertEquals("Expect null configuration", null, candidate);
+        assertTrue(mWifiNetworkSelector.getConnectableScanDetails().isEmpty());
     }
 
     /**
@@ -592,9 +627,19 @@
      */
     @Test
     public void test2GhzQualifiedNo5GhzAvailable() {
+        // Rssi after connected.
+        when(mWifiInfo.getRssi()).thenReturn(mThresholdQualifiedRssi2G + 1);
+        // No streaming traffic.
+        when(mWifiInfo.getTxSuccessRatePps()).thenReturn(0.0);
+        when(mWifiInfo.getRxSuccessRatePps()).thenReturn(0.0);
+
         // Do not perform selection on 2GHz if current network is good and no 5GHz available
-        testStayOrSwitch(mThresholdQualifiedRssi2G, false,
-                mThresholdQualifiedRssi2G + 10, false, false);
+        testStayOrTryToSwitch(
+                mThresholdQualifiedRssi2G + 1 /* rssi before connected */,
+                false /* not a 5G network */,
+                false /* not open network */,
+                // Should not try to switch.
+                false);
     }
 
     /**
@@ -609,24 +654,24 @@
      */
     @Test
     public void test2GhzHighQuality5GhzAvailable() {
-        // When on 2GHz, even with "good" signal strength, run selection if 5GHz available
-        testStayOrSwitch(mThresholdQualifiedRssi2G, false,
-                mThresholdQualifiedRssi5G - 1, true, true);
-    }
+        // Rssi after connected.
+        when(mWifiInfo.getRssi()).thenReturn(mThresholdQualifiedRssi2G + 1);
+        // No streaming traffic.
+        when(mWifiInfo.getTxSuccessRatePps()).thenReturn(0.0);
+        when(mWifiInfo.getRxSuccessRatePps()).thenReturn(0.0);
 
-    /**
-     * Wifi network selector performs network selection on 2Ghz networks if the current network
-     * is not of high quality.
-     *
-     * WifiStateMachine is under connected state and 2.4GHz test1 is connected.
-     *
-     * Expected behavior: network selection is performed
-     */
-    @Test
-    public void test2GhzNotQualifiedOther2GhzAvailable() {
-        // Run Selection on 2Ghz networks when not qualified even if no 5GHz available
-        testStayOrSwitch(mThresholdQualifiedRssi2G - 1, false,
-                mThresholdQualifiedRssi2G - 10, false, true);
+        // When on 2GHz, even with "good" signal strength, run selection if 5GHz available
+        testStayOrTryToSwitch(
+                // Parameters for network1:
+                mThresholdQualifiedRssi2G + 1 /* rssi before connected */,
+                false /* not a 5G network */,
+                false /* not open network */,
+                // Parameters for network2:
+                mThresholdQualifiedRssi5G + 1 /* rssi */,
+                true /* a 5G network */,
+                false /* not open network */,
+                // Should try to switch.
+                true);
     }
 
     /**
@@ -639,10 +684,19 @@
      */
     @Test
     public void test5GhzNotQualifiedLowRssi() {
-        // Run Selection when the current 5Ghz network has low RSSI, regardless of what may
-        // be available. The second scan result is irrelevant.
-        testStayOrSwitch(mThresholdQualifiedRssi5G - 1, true,
-                mThresholdQualifiedRssi2G - 1, false, true);
+        // Rssi after connected.
+        when(mWifiInfo.getRssi()).thenReturn(mThresholdQualifiedRssi5G - 1);
+        // No streaming traffic.
+        when(mWifiInfo.getTxSuccessRatePps()).thenReturn(0.0);
+        when(mWifiInfo.getRxSuccessRatePps()).thenReturn(0.0);
+
+        // Run Selection when the current 5Ghz network has low RSSI.
+        testStayOrTryToSwitch(
+                mThresholdQualifiedRssi5G + 1 /* rssi before connected */,
+                true /* a 5G network */,
+                false /* not open network */,
+                // Should try to switch.
+                true);
     }
 
     /**
@@ -655,9 +709,162 @@
      */
     @Test
     public void test5GhzQualified() {
+        // Rssi after connected.
+        when(mWifiInfo.getRssi()).thenReturn(mThresholdQualifiedRssi5G + 1);
+        // No streaming traffic.
+        when(mWifiInfo.getTxSuccessRatePps()).thenReturn(0.0);
+        when(mWifiInfo.getRxSuccessRatePps()).thenReturn(0.0);
+
         // Connected to a high quality 5Ghz network, so the other result is irrelevant
-        testStayOrSwitch(mThresholdQualifiedRssi5G, true,
-                mThresholdQualifiedRssi5G + 10, true, false);
+        testStayOrTryToSwitch(
+                mThresholdQualifiedRssi5G + 1 /* rssi before connected */,
+                true /* a 5G network */,
+                false /* not open network */,
+                // Should not try to switch.
+                false);
+    }
+
+    /**
+     * New network selection is performed if the currently connected network
+     * band is 2G and there is no sign of streaming traffic.
+     *
+     * Expected behavior: Network Selector perform network selection after connected
+     * to the first one.
+     */
+    @Test
+    public void band2GNetworkIsNotSufficientWhenNoOngoingTrafficAnd5GhzAvailable() {
+        // Rssi after connected.
+        when(mWifiInfo.getRssi()).thenReturn(mThresholdQualifiedRssi2G + 1);
+        // No streaming traffic.
+        when(mWifiInfo.getTxSuccessRatePps()).thenReturn(0.0);
+        when(mWifiInfo.getRxSuccessRatePps()).thenReturn(0.0);
+
+        testStayOrTryToSwitch(
+                // Parameters for network1:
+                mThresholdQualifiedRssi2G + 1 /* rssi before connected */,
+                false /* not a 5G network */,
+                false /* not open network */,
+                // Parameters for network2:
+                mThresholdQualifiedRssi5G + 1 /* rssi */,
+                true /* a 5G network */,
+                false /* not open network */,
+                // Should try to switch.
+                true);
+    }
+
+    /**
+     * New network selection is performed if the currently connected network
+     * band is 2G with bad rssi.
+     *
+     * Expected behavior: Network Selector perform network selection after connected
+     * to the first one.
+     */
+    @Test
+    public void band2GNetworkIsNotSufficientWithBadRssi() {
+        // Rssi after connected.
+        when(mWifiInfo.getRssi()).thenReturn(mThresholdQualifiedRssi2G - 1);
+        // No streaming traffic.
+        when(mWifiInfo.getTxSuccessRatePps()).thenReturn(0.0);
+        when(mWifiInfo.getRxSuccessRatePps()).thenReturn(0.0);
+
+        testStayOrTryToSwitch(
+                mThresholdQualifiedRssi2G + 1 /* rssi before connected */,
+                false /* not a 5G network */,
+                false /* not open network */,
+                // Should try to switch.
+                true);
+    }
+
+    /**
+     * New network selection is not performed if the currently connected 2G network
+     * has good Rssi and sign of streaming tx traffic.
+     *
+     * Expected behavior: Network selector does not perform network selection.
+     */
+    @Test
+    public void band2GNetworkIsSufficientWhenOnGoingTxTrafficCombinedWithGoodRssi() {
+        // Rssi after connected.
+        when(mWifiInfo.getRssi()).thenReturn(mThresholdQualifiedRssi2G + 1);
+        // Streaming traffic
+        when(mWifiInfo.getTxSuccessRatePps()).thenReturn(
+                (double) (mStayOnNetworkMinimumTxRate + 1));
+        when(mWifiInfo.getRxSuccessRatePps()).thenReturn(0.0);
+
+        testStayOrTryToSwitch(
+                mThresholdQualifiedRssi2G + 1 /* rssi before connected */,
+                false /* not a 5G network */,
+                true /* open network */,
+                // Should not try to switch.
+                false);
+    }
+
+    /**
+     * New network selection is not performed if the currently connected 2G network
+     * has good Rssi and sign of streaming rx traffic.
+     *
+     * Expected behavior: Network selector does not perform network selection.
+     */
+    @Test
+    public void band2GNetworkIsSufficientWhenOnGoingRxTrafficCombinedWithGoodRssi() {
+        // Rssi after connected.
+        when(mWifiInfo.getRssi()).thenReturn(mThresholdQualifiedRssi2G + 1);
+        // Streaming traffic
+        when(mWifiInfo.getTxSuccessRatePps()).thenReturn(0.0);
+        when(mWifiInfo.getRxSuccessRatePps()).thenReturn(
+                (double) (mStayOnNetworkMinimumRxRate + 1));
+
+        testStayOrTryToSwitch(
+                mThresholdQualifiedRssi2G + 1 /* rssi before connected */,
+                false /* not a 5G network */,
+                true /* open network */,
+                // Should not try to switch.
+                false);
+    }
+
+    /**
+     * New network selection is not performed if the currently connected 5G network
+     * has good Rssi and sign of streaming tx traffic.
+     *
+     * Expected behavior: Network selector does not perform network selection.
+     */
+    @Test
+    public void band5GNetworkIsSufficientWhenOnGoingTxTrafficCombinedWithGoodRssi() {
+        // Rssi after connected.
+        when(mWifiInfo.getRssi()).thenReturn(mThresholdQualifiedRssi5G + 1);
+        // Streaming traffic
+        when(mWifiInfo.getTxSuccessRatePps()).thenReturn(
+                (double) (mStayOnNetworkMinimumTxRate + 1));
+        when(mWifiInfo.getRxSuccessRatePps()).thenReturn(0.0);
+
+        testStayOrTryToSwitch(
+                mThresholdQualifiedRssi5G + 1 /* rssi before connected */,
+                true /* a 5G network */,
+                true /* open network */,
+                // Should not try to switch.
+                false);
+    }
+
+    /**
+     * New network selection is not performed if the currently connected 5G network
+     * has good Rssi and sign of streaming rx traffic.
+     *
+     * Expected behavior: Network selector does not perform network selection.
+     */
+    @Test
+    public void band5GNetworkIsSufficientWhenOnGoingRxTrafficCombinedWithGoodRssi() {
+        // Rssi after connected.
+        when(mWifiInfo.getRssi()).thenReturn(mThresholdQualifiedRssi5G + 1);
+        // Streaming traffic
+        when(mWifiInfo.getTxSuccessRatePps()).thenReturn(0.0);
+        when(mWifiInfo.getRxSuccessRatePps()).thenReturn(
+                (double) (mStayOnNetworkMinimumRxRate + 1));
+
+        testStayOrTryToSwitch(
+                mThresholdQualifiedRssi5G + 1 /* rssi before connected */,
+                true /* a 5G network */,
+                true /* open network */,
+                // Should not try to switch.
+                false);
     }
 
     /**
@@ -667,45 +874,207 @@
      * It sets up two networks, connects to the first, and then ensures that
      * both are available in the scan results for the NetworkSelector.
      */
-    private void testStayOrSwitch(int levelNetwork1, boolean is5GHzNetwork1,
-                int levelNetwork2, boolean is5GHzNetwork2, boolean shouldSelect) {
-        // Create an updated ScanDetails that includes a new network
+    private void testStayOrTryToSwitch(
+            int rssiNetwork1, boolean is5GHzNetwork1, boolean isOpenNetwork1,
+            int rssiNetwork2, boolean is5GHzNetwork2, boolean isOpenNetwork2,
+            boolean shouldSelect) {
         String[] ssids = {"\"test1\"", "\"test2\""};
         String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
-        int[] freqs = new int[2];
-        freqs[0] = is5GHzNetwork1 ? 5180 : 2437;
-        freqs[1] = is5GHzNetwork2 ? 5180 : 2437;
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
-        int[] levels = {levelNetwork1, levelNetwork2};
-        int[] securities = {SECURITY_PSK, SECURITY_PSK};
+        int[] freqs = {is5GHzNetwork1 ? 5180 : 2437, is5GHzNetwork2 ? 5180 : 2437};
+        String[] caps = {isOpenNetwork1 ? "[ESS]" : "[WPA2-EAP-CCMP][ESS]",
+                         isOpenNetwork2 ? "[ESS]" : "[WPA2-EAP-CCMP][ESS]"};
+        int[] levels = {rssiNetwork1, rssiNetwork2};
+        int[] securities = {isOpenNetwork1 ? SECURITY_NONE : SECURITY_PSK,
+                            isOpenNetwork2 ? SECURITY_NONE : SECURITY_PSK};
+        testStayOrTryToSwitchImpl(ssids, bssids, freqs, caps, levels, securities, shouldSelect);
+    }
 
+    /**
+     * This is a meta-test that given one scan results, will
+     * determine whether or not network selection should be performed.
+     *
+     * It sets up two networks, connects to the first, and then ensures that
+     * the scan results for the NetworkSelector.
+     */
+    private void testStayOrTryToSwitch(
+            int rssi, boolean is5GHz, boolean isOpenNetwork,
+            boolean shouldSelect) {
+        String[] ssids = {"\"test1\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3"};
+        int[] freqs = {is5GHz ? 5180 : 2437};
+        String[] caps = {isOpenNetwork ? "[ESS]" : "[WPA2-EAP-CCMP][ESS]"};
+        int[] levels = {rssi};
+        int[] securities = {isOpenNetwork ? SECURITY_NONE : SECURITY_PSK};
+        testStayOrTryToSwitchImpl(ssids, bssids, freqs, caps, levels, securities, shouldSelect);
+    }
+
+    private void testStayOrTryToSwitchImpl(String[] ssids, String[] bssids, int[] freqs,
+            String[] caps, int[] levels, int[] securities,
+            boolean shouldSelect) {
         // Make a network selection to connect to test1.
         ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
                 WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
-                    freqs, caps, levels, securities, mWifiConfigManager, mClock);
+                        freqs, caps, levels, securities, mWifiConfigManager, mClock);
         List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
         HashSet<String> blacklist = new HashSet<String>();
-        WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(scanDetails,
-                blacklist, mWifiInfo, false, true, false);
+        // DummyNetworkEvaluator always return the first network in the scan results
+        // for connection, so this should connect to the first network.
+        WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(
+                scanDetails,
+                blacklist, mWifiInfo, false, true, true);
+        assertNotNull("Result should be not null", candidate);
+        WifiNetworkSelectorTestUtil.verifySelectedScanResult(mWifiConfigManager,
+                scanDetails.get(0).getScanResult(), candidate);
 
         when(mWifiInfo.getNetworkId()).thenReturn(0);
         when(mWifiInfo.getBSSID()).thenReturn(bssids[0]);
-        when(mWifiInfo.is24GHz()).thenReturn(!is5GHzNetwork1);
-        when(mWifiInfo.is5GHz()).thenReturn(is5GHzNetwork1);
-        when(mWifiInfo.getRssi()).thenReturn(levels[0]);
+        when(mWifiInfo.is24GHz()).thenReturn(!ScanResult.is5GHz(freqs[0]));
+        when(mWifiInfo.is5GHz()).thenReturn(ScanResult.is5GHz(freqs[0]));
+
         when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime()
                 + WifiNetworkSelector.MINIMUM_NETWORK_SELECTION_INTERVAL_MS + 2000);
 
         candidate = mWifiNetworkSelector.selectNetwork(scanDetails, blacklist, mWifiInfo,
                 true, false, false);
 
-        if (!shouldSelect) {
-            assertEquals("Expect null configuration", null, candidate);
-        } else {
+        // DummyNetworkEvaluator always return the first network in the scan results
+        // for connection, so if nework selection is performed, the first network should
+        // be returned as candidate.
+        if (shouldSelect) {
             assertNotNull("Result should be not null", candidate);
-            ScanResult expectedResult = scanDetails.get(0).getScanResult();
             WifiNetworkSelectorTestUtil.verifySelectedScanResult(mWifiConfigManager,
-                    expectedResult, candidate);
+                    scanDetails.get(0).getScanResult(), candidate);
+        } else {
+            assertEquals("Expect null configuration", null, candidate);
         }
     }
+
+    /**
+     * {@link WifiNetworkSelector#getFilteredScanDetailsForOpenUnsavedNetworks()} should filter out
+     * networks that are not open after network selection is made.
+     *
+     * Expected behavior: return open networks only
+     */
+    @Test
+    public void getfilterOpenUnsavedNetworks_filtersForOpenNetworks() {
+        String[] ssids = {"\"test1\"", "\"test2\""};
+        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};
+        mDummyEvaluator.setEvaluatorToSelectCandidate(false);
+
+        List<ScanDetail> scanDetails = WifiNetworkSelectorTestUtil.buildScanDetails(
+                ssids, bssids, freqs, caps, levels, mClock);
+        HashSet<String> blacklist = new HashSet<>();
+
+        mWifiNetworkSelector.selectNetwork(scanDetails, blacklist, mWifiInfo, false, true, false);
+        List<ScanDetail> expectedOpenUnsavedNetworks = new ArrayList<>();
+        expectedOpenUnsavedNetworks.add(scanDetails.get(1));
+        assertEquals("Expect open unsaved networks",
+                expectedOpenUnsavedNetworks,
+                mWifiNetworkSelector.getFilteredScanDetailsForOpenUnsavedNetworks());
+    }
+
+    /**
+     * {@link WifiNetworkSelector#getFilteredScanDetailsForOpenUnsavedNetworks()} should filter out
+     * saved networks after network selection is made. This should return an empty list when there
+     * are no unsaved networks available.
+     *
+     * Expected behavior: return unsaved networks only. Return empty list if there are no unsaved
+     * networks.
+     */
+    @Test
+    public void getfilterOpenUnsavedNetworks_filtersOutSavedNetworks() {
+        String[] ssids = {"\"test1\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3"};
+        int[] freqs = {2437, 5180};
+        String[] caps = {"[ESS]"};
+        int[] levels = {mThresholdMinimumRssi2G + 1};
+        int[] securities = {SECURITY_NONE};
+        mDummyEvaluator.setEvaluatorToSelectCandidate(false);
+
+        List<ScanDetail> unSavedScanDetails = WifiNetworkSelectorTestUtil.buildScanDetails(
+                ssids, bssids, freqs, caps, levels, mClock);
+        HashSet<String> blacklist = new HashSet<>();
+
+        mWifiNetworkSelector.selectNetwork(
+                unSavedScanDetails, blacklist, mWifiInfo, false, true, false);
+        assertEquals("Expect open unsaved networks",
+                unSavedScanDetails,
+                mWifiNetworkSelector.getFilteredScanDetailsForOpenUnsavedNetworks());
+
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
+                WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
+                        freqs, caps, levels, securities, mWifiConfigManager, mClock);
+        List<ScanDetail> savedScanDetails = scanDetailsAndConfigs.getScanDetails();
+
+        mWifiNetworkSelector.selectNetwork(
+                savedScanDetails, blacklist, mWifiInfo, false, true, false);
+        // Saved networks are filtered out.
+        assertTrue(mWifiNetworkSelector.getFilteredScanDetailsForOpenUnsavedNetworks().isEmpty());
+    }
+
+    /**
+     * {@link WifiNetworkSelector#getFilteredScanDetailsForOpenUnsavedNetworks()} should filter out
+     * bssid blacklisted networks.
+     *
+     * Expected behavior: do not return blacklisted network
+     */
+    @Test
+    public void getfilterOpenUnsavedNetworks_filtersOutBlacklistedNetworks() {
+        String[] ssids = {"\"test1\"", "\"test2\""};
+        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};
+        mDummyEvaluator.setEvaluatorToSelectCandidate(false);
+
+        List<ScanDetail> scanDetails = WifiNetworkSelectorTestUtil.buildScanDetails(
+                ssids, bssids, freqs, caps, levels, mClock);
+        HashSet<String> blacklist = new HashSet<>();
+        blacklist.add(bssids[0]);
+
+        mWifiNetworkSelector.selectNetwork(scanDetails, blacklist, mWifiInfo, false, true, false);
+        List<ScanDetail> expectedOpenUnsavedNetworks = new ArrayList<>();
+        expectedOpenUnsavedNetworks.add(scanDetails.get(1));
+        assertEquals("Expect open unsaved networks",
+                expectedOpenUnsavedNetworks,
+                mWifiNetworkSelector.getFilteredScanDetailsForOpenUnsavedNetworks());
+    }
+
+    /**
+     * {@link WifiNetworkSelector#getFilteredScanDetailsForOpenUnsavedNetworks()} should return
+     * empty list when there are no open networks after network selection is made.
+     *
+     * Expected behavior: return empty list
+     */
+    @Test
+    public void getfilterOpenUnsavedNetworks_returnsEmptyListWhenNoOpenNetworksPresent() {
+        String[] ssids = {"\"test1\"", "\"test2\""};
+        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};
+        mDummyEvaluator.setEvaluatorToSelectCandidate(false);
+
+        List<ScanDetail> scanDetails = WifiNetworkSelectorTestUtil.buildScanDetails(
+                ssids, bssids, freqs, caps, levels, mClock);
+        HashSet<String> blacklist = new HashSet<>();
+
+        mWifiNetworkSelector.selectNetwork(scanDetails, blacklist, mWifiInfo, false, true, false);
+        assertTrue(mWifiNetworkSelector.getFilteredScanDetailsForOpenUnsavedNetworks().isEmpty());
+    }
+
+    /**
+     * {@link WifiNetworkSelector#getFilteredScanDetailsForOpenUnsavedNetworks()} should return
+     * empty list when no network selection has been made.
+     *
+     * Expected behavior: return empty list
+     */
+    @Test
+    public void getfilterOpenUnsavedNetworks_returnsEmptyListWhenNoNetworkSelectionMade() {
+        assertTrue(mWifiNetworkSelector.getFilteredScanDetailsForOpenUnsavedNetworks().isEmpty());
+    }
 }
+
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTestUtil.java b/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTestUtil.java
index 9c78c9b..66507f5 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTestUtil.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTestUtil.java
@@ -292,19 +292,19 @@
         if (scanDetails.size() <= configs.length) {
             for (int i = 0; i < scanDetails.size(); i++) {
                 ScanDetail scanDetail = scanDetails.get(i);
-                when(wifiConfigManager.getSavedNetworkForScanDetailAndCache(eq(scanDetail)))
+                when(wifiConfigManager.getConfiguredNetworkForScanDetailAndCache(eq(scanDetail)))
                         .thenReturn(configs[i]);
             }
         } else {
             for (int i = 0; i < configs.length; i++) {
                 ScanDetail scanDetail = scanDetails.get(i);
-                when(wifiConfigManager.getSavedNetworkForScanDetailAndCache(eq(scanDetail)))
+                when(wifiConfigManager.getConfiguredNetworkForScanDetailAndCache(eq(scanDetail)))
                         .thenReturn(configs[i]);
             }
 
             // associated the remaining scan details with a NULL config.
             for (int i = configs.length; i < scanDetails.size(); i++) {
-                when(wifiConfigManager.getSavedNetworkForScanDetailAndCache(
+                when(wifiConfigManager.getConfiguredNetworkForScanDetailAndCache(
                         eq(scanDetails.get(i)))).thenReturn(null);
             }
         }
@@ -370,7 +370,7 @@
         config.networkId = networkId;
         config.meteredHint = meteredHint;
 
-        when(wifiConfigManager.getSavedNetworkForScanDetailAndCache(eq(scanDetail)))
+        when(wifiConfigManager.getConfiguredNetworkForScanDetailAndCache(eq(scanDetail)))
                 .thenReturn(new WifiConfiguration(config));
         when(wifiConfigManager.getConfiguredNetwork(eq(networkId)))
                 .then(new AnswerWithArguments() {
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiNotificationControllerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiNotificationControllerTest.java
index f7b3bf6..27055a8 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiNotificationControllerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiNotificationControllerTest.java
@@ -16,7 +16,7 @@
 
 package com.android.server.wifi;
 
-import static org.mockito.Mockito.any;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -25,23 +25,16 @@
 
 import android.app.Notification;
 import android.app.NotificationManager;
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.IntentFilter;
 import android.content.res.Resources;
-import android.net.NetworkInfo;
 import android.net.wifi.ScanResult;
-import android.net.wifi.WifiManager;
-import android.net.wifi.WifiScanner;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.test.TestLooper;
 import android.provider.Settings;
-import android.test.suitebuilder.annotation.SmallTest;
 
 import org.junit.Before;
 import org.junit.Test;
-import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -49,26 +42,17 @@
 import java.util.List;
 
 /**
- * Unit tests for {@link com.android.server.wifi.WifiScanningServiceImpl}.
+ * Unit tests for {@link WifiNotificationController}.
  */
-@SmallTest
 public class WifiNotificationControllerTest {
-    public static final String TAG = "WifiScanningServiceTest";
 
     @Mock private Context mContext;
     @Mock private Resources mResources;
     @Mock private FrameworkFacade mFrameworkFacade;
     @Mock private NotificationManager mNotificationManager;
     @Mock private UserManager mUserManager;
-    @Mock private WifiInjector mWifiInjector;
-    @Mock private WifiScanner mWifiScanner;
-    WifiNotificationController mWifiNotificationController;
+    private WifiNotificationController mNotificationController;
 
-    /**
-     * Internal BroadcastReceiver that WifiNotificationController uses to listen for broadcasts
-     * this is initialized by calling startServiceAndLoadDriver
-     */
-    BroadcastReceiver mBroadcastReceiver;
 
     /** Initialize objects before each test run. */
     @Before
@@ -76,86 +60,149 @@
         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);
 
-        when(mWifiInjector.getWifiScanner()).thenReturn(mWifiScanner);
-
         TestLooper mock_looper = new TestLooper();
-        mWifiNotificationController = new WifiNotificationController(
+        mNotificationController = new WifiNotificationController(
                 mContext, mock_looper.getLooper(), mFrameworkFacade,
-                mock(Notification.Builder.class), mWifiInjector);
-        ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
-                ArgumentCaptor.forClass(BroadcastReceiver.class);
-
-        verify(mContext)
-                .registerReceiver(broadcastReceiverCaptor.capture(), any(IntentFilter.class));
-        mBroadcastReceiver = broadcastReceiverCaptor.getValue();
+                mock(Notification.Builder.class));
+        mNotificationController.handleScreenStateChanged(true);
     }
 
-    private void setOpenAccessPoint() {
-        List<ScanResult> scanResults = new ArrayList<>();
+    private List<ScanDetail> createOpenScanResults() {
+        List<ScanDetail> scanResults = new ArrayList<>();
         ScanResult scanResult = new ScanResult();
         scanResult.capabilities = "[ESS]";
-        scanResults.add(scanResult);
-        when(mWifiScanner.getSingleScanResults()).thenReturn(scanResults);
+        scanResults.add(new ScanDetail(scanResult, null /* networkDetail */));
+        return scanResults;
     }
 
-    /** Verifies that a notification is displayed (and retracted) given system events. */
+    /**
+     * When scan results with open networks are handled, a notification is posted.
+     */
     @Test
-    public void verifyNotificationDisplayed() throws Exception {
-        TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext, WifiManager.WIFI_STATE_ENABLED);
-        TestUtil.sendNetworkStateChanged(mBroadcastReceiver, mContext,
-                NetworkInfo.DetailedState.DISCONNECTED);
-        setOpenAccessPoint();
+    public void handleScanResults_hasOpenNetworks_notificationDisplayed() {
+        mNotificationController.handleScanResults(createOpenScanResults());
 
-        // The notification should not be displayed after only two scan results.
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-        verify(mNotificationManager, never())
-                .notifyAsUser(any(), anyInt(), any(), any(UserHandle.class));
-
-        // Changing to and from "SCANNING" state should not affect the counter.
-        TestUtil.sendNetworkStateChanged(mBroadcastReceiver, mContext,
-                NetworkInfo.DetailedState.SCANNING);
-        TestUtil.sendNetworkStateChanged(mBroadcastReceiver, mContext,
-                NetworkInfo.DetailedState.DISCONNECTED);
-
-        // The third scan result notification will trigger the notification.
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-        verify(mNotificationManager)
-                .notifyAsUser(any(), anyInt(), any(), any(UserHandle.class));
-        verify(mNotificationManager, never())
-                .cancelAsUser(any(), anyInt(), any(UserHandle.class));
-
-        // Changing network state should cause the notification to go away.
-        TestUtil.sendNetworkStateChanged(mBroadcastReceiver, mContext,
-                NetworkInfo.DetailedState.CONNECTED);
-        verify(mNotificationManager)
-                .cancelAsUser(any(), anyInt(), any(UserHandle.class));
+        verify(mNotificationManager).notifyAsUser(any(), anyInt(), any(), any());
     }
 
+    /**
+     * When scan results with no open networks are handled, a notification is not posted.
+     */
     @Test
-    public void verifyNotificationNotDisplayed_userHasDisallowConfigWifiRestriction() {
+    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);
 
-        TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext, WifiManager.WIFI_STATE_ENABLED);
-        TestUtil.sendNetworkStateChanged(mBroadcastReceiver, mContext,
-                NetworkInfo.DetailedState.DISCONNECTED);
-        setOpenAccessPoint();
+        mNotificationController.handleScanResults(createOpenScanResults());
 
-        // The notification should be displayed after three scan results.
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
+        verify(mNotificationManager, never()).notifyAsUser(any(), anyInt(), any(), any());
+    }
 
-        verify(mNotificationManager, never())
-                .notifyAsUser(any(), anyInt(), any(), any(UserHandle.class));
+    /** 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 41f14dd..24d3afa 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiScoreReportTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiScoreReportTest.java
@@ -17,8 +17,10 @@
 package com.android.server.wifi;
 
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.atMost;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -36,6 +38,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.io.PrintWriter;
 import java.util.Arrays;
 
 /**
@@ -49,11 +52,13 @@
     WifiScoreReport mWifiScoreReport;
     ScanDetailCache mScanDetailCache;
     WifiInfo mWifiInfo;
+    int mAggr; // Aggressive handover
     @Mock Context mContext;
     @Mock NetworkAgent mNetworkAgent;
     @Mock Resources mResources;
     @Mock WifiConfigManager mWifiConfigManager;
     @Mock WifiMetrics mWifiMetrics;
+    @Mock PrintWriter mPrintWriter;
 
     /**
      * Sets up resource values for testing
@@ -106,6 +111,7 @@
         config.hiddenSSID = false;
         mWifiInfo = new WifiInfo();
         mWifiInfo.setFrequency(2412);
+        mAggr = 0;
         when(mWifiConfigManager.getSavedNetworks()).thenReturn(Arrays.asList(config));
         when(mWifiConfigManager.getConfiguredNetwork(anyInt())).thenReturn(config);
         mWifiConfiguration = config;
@@ -116,7 +122,7 @@
         when(mWifiConfigManager.getScanDetailCacheForNetwork(anyInt()))
                 .thenReturn(mScanDetailCache);
         when(mContext.getResources()).thenReturn(mResources);
-        mWifiScoreReport = new WifiScoreReport(mContext, mWifiConfigManager);
+        mWifiScoreReport = new WifiScoreReport(mContext, mWifiConfigManager, new Clock());
     }
 
     /**
@@ -138,10 +144,9 @@
      */
     @Test
     public void calculateAndReportScoreSucceeds() throws Exception {
-        int aggressiveHandover = 0;
         mWifiInfo.setRssi(-77);
         mWifiScoreReport.calculateAndReportScore(mWifiInfo,
-                mNetworkAgent, aggressiveHandover, mWifiMetrics);
+                mNetworkAgent, mAggr, mWifiMetrics);
         verify(mNetworkAgent).sendNetworkScore(anyInt());
         verify(mWifiMetrics).incrementWifiScoreCount(anyInt());
     }
@@ -155,7 +160,7 @@
     public void networkAgentMayBeNull() throws Exception {
         mWifiInfo.setRssi(-33);
         mWifiScoreReport.enableVerboseLogging(true);
-        mWifiScoreReport.calculateAndReportScore(mWifiInfo, null, 0, mWifiMetrics);
+        mWifiScoreReport.calculateAndReportScore(mWifiInfo, null, mAggr, mWifiMetrics);
         verify(mWifiMetrics).incrementWifiScoreCount(anyInt());
     }
 
@@ -174,7 +179,7 @@
         mWifiInfo.txSuccessRate = 5.1; // proportional to pps
         mWifiInfo.rxSuccessRate = 5.1;
         for (int i = 0; i < 10; i++) {
-            mWifiScoreReport.calculateAndReportScore(mWifiInfo, mNetworkAgent, 0, mWifiMetrics);
+            mWifiScoreReport.calculateAndReportScore(mWifiInfo, mNetworkAgent, mAggr, mWifiMetrics);
         }
         int score = mWifiInfo.score;
         assertTrue(score > CELLULAR_THRESHOLD_SCORE);
@@ -197,10 +202,89 @@
         mWifiInfo.txSuccessRate = 0.1;
         mWifiInfo.rxSuccessRate = 0.1;
         for (int i = 0; i < 10; i++) {
-            mWifiScoreReport.calculateAndReportScore(mWifiInfo, mNetworkAgent, 0, mWifiMetrics);
+            mWifiScoreReport.calculateAndReportScore(mWifiInfo, mNetworkAgent, mAggr, mWifiMetrics);
         }
         int score = mWifiInfo.score;
         assertTrue(score < CELLULAR_THRESHOLD_SCORE);
         verify(mNetworkAgent, atLeast(1)).sendNetworkScore(score);
     }
+
+    /**
+     * Test reporting with aggressive handover
+     */
+    @Test
+    public void calculateAndReportScoreSucceedsAggressively() throws Exception {
+        mAggr = 1;
+        mWifiInfo.setRssi(-77);
+        mWifiScoreReport.calculateAndReportScore(mWifiInfo, mNetworkAgent, mAggr, mWifiMetrics);
+        verify(mNetworkAgent).sendNetworkScore(anyInt());
+        verify(mWifiMetrics).incrementWifiScoreCount(anyInt());
+    }
+
+    /**
+     * Test low rssi with aggressive handover
+     */
+    @Test
+    public void giveUpOnBadRssiAggressively() throws Exception {
+        mAggr = 1;
+        mWifiInfo.setRssi(-83);
+        mWifiScoreReport.calculateAndReportScore(mWifiInfo, mNetworkAgent, mAggr, mWifiMetrics);
+        int score = mWifiInfo.score;
+        verify(mNetworkAgent, atLeast(1)).sendNetworkScore(score);
+        assertTrue(score < CELLULAR_THRESHOLD_SCORE);
+    }
+
+    /**
+     * Test high rssi with aggressive handover
+     */
+    @Test
+    public void allowGoodRssiAggressively() throws Exception {
+        mAggr = 1;
+        mWifiInfo.setRssi(-65);
+        mWifiScoreReport.calculateAndReportScore(mWifiInfo, mNetworkAgent, mAggr, mWifiMetrics);
+        int score = mWifiInfo.score;
+        verify(mNetworkAgent, atLeast(1)).sendNetworkScore(score);
+        assertTrue(score > CELLULAR_THRESHOLD_SCORE);
+    }
+
+    /**
+     * Test data logging
+     */
+    @Test
+    public void testDataLogging() throws Exception {
+        mAggr = 1;
+        for (int i = 0; i < 10; i++) {
+            mWifiInfo.setRssi(-65 + i);
+            mWifiInfo.setLinkSpeed(300);
+            mWifiInfo.setFrequency(5220);
+            mWifiInfo.txSuccessRate = 0.1 + i;
+            mWifiInfo.txRetriesRate = 0.2 + i;
+            mWifiInfo.txBadRate = 0.01 * i;
+            mWifiInfo.rxSuccessRate = 0.3 + i;
+            mWifiScoreReport.calculateAndReportScore(mWifiInfo, mNetworkAgent, mAggr, mWifiMetrics);
+        }
+        mWifiScoreReport.dump(null, mPrintWriter, null);
+        verify(mPrintWriter, atLeast(11)).println(anyString());
+    }
+
+    /**
+     *  Test data logging limit
+     *  <p>
+     *  Check that only a bounded amount of data is collected for dumpsys report
+     */
+    @Test
+    public void testDataLoggingLimit() throws Exception {
+        for (int i = 0; i < 14500; i++) {
+            mWifiInfo.setRssi(-65 + i % 20);
+            mWifiInfo.setLinkSpeed(300);
+            mWifiInfo.setFrequency(5220);
+            mWifiInfo.txSuccessRate = 0.1 + i % 100;
+            mWifiInfo.txRetriesRate = 0.2 + i % 100;
+            mWifiInfo.txBadRate = 0.0001 * i;
+            mWifiInfo.rxSuccessRate = 0.3 + i % 200;
+            mWifiScoreReport.calculateAndReportScore(mWifiInfo, mNetworkAgent, mAggr, mWifiMetrics);
+        }
+        mWifiScoreReport.dump(null, mPrintWriter, null);
+        verify(mPrintWriter, atMost(14401)).println(anyString());
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java b/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java
index d5bfb20..055050d 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java
@@ -155,12 +155,12 @@
     @Mock WifiPermissionsUtil mWifiPermissionsUtil;
     @Mock WifiSettingsStore mSettingsStore;
     @Mock ContentResolver mContentResolver;
+    @Mock PackageManager mPackageManager;
     @Mock UserManager mUserManager;
     @Mock WifiConfiguration mApConfig;
     @Mock ActivityManager mActivityManager;
     @Mock AppOpsManager mAppOpsManager;
     @Mock IBinder mAppBinder;
-    @Mock WifiNotificationController mWifiNotificationController;
     @Mock LocalOnlyHotspotRequestInfo mRequestInfo;
     @Mock LocalOnlyHotspotRequestInfo mRequestInfo2;
 
@@ -217,6 +217,14 @@
             };
             mChannel.connect(null, handler, messenger);
         }
+
+        private Message sendMessageSynchronously(Message request) {
+            return mChannel.sendMessageSynchronously(request);
+        }
+
+        private void sendMessage(Message request) {
+            mChannel.sendMessage(request);
+        }
     }
 
     @Before public void setUp() {
@@ -236,6 +244,7 @@
         when(mHandlerThread.getLooper()).thenReturn(mLooper.getLooper());
         when(mContext.getResources()).thenReturn(mResources);
         when(mContext.getContentResolver()).thenReturn(mContentResolver);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
         doNothing().when(mFrameworkFacade).registerContentObserver(eq(mContext), any(),
                 anyBoolean(), any());
         when(mContext.getSystemService(Context.ACTIVITY_SERVICE)).thenReturn(mActivityManager);
@@ -268,18 +277,11 @@
         when(mWifiInjector.getWifiPermissionsUtil()).thenReturn(mWifiPermissionsUtil);
         when(mWifiInjector.getWifiSettingsStore()).thenReturn(mSettingsStore);
         when(mWifiInjector.getClock()).thenReturn(mClock);
-        when(mWifiInjector.getWifiNotificationController()).thenReturn(mWifiNotificationController);
         mWifiServiceImpl = new WifiServiceImpl(mContext, mWifiInjector, mAsyncChannel);
         mWifiServiceImpl.setWifiHandlerLogForTest(mLog);
     }
 
-    @Test
-    public void testRemoveNetworkUnknown() {
-        assertFalse(mWifiServiceImpl.removeNetwork(-1));
-    }
-
-    @Test
-    public void testAsyncChannelHalfConnected() {
+    private WifiAsyncChannelTester verifyAsyncChannelHalfConnected() {
         WifiAsyncChannelTester channelTester = new WifiAsyncChannelTester(mWifiInjector);
         Handler handler = mock(Handler.class);
         TestLooper looper = new TestLooper();
@@ -289,6 +291,26 @@
         assertEquals("AsyncChannel must be half connected",
                 WifiAsyncChannelTester.CHANNEL_STATE_HALF_CONNECTED,
                 channelTester.getChannelState());
+        return channelTester;
+    }
+
+    /**
+     * Verifies that any operations on WifiServiceImpl without setting up the WifiStateMachine
+     * channel would fail.
+     */
+    @Test
+    public void testRemoveNetworkUnknown() {
+        assertFalse(mWifiServiceImpl.removeNetwork(-1));
+        verify(mWifiStateMachine, never()).syncRemoveNetwork(any(), anyInt());
+    }
+
+    /**
+     * Tests whether we're able to set up an async channel connection with WifiServiceImpl.
+     * This is the path used by some WifiManager public API calls.
+     */
+    @Test
+    public void testAsyncChannelHalfConnected() {
+        verifyAsyncChannelHalfConnected();
     }
 
     /**
@@ -340,6 +362,7 @@
     public void testSetWifiEnabledSuccess() throws Exception {
         when(mWifiStateMachine.syncGetWifiApState()).thenReturn(WifiManager.WIFI_AP_STATE_DISABLED);
         when(mSettingsStore.handleWifiToggled(eq(true))).thenReturn(true);
+        when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
         assertTrue(mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, true));
         verify(mWifiController).sendMessage(eq(CMD_WIFI_TOGGLED));
     }
@@ -364,21 +387,47 @@
         doThrow(new SecurityException()).when(mContext)
                 .enforceCallingOrSelfPermission(eq(android.Manifest.permission.CHANGE_WIFI_STATE),
                                                 eq("WifiService"));
+        when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
         mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, true);
         verify(mWifiStateMachine, never()).syncGetWifiApState();
     }
 
     /**
      * Verify that a call from an app with the NETWORK_SETTINGS permission can enable wifi if we
+     * are in airplane mode.
+     */
+    @Test
+    public void testSetWifiEnabledFromNetworkSettingsHolderWhenInAirplaneMode() throws Exception {
+        when(mSettingsStore.handleWifiToggled(eq(true))).thenReturn(true);
+        when(mSettingsStore.isAirplaneModeOn()).thenReturn(true);
+        when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(true);
+        assertTrue(mWifiServiceImpl.setWifiEnabled(SYSUI_PACKAGE_NAME, true));
+        verify(mWifiController).sendMessage(eq(CMD_WIFI_TOGGLED));
+    }
+
+    /**
+     * Verify that a caller without the NETWORK_SETTINGS permission can't enable wifi
+     * if we are in airplane mode.
+     */
+    @Test
+    public void testSetWifiEnabledFromAppFailsWhenInAirplaneMode() throws Exception {
+        when(mSettingsStore.handleWifiToggled(eq(true))).thenReturn(true);
+        when(mSettingsStore.isAirplaneModeOn()).thenReturn(true);
+        when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(false);
+        assertFalse(mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, true));
+        verify(mWifiController, never()).sendMessage(eq(CMD_WIFI_TOGGLED));
+    }
+
+    /**
+     * Verify that a call from an app with the NETWORK_SETTINGS permission can enable wifi if we
      * are in softap mode.
      */
     @Test
     public void testSetWifiEnabledFromNetworkSettingsHolderWhenApEnabled() throws Exception {
         when(mWifiStateMachine.syncGetWifiApState()).thenReturn(WifiManager.WIFI_AP_STATE_ENABLED);
         when(mSettingsStore.handleWifiToggled(eq(true))).thenReturn(true);
-        when(mContext.checkCallingOrSelfPermission(
-                eq(android.Manifest.permission.NETWORK_SETTINGS)))
-                        .thenReturn(PackageManager.PERMISSION_GRANTED);
+        when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(true);
+        when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
         assertTrue(mWifiServiceImpl.setWifiEnabled(SYSUI_PACKAGE_NAME, true));
         verify(mWifiController).sendMessage(eq(CMD_WIFI_TOGGLED));
     }
@@ -389,9 +438,8 @@
     @Test
     public void testSetWifiEnabledFromAppFailsWhenApEnabled() throws Exception {
         when(mWifiStateMachine.syncGetWifiApState()).thenReturn(WifiManager.WIFI_AP_STATE_ENABLED);
-        when(mContext.checkCallingOrSelfPermission(
-                eq(android.Manifest.permission.NETWORK_SETTINGS)))
-                        .thenReturn(PackageManager.PERMISSION_DENIED);
+        when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(false);
+        when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
         assertFalse(mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, true));
         verify(mSettingsStore, never()).handleWifiToggled(anyBoolean());
         verify(mWifiController, never()).sendMessage(eq(CMD_WIFI_TOGGLED));
@@ -1524,8 +1572,10 @@
     public void testAddPasspointProfileViaAddNetwork() throws Exception {
         WifiConfiguration config = WifiConfigurationTestUtil.createPasspointNetwork();
         config.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
-        when(mResources.getBoolean(com.android.internal.R.bool.config_wifi_hotspot2_enabled))
-                .thenReturn(true);
+
+        PackageManager pm = mock(PackageManager.class);
+        when(pm.hasSystemFeature(PackageManager.FEATURE_WIFI_PASSPOINT)).thenReturn(true);
+        when(mContext.getPackageManager()).thenReturn(pm);
 
         when(mWifiStateMachine.syncAddOrUpdatePasspointConfig(any(),
                 any(PasspointConfiguration.class), anyInt())).thenReturn(true);
@@ -1580,4 +1630,183 @@
         mWifiServiceImpl.retrieveBackupData();
         verify(mWifiBackupRestore, never()).retrieveBackupDataFromConfigurations(any(List.class));
     }
+
+    /**
+     * Helper to test handling of async messages by wifi service when the message comes from an
+     * app without {@link android.Manifest.permission#CHANGE_WIFI_STATE} permission.
+     */
+    private void verifyAsyncChannelMessageHandlingWithoutChangePermisson(
+            int requestMsgWhat, int expectedReplyMsgwhat) {
+        WifiAsyncChannelTester tester = verifyAsyncChannelHalfConnected();
+
+        int uidWithoutPermission = 5;
+        when(mWifiPermissionsUtil.checkChangePermission(eq(uidWithoutPermission)))
+                .thenReturn(false);
+
+        Message request = Message.obtain();
+        request.what = requestMsgWhat;
+        request.sendingUid = uidWithoutPermission;
+
+        mLooper.startAutoDispatch();
+        Message reply = tester.sendMessageSynchronously(request);
+        mLooper.stopAutoDispatch();
+
+        verify(mWifiStateMachine, never()).sendMessage(any(Message.class));
+        assertEquals(expectedReplyMsgwhat, reply.what);
+        assertEquals(WifiManager.NOT_AUTHORIZED, reply.arg1);
+    }
+
+    /**
+     * Verify that the CONNECT_NETWORK message received from an app without
+     * {@link android.Manifest.permission#CHANGE_WIFI_STATE} permission is rejected with the correct
+     * error code.
+     */
+    @Test
+    public void testConnectNetworkWithoutChangePermission() throws Exception {
+        verifyAsyncChannelMessageHandlingWithoutChangePermisson(
+                WifiManager.CONNECT_NETWORK, WifiManager.CONNECT_NETWORK_FAILED);
+    }
+
+    /**
+     * Verify that the FORGET_NETWORK message received from an app without
+     * {@link android.Manifest.permission#CHANGE_WIFI_STATE} permission is rejected with the correct
+     * error code.
+     */
+    @Test
+    public void testForgetNetworkWithoutChangePermission() throws Exception {
+        verifyAsyncChannelMessageHandlingWithoutChangePermisson(
+                WifiManager.SAVE_NETWORK, WifiManager.SAVE_NETWORK_FAILED);
+    }
+
+    /**
+     * Verify that the START_WPS message received from an app without
+     * {@link android.Manifest.permission#CHANGE_WIFI_STATE} permission is rejected with the correct
+     * error code.
+     */
+    @Test
+    public void testStartWpsWithoutChangePermission() throws Exception {
+        verifyAsyncChannelMessageHandlingWithoutChangePermisson(
+                WifiManager.START_WPS, WifiManager.WPS_FAILED);
+    }
+
+    /**
+     * Verify that the CANCEL_WPS message received from an app without
+     * {@link android.Manifest.permission#CHANGE_WIFI_STATE} permission is rejected with the correct
+     * error code.
+     */
+    @Test
+    public void testCancelWpsWithoutChangePermission() throws Exception {
+        verifyAsyncChannelMessageHandlingWithoutChangePermisson(
+                WifiManager.CANCEL_WPS, WifiManager.CANCEL_WPS_FAILED);
+    }
+
+    /**
+     * Verify that the DISABLE_NETWORK message received from an app without
+     * {@link android.Manifest.permission#CHANGE_WIFI_STATE} permission is rejected with the correct
+     * error code.
+     */
+    @Test
+    public void testDisableNetworkWithoutChangePermission() throws Exception {
+        verifyAsyncChannelMessageHandlingWithoutChangePermisson(
+                WifiManager.DISABLE_NETWORK, WifiManager.DISABLE_NETWORK_FAILED);
+    }
+
+    /**
+     * Verify that the RSSI_PKTCNT_FETCH message received from an app without
+     * {@link android.Manifest.permission#CHANGE_WIFI_STATE} permission is rejected with the correct
+     * error code.
+     */
+    @Test
+    public void testRssiPktcntFetchWithoutChangePermission() throws Exception {
+        verifyAsyncChannelMessageHandlingWithoutChangePermisson(
+                WifiManager.RSSI_PKTCNT_FETCH, WifiManager.RSSI_PKTCNT_FETCH_FAILED);
+    }
+
+    /**
+     * Helper to test handling of async messages by wifi service when the message comes from an
+     * app with {@link android.Manifest.permission#CHANGE_WIFI_STATE} permission.
+     */
+    private void verifyAsyncChannelMessageHandlingWithChangePermisson(
+            int requestMsgWhat, Object requestMsgObj) {
+        WifiAsyncChannelTester tester = verifyAsyncChannelHalfConnected();
+
+        when(mWifiPermissionsUtil.checkChangePermission(anyInt())).thenReturn(true);
+
+        Message request = Message.obtain();
+        request.what = requestMsgWhat;
+        request.obj = requestMsgObj;
+
+        tester.sendMessage(request);
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mWifiStateMachine).sendMessage(messageArgumentCaptor.capture());
+        assertEquals(requestMsgWhat, messageArgumentCaptor.getValue().what);
+    }
+
+    /**
+     * Verify that the CONNECT_NETWORK message received from an app with
+     * {@link android.Manifest.permission#CHANGE_WIFI_STATE} permission is forwarded to
+     * WifiStateMachine.
+     */
+    @Test
+    public void testConnectNetworkWithChangePermission() throws Exception {
+        verifyAsyncChannelMessageHandlingWithChangePermisson(
+                WifiManager.CONNECT_NETWORK, new WifiConfiguration());
+    }
+
+    /**
+     * Verify that the SAVE_NETWORK message received from an app with
+     * {@link android.Manifest.permission#CHANGE_WIFI_STATE} permission is forwarded to
+     * WifiStateMachine.
+     */
+    @Test
+    public void testSaveNetworkWithChangePermission() throws Exception {
+        verifyAsyncChannelMessageHandlingWithChangePermisson(
+                WifiManager.SAVE_NETWORK, new WifiConfiguration());
+    }
+
+    /**
+     * Verify that the START_WPS message received from an app with
+     * {@link android.Manifest.permission#CHANGE_WIFI_STATE} permission is forwarded to
+     * WifiStateMachine.
+     */
+    @Test
+    public void testStartWpsWithChangePermission() throws Exception {
+        verifyAsyncChannelMessageHandlingWithChangePermisson(
+                WifiManager.START_WPS, new Object());
+    }
+
+    /**
+     * Verify that the CANCEL_WPS message received from an app with
+     * {@link android.Manifest.permission#CHANGE_WIFI_STATE} permission is forwarded to
+     * WifiStateMachine.
+     */
+    @Test
+    public void testCancelWpsWithChangePermission() throws Exception {
+        verifyAsyncChannelMessageHandlingWithChangePermisson(
+                WifiManager.CANCEL_WPS, new Object());
+    }
+
+    /**
+     * Verify that the DISABLE_NETWORK message received from an app with
+     * {@link android.Manifest.permission#CHANGE_WIFI_STATE} permission is forwarded to
+     * WifiStateMachine.
+     */
+    @Test
+    public void testDisableNetworkWithChangePermission() throws Exception {
+        verifyAsyncChannelMessageHandlingWithChangePermisson(
+                WifiManager.DISABLE_NETWORK, new Object());
+    }
+
+    /**
+     * Verify that the RSSI_PKTCNT_FETCH message received from an app with
+     * {@link android.Manifest.permission#CHANGE_WIFI_STATE} permission is forwarded to
+     * WifiStateMachine.
+     */
+    @Test
+    public void testRssiPktcntFetchWithChangePermission() throws Exception {
+        verifyAsyncChannelMessageHandlingWithChangePermisson(
+                WifiManager.RSSI_PKTCNT_FETCH, new Object());
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java b/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java
index 2a30b67..929a5fe 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.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.*;
@@ -41,7 +42,6 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
-import android.content.res.Resources;
 import android.net.ConnectivityManager;
 import android.net.DhcpResults;
 import android.net.LinkProperties;
@@ -80,10 +80,13 @@
 import android.os.test.TestLooper;
 import android.provider.Settings;
 import android.security.KeyStore;
+import android.telephony.PhoneStateListener;
+import android.telephony.TelephonyManager;
 import android.test.mock.MockContentProvider;
 import android.test.mock.MockContentResolver;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
+import android.util.Pair;
 import android.util.SparseArray;
 
 import com.android.internal.R;
@@ -130,6 +133,8 @@
             (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 TEST_RSSI = -54;
     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\"";
@@ -153,32 +158,6 @@
         mWsm.enableVerboseLogging(1);
     }
 
-    private class TestIpManager extends IpManager {
-        TestIpManager(Context context, String ifname, IpManager.Callback callback) {
-            // Call dependency-injection superclass constructor.
-            super(context, ifname, callback, mock(INetworkManagementService.class));
-        }
-
-        @Override
-        public void startProvisioning(IpManager.ProvisioningConfiguration config) {}
-
-        @Override
-        public void stop() {}
-
-        @Override
-        public void confirmConfiguration() {}
-
-        void injectDhcpSuccess(DhcpResults dhcpResults) {
-            mCallback.onNewDhcpResults(dhcpResults);
-            mCallback.onProvisioningSuccess(new LinkProperties());
-        }
-
-        void injectDhcpFailure() {
-            mCallback.onNewDhcpResults(null);
-            mCallback.onProvisioningFailure(new LinkProperties());
-        }
-    }
-
     private FrameworkFacade getFrameworkFacade() throws Exception {
         FrameworkFacade facade = mock(FrameworkFacade.class);
 
@@ -212,8 +191,8 @@
                 .then(new AnswerWithArguments() {
                     public IpManager answer(
                             Context context, String ifname, IpManager.Callback callback) {
-                        mTestIpManager = new TestIpManager(context, ifname, callback);
-                        return mTestIpManager;
+                        mIpManagerCallback = callback;
+                        return mIpManager;
                     }
                 });
 
@@ -227,9 +206,6 @@
         Context context = mock(Context.class);
         when(context.getPackageManager()).thenReturn(pkgMgr);
 
-        MockResources resources = new com.android.server.wifi.MockResources();
-        when(context.getResources()).thenReturn(resources);
-
         MockContentResolver mockContentResolver = new MockContentResolver();
         mockContentResolver.addProvider(Settings.AUTHORITY,
                 new MockContentProvider(context) {
@@ -253,9 +229,11 @@
         return context;
     }
 
-    private Resources getMockResources() {
+    private MockResources getMockResources() {
         MockResources resources = new MockResources();
         resources.setBoolean(R.bool.config_wifi_enable_wifi_firmware_debugging, false);
+        resources.setBoolean(
+                R.bool.config_wifi_framework_enable_voice_call_sar_tx_power_limit, false);
         return resources;
     }
 
@@ -290,11 +268,11 @@
         Log.d(TAG, "WifiStateMachine state -" + stream.toString());
     }
 
-    private static ScanDetail getGoogleGuestScanDetail(int rssi) {
+    private static ScanDetail getGoogleGuestScanDetail(int rssi, String bssid, int freq) {
         ScanResult.InformationElement ie[] = new ScanResult.InformationElement[1];
         ie[0] = ScanResults.generateSsidIe(sSSID);
         NetworkDetail nd = new NetworkDetail(sBSSID, ie, new ArrayList<String>(), sFreq);
-        ScanDetail detail = new ScanDetail(nd, sWifiSsid, sBSSID, "", rssi, sFreq,
+        ScanDetail detail = new ScanDetail(nd, sWifiSsid, bssid, "", rssi, freq,
                 Long.MAX_VALUE, /* needed so that scan results aren't rejected because
                                    there older than scan start */
                 ie, new ArrayList<String>());
@@ -305,16 +283,26 @@
         ScanResults sr = ScanResults.create(0, 2412, 2437, 2462, 5180, 5220, 5745, 5825);
         ArrayList<ScanDetail> list = sr.getScanDetailArrayList();
 
-        int rssi = -65;
-        list.add(getGoogleGuestScanDetail(rssi));
+        list.add(getGoogleGuestScanDetail(TEST_RSSI, sBSSID, sFreq));
         return list;
     }
 
+    private void injectDhcpSuccess(DhcpResults dhcpResults) {
+        mIpManagerCallback.onNewDhcpResults(dhcpResults);
+        mIpManagerCallback.onProvisioningSuccess(new LinkProperties());
+    }
+
+    private void injectDhcpFailure() {
+        mIpManagerCallback.onNewDhcpResults(null);
+        mIpManagerCallback.onProvisioningFailure(new LinkProperties());
+    }
+
     static final String   sSSID = "\"GoogleGuest\"";
     static final WifiSsid sWifiSsid = WifiSsid.createFromAsciiEncoded(sSSID);
-    static final String   sHexSSID = sWifiSsid.getHexString().replace("0x", "").replace("22", "");
     static final String   sBSSID = "01:02:03:04:05:06";
+    static final String   sBSSID1 = "02:01:04:03:06:05";
     static final int      sFreq = 2437;
+    static final int      sFreq1 = 5240;
     static final String   WIFI_IFACE_NAME = "mockWlan";
 
     WifiStateMachine mWsm;
@@ -324,9 +312,12 @@
     AsyncChannel  mWsmAsyncChannel;
     TestAlarmManager mAlarmManager;
     MockWifiMonitor mWifiMonitor;
-    TestIpManager mTestIpManager;
     TestLooper mLooper;
     Context mContext;
+    MockResources mResources;
+    FrameworkFacade mFrameworkFacade;
+    IpManager.Callback mIpManagerCallback;
+    PhoneStateListener mPhoneStateListener;
 
     final ArgumentCaptor<SoftApManager.Listener> mSoftApManagerListenerCaptor =
                     ArgumentCaptor.forClass(SoftApManager.Listener.class);
@@ -354,6 +345,11 @@
     @Mock WifiStateTracker mWifiStateTracker;
     @Mock PasspointManager mPasspointManager;
     @Mock SelfRecovery mSelfRecovery;
+    @Mock IpManager mIpManager;
+    @Mock TelephonyManager mTelephonyManager;
+    @Mock WrongPasswordNotifier mWrongPasswordNotifier;
+    @Mock Clock mClock;
+    @Mock ScanDetailCache mScanDetailCache;
 
     public WifiStateMachineTest() throws Exception {
     }
@@ -394,9 +390,13 @@
         when(mWifiInjector.getWifiMonitor()).thenReturn(mWifiMonitor);
         when(mWifiInjector.getWifiNative()).thenReturn(mWifiNative);
         when(mWifiInjector.getSelfRecovery()).thenReturn(mSelfRecovery);
+        when(mWifiInjector.makeTelephonyManager()).thenReturn(mTelephonyManager);
+        when(mWifiInjector.getClock()).thenReturn(mClock);
 
-        when(mWifiNative.setupForClientMode()).thenReturn(mClientInterface);
-        when(mWifiNative.setupForSoftApMode()).thenReturn(mApInterface);
+        when(mWifiNative.setupForClientMode())
+                .thenReturn(Pair.create(WifiNative.SETUP_SUCCESS, mClientInterface));
+        when(mWifiNative.setupForSoftApMode())
+                .thenReturn(Pair.create(WifiNative.SETUP_SUCCESS, mApInterface));
         when(mApInterface.getInterfaceName()).thenReturn(WIFI_IFACE_NAME);
         when(mWifiNative.getInterfaceName()).thenReturn(WIFI_IFACE_NAME);
         when(mWifiNative.enableSupplicant()).thenReturn(true);
@@ -404,21 +404,21 @@
         when(mWifiNative.getFrameworkNetworkId(anyInt())).thenReturn(0);
 
 
-        FrameworkFacade factory = getFrameworkFacade();
+        mFrameworkFacade = getFrameworkFacade();
         mContext = getContext();
 
-        Resources resources = getMockResources();
-        when(mContext.getResources()).thenReturn(resources);
+        mResources = getMockResources();
+        when(mContext.getResources()).thenReturn(mResources);
 
-        when(factory.getIntegerSetting(mContext,
+        when(mFrameworkFacade.getIntegerSetting(mContext,
                 Settings.Global.WIFI_FREQUENCY_BAND,
                 WifiManager.WIFI_FREQUENCY_BAND_AUTO)).thenReturn(
                 WifiManager.WIFI_FREQUENCY_BAND_AUTO);
 
-        when(factory.makeApConfigStore(eq(mContext), eq(mBackupManagerProxy)))
+        when(mFrameworkFacade.makeApConfigStore(eq(mContext), eq(mBackupManagerProxy)))
                 .thenReturn(mApConfigStore);
 
-        when(factory.makeSupplicantStateTracker(
+        when(mFrameworkFacade.makeSupplicantStateTracker(
                 any(Context.class), any(WifiConfigManager.class),
                 any(Handler.class))).thenReturn(mSupplicantStateTracker);
 
@@ -431,8 +431,20 @@
         when(mApInterface.asBinder()).thenReturn(mApInterfaceBinder);
         when(mClientInterface.asBinder()).thenReturn(mClientInterfaceBinder);
 
-        mWsm = new WifiStateMachine(mContext, factory, mLooper.getLooper(),
-            mUserManager, mWifiInjector, mBackupManagerProxy, mCountryCode, mWifiNative);
+        doAnswer(new AnswerWithArguments() {
+            public void answer(PhoneStateListener phoneStateListener, int events)
+                    throws Exception {
+                mPhoneStateListener = phoneStateListener;
+            }
+        }).when(mTelephonyManager).listen(any(PhoneStateListener.class), anyInt());
+
+        initializeWsm();
+    }
+
+    private void initializeWsm() throws Exception {
+        mWsm = new WifiStateMachine(mContext, mFrameworkFacade, mLooper.getLooper(),
+                mUserManager, mWifiInjector, mBackupManagerProxy, mCountryCode, mWifiNative,
+                mWrongPasswordNotifier);
         mWsmThread = getWsmHandlerThread(mWsm);
 
         final AsyncChannel channel = new AsyncChannel();
@@ -459,6 +471,10 @@
         /* Now channel is supposed to be connected */
 
         mBinderToken = Binder.clearCallingIdentity();
+
+        /* Send the BOOT_COMPLETED message to setup some WSM state. */
+        mWsm.sendMessage(WifiStateMachine.CMD_BOOT_COMPLETED);
+        mLooper.dispatchAll();
     }
 
     @After
@@ -517,6 +533,7 @@
 
         assertEquals("SoftApState", getCurrentState().getName());
 
+        verify(mWifiNative).setupForSoftApMode();
         verify(mSoftApManager).start();
 
         // reset expectations for mContext due to previously sent AP broadcast
@@ -796,10 +813,14 @@
 
         mWsm.sendMessage(WifiMonitor.SUP_CONNECTION_EVENT);
         mLooper.dispatchAll();
+
+        verify(mWifiNative).setupForClientMode();
+        verify(mWifiLastResortWatchdog).clearAllFailureCounts();
     }
 
     private void addNetworkAndVerifySuccess(boolean isHidden) throws Exception {
         WifiConfiguration config = new WifiConfiguration();
+        config.networkId = FRAMEWORK_NETWORK_ID;
         config.SSID = sSSID;
         config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         config.hiddenSSID = isHidden;
@@ -932,6 +953,13 @@
 
         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.get(sBSSID)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, sBSSID, sFreq).getScanResult());
 
         mWsm.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, sBSSID);
         mLooper.dispatchAll();
@@ -948,9 +976,15 @@
         dhcpResults.addDns("8.8.8.8");
         dhcpResults.setLeaseDuration(3600);
 
-        mTestIpManager.injectDhcpSuccess(dhcpResults);
+        injectDhcpSuccess(dhcpResults);
         mLooper.dispatchAll();
 
+        WifiInfo wifiInfo = mWsm.getWifiInfo();
+        assertNotNull(wifiInfo);
+        assertEquals(sBSSID, wifiInfo.getBSSID());
+        assertEquals(sFreq, wifiInfo.getFrequency());
+        assertTrue(sWifiSsid.equals(wifiInfo.getWifiSsid()));
+
         assertEquals("ConnectedState", getCurrentState().getName());
     }
 
@@ -986,7 +1020,7 @@
         dhcpResults.addDns("8.8.8.8");
         dhcpResults.setLeaseDuration(3600);
 
-        mTestIpManager.injectDhcpSuccess(dhcpResults);
+        injectDhcpSuccess(dhcpResults);
         mLooper.dispatchAll();
 
         assertEquals("ConnectedState", getCurrentState().getName());
@@ -1069,12 +1103,108 @@
 
         assertEquals("ObtainingIpState", getCurrentState().getName());
 
-        mTestIpManager.injectDhcpFailure();
+        injectDhcpFailure();
         mLooper.dispatchAll();
 
         assertEquals("DisconnectingState", getCurrentState().getName());
     }
 
+    /**
+     * Verify that the network selection status will be updated with DISABLED_AUTHENTICATION_FAILURE
+     * when wrong password authentication failure is detected and the network had been
+     * connected previously.
+     */
+    @Test
+    public void testWrongPasswordWithPreviouslyConnected() throws Exception {
+        initializeAndAddNetworkAndVerifySuccess();
+
+        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+        mLooper.dispatchAll();
+
+        mLooper.startAutoDispatch();
+        mWsm.syncEnableNetwork(mWsmAsyncChannel, 0, true);
+        mLooper.stopAutoDispatch();
+
+        verify(mWifiConfigManager).enableNetwork(eq(0), eq(true), anyInt());
+
+        WifiConfiguration config = new WifiConfiguration();
+        config.getNetworkSelectionStatus().setHasEverConnected(true);
+        when(mWifiConfigManager.getConfiguredNetwork(anyInt())).thenReturn(config);
+
+        mWsm.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT, 0,
+                WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD);
+        mLooper.dispatchAll();
+
+        verify(mWrongPasswordNotifier, never()).onWrongPasswordError(anyString());
+        verify(mWifiConfigManager).updateNetworkSelectionStatus(anyInt(),
+                eq(WifiConfiguration.NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE));
+
+        assertEquals("DisconnectedState", getCurrentState().getName());
+    }
+
+    /**
+     * Verify that the network selection status will be updated with DISABLED_BY_WRONG_PASSWORD
+     * when wrong password authentication failure is detected and the network has never been
+     * connected.
+     */
+    @Test
+    public void testWrongPasswordWithNeverConnected() throws Exception {
+        initializeAndAddNetworkAndVerifySuccess();
+
+        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+        mLooper.dispatchAll();
+
+        mLooper.startAutoDispatch();
+        mWsm.syncEnableNetwork(mWsmAsyncChannel, 0, true);
+        mLooper.stopAutoDispatch();
+
+        verify(mWifiConfigManager).enableNetwork(eq(0), eq(true), anyInt());
+
+        WifiConfiguration config = new WifiConfiguration();
+        config.SSID = sSSID;
+        config.getNetworkSelectionStatus().setHasEverConnected(false);
+        when(mWifiConfigManager.getConfiguredNetwork(anyInt())).thenReturn(config);
+
+        mWsm.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT, 0,
+                WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD);
+        mLooper.dispatchAll();
+
+        verify(mWrongPasswordNotifier).onWrongPasswordError(eq(sSSID));
+        verify(mWifiConfigManager).updateNetworkSelectionStatus(anyInt(),
+                eq(WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD));
+
+        assertEquals("DisconnectedState", getCurrentState().getName());
+    }
+
+    /**
+     * Verify that the network selection status will be updated with DISABLED_BY_WRONG_PASSWORD
+     * when wrong password authentication failure is detected and the network is unknown.
+     */
+    @Test
+    public void testWrongPasswordWithNullNetwork() throws Exception {
+        initializeAndAddNetworkAndVerifySuccess();
+
+        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+        mLooper.dispatchAll();
+
+        mLooper.startAutoDispatch();
+        mWsm.syncEnableNetwork(mWsmAsyncChannel, 0, true);
+        mLooper.stopAutoDispatch();
+
+        verify(mWifiConfigManager).enableNetwork(eq(0), eq(true), anyInt());
+
+        when(mWifiConfigManager.getConfiguredNetwork(anyInt())).thenReturn(null);
+
+        mWsm.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT, 0,
+                WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD);
+        mLooper.dispatchAll();
+
+        verify(mWifiConfigManager).updateNetworkSelectionStatus(anyInt(),
+                eq(WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD));
+
+        assertEquals("DisconnectedState", getCurrentState().getName());
+    }
+
     @Test
     public void testBadNetworkEvent() throws Exception {
         initializeAndAddNetworkAndVerifySuccess();
@@ -1113,7 +1243,7 @@
     public void disconnect() throws Exception {
         connect();
 
-        mWsm.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, -1, 3, "01:02:03:04:05:06");
+        mWsm.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, -1, 3, sBSSID);
         mLooper.dispatchAll();
         mWsm.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
                 new StateChangeResult(0, sWifiSsid, sBSSID, SupplicantState.DISCONNECTED));
@@ -1606,6 +1736,61 @@
     }
 
     /**
+     * Verifies that WifiInfo is updated upon SUPPLICANT_STATE_CHANGE_EVENT.
+     */
+    @Test
+    public void testWifiInfoUpdatedUponSupplicantStateChangedEvent() throws Exception {
+        // Connect to network with |sBSSID|, |sFreq|.
+        connect();
+
+        // Set the scan detail cache for roaming target.
+        when(mWifiConfigManager.getScanDetailCacheForNetwork(FRAMEWORK_NETWORK_ID))
+                .thenReturn(mScanDetailCache);
+        when(mScanDetailCache.getScanDetail(sBSSID1)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, sBSSID1, sFreq1));
+        when(mScanDetailCache.get(sBSSID1)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, sBSSID1, sFreq1).getScanResult());
+
+        // This simulates the behavior of roaming to network with |sBSSID1|, |sFreq1|.
+        // Send a SUPPLICANT_STATE_CHANGE_EVENT, verify WifiInfo is updated.
+        mWsm.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
+                new StateChangeResult(0, sWifiSsid, sBSSID1, SupplicantState.COMPLETED));
+        mLooper.dispatchAll();
+
+        WifiInfo wifiInfo = mWsm.getWifiInfo();
+        assertEquals(sBSSID1, wifiInfo.getBSSID());
+        assertEquals(sFreq1, wifiInfo.getFrequency());
+        assertEquals(SupplicantState.COMPLETED, wifiInfo.getSupplicantState());
+    }
+
+    /**
+     * Verifies that WifiInfo is updated upon CMD_ASSOCIATED_BSSID event.
+     */
+    @Test
+    public void testWifiInfoUpdatedUponAssociatedBSSIDEvent() throws Exception {
+        // Connect to network with |sBSSID|, |sFreq|.
+        connect();
+
+        // Set the scan detail cache for roaming target.
+        when(mWifiConfigManager.getScanDetailCacheForNetwork(FRAMEWORK_NETWORK_ID))
+                .thenReturn(mScanDetailCache);
+        when(mScanDetailCache.getScanDetail(sBSSID1)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, sBSSID1, sFreq1));
+        when(mScanDetailCache.get(sBSSID1)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, sBSSID1, sFreq1).getScanResult());
+
+        // This simulates the behavior of roaming to network with |sBSSID1|, |sFreq1|.
+        // Send a CMD_ASSOCIATED_BSSID, verify WifiInfo is updated.
+        mWsm.sendMessage(WifiStateMachine.CMD_ASSOCIATED_BSSID, 0, 0, sBSSID1);
+        mLooper.dispatchAll();
+
+        WifiInfo wifiInfo = mWsm.getWifiInfo();
+        assertEquals(sBSSID1, wifiInfo.getBSSID());
+        assertEquals(sFreq1, wifiInfo.getFrequency());
+        assertEquals(SupplicantState.COMPLETED, wifiInfo.getSupplicantState());
+    }
+
+    /**
      * Verifies that WifiInfo is cleared upon exiting and entering WifiInfo, and that it is not
      * updated by SUPPLICAN_STATE_CHANGE_EVENTs in ScanModeState.
      * This protects WifiStateMachine from  getting into a bad state where WifiInfo says wifi is
@@ -1714,4 +1899,335 @@
         mLooper.stopAutoDispatch();
         assertFalse(succeeded);
     }
+
+    /**
+     * Test that failure to start HAL in AP mode increments the corresponding metrics.
+     */
+    @Test
+    public void testSetupForSoftApModeHalFailureIncrementsMetrics() throws Exception {
+        when(mWifiNative.setupForSoftApMode())
+                .thenReturn(Pair.create(WifiNative.SETUP_FAILURE_HAL, null));
+
+        SoftApModeConfiguration config = new SoftApModeConfiguration(
+                WifiManager.IFACE_IP_MODE_TETHERED, new WifiConfiguration());
+        mWsm.setHostApRunning(config, true);
+        mLooper.dispatchAll();
+
+        verify(mWifiNative).setupForSoftApMode();
+        verify(mWifiMetrics).incrementNumWifiOnFailureDueToHal();
+    }
+
+    /**
+     * Test that failure to start HAL in AP mode increments the corresponding metrics.
+     */
+    @Test
+    public void testSetupForSoftApModeWificondFailureIncrementsMetrics() throws Exception {
+        when(mWifiNative.setupForSoftApMode())
+                .thenReturn(Pair.create(WifiNative.SETUP_FAILURE_WIFICOND, null));
+
+        SoftApModeConfiguration config = new SoftApModeConfiguration(
+                WifiManager.IFACE_IP_MODE_TETHERED, new WifiConfiguration());
+        mWsm.setHostApRunning(config, true);
+        mLooper.dispatchAll();
+
+        verify(mWifiNative).setupForSoftApMode();
+        verify(mWifiMetrics).incrementNumWifiOnFailureDueToWificond();
+    }
+
+    /**
+     * Test that failure to start HAL in client mode increments the corresponding metrics.
+     */
+    @Test
+    public void testSetupForClientModeHalFailureIncrementsMetrics() throws Exception {
+        when(mWifiNative.setupForClientMode())
+                .thenReturn(Pair.create(WifiNative.SETUP_FAILURE_HAL, null));
+
+        mWsm.setSupplicantRunning(true);
+        mLooper.dispatchAll();
+
+        mWsm.sendMessage(WifiMonitor.SUP_CONNECTION_EVENT);
+        mLooper.dispatchAll();
+
+        verify(mWifiNative).setupForClientMode();
+        verify(mWifiMetrics).incrementNumWifiOnFailureDueToHal();
+    }
+
+    /**
+     * Test that failure to start HAL in client mode increments the corresponding metrics.
+     */
+    @Test
+    public void testSetupForClientModeWificondFailureIncrementsMetrics() throws Exception {
+        when(mWifiNative.setupForClientMode())
+                .thenReturn(Pair.create(WifiNative.SETUP_FAILURE_WIFICOND, null));
+
+        mWsm.setSupplicantRunning(true);
+        mLooper.dispatchAll();
+
+        mWsm.sendMessage(WifiMonitor.SUP_CONNECTION_EVENT);
+        mLooper.dispatchAll();
+
+        verify(mWifiNative).setupForClientMode();
+        verify(mWifiMetrics).incrementNumWifiOnFailureDueToWificond();
+    }
+
+    /**
+     * Test that we don't register the telephony call state listener on devices which do not support
+     * setting/resetting Tx power limit.
+     */
+    @Test
+    public void testVoiceCallSar_disabledTxPowerScenario_WifiOn() throws Exception {
+        loadComponentsInStaMode();
+        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+        assertEquals(WifiStateMachine.CONNECT_MODE, mWsm.getOperationalModeForTest());
+        assertEquals("DisconnectedState", getCurrentState().getName());
+        assertNull(mPhoneStateListener);
+    }
+
+    /**
+     * Test that we do register the telephony call state listener on devices which do support
+     * setting/resetting Tx power limit.
+     */
+    @Test
+    public void testVoiceCallSar_enabledTxPowerScenario_WifiOn() throws Exception {
+        mResources.setBoolean(
+                R.bool.config_wifi_framework_enable_voice_call_sar_tx_power_limit, true);
+        initializeWsm();
+
+        loadComponentsInStaMode();
+        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+        assertEquals(WifiStateMachine.CONNECT_MODE, mWsm.getOperationalModeForTest());
+        assertEquals("DisconnectedState", getCurrentState().getName());
+        assertNotNull(mPhoneStateListener);
+    }
+
+    /**
+     * Test that we do register the telephony call state listener on devices which do support
+     * setting/resetting Tx power limit and set the tx power level if we're in state
+     * {@link TelephonyManager#CALL_STATE_OFFHOOK}.
+     */
+    @Test
+    public void testVoiceCallSar_enabledTxPowerScenarioCallStateOffHook_WhenWifiTurnedOn()
+            throws Exception {
+        mResources.setBoolean(
+                R.bool.config_wifi_framework_enable_voice_call_sar_tx_power_limit, true);
+        initializeWsm();
+
+        when(mWifiNative.selectTxPowerScenario(anyInt())).thenReturn(true);
+        when(mTelephonyManager.isOffhook()).thenReturn(true);
+
+        loadComponentsInStaMode();
+        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+        assertEquals(WifiStateMachine.CONNECT_MODE, mWsm.getOperationalModeForTest());
+        assertEquals("DisconnectedState", getCurrentState().getName());
+        assertNotNull(mPhoneStateListener);
+        verify(mWifiNative).selectTxPowerScenario(eq(WifiNative.TX_POWER_SCENARIO_VOICE_CALL));
+    }
+
+    /**
+     * Test that we do register the telephony call state listener on devices which do support
+     * setting/resetting Tx power limit and set the tx power level if we're in state
+     * {@link TelephonyManager#CALL_STATE_IDLE}.
+     */
+    @Test
+    public void testVoiceCallSar_enabledTxPowerScenarioCallStateIdle_WhenWifiTurnedOn()
+            throws Exception {
+        mResources.setBoolean(
+                R.bool.config_wifi_framework_enable_voice_call_sar_tx_power_limit, true);
+        initializeWsm();
+
+        when(mWifiNative.selectTxPowerScenario(anyInt())).thenReturn(true);
+        when(mTelephonyManager.isIdle()).thenReturn(true);
+
+        loadComponentsInStaMode();
+        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+        assertEquals(WifiStateMachine.CONNECT_MODE, mWsm.getOperationalModeForTest());
+        assertEquals("DisconnectedState", getCurrentState().getName());
+        assertNotNull(mPhoneStateListener);
+    }
+
+    /**
+     * Test that we do register the telephony call state listener on devices which do support
+     * setting/resetting Tx power limit and set the tx power level if we're in state
+     * {@link TelephonyManager#CALL_STATE_OFFHOOK}. This test checks if the
+     * {@link WifiNative#selectTxPowerScenario(int)} failure is handled correctly.
+     */
+    @Test
+    public void testVoiceCallSar_enabledTxPowerScenarioCallStateOffHook_WhenWifiTurnedOn_Fails()
+            throws Exception {
+        mResources.setBoolean(
+                R.bool.config_wifi_framework_enable_voice_call_sar_tx_power_limit, true);
+        initializeWsm();
+
+        when(mWifiNative.selectTxPowerScenario(anyInt())).thenReturn(false);
+        when(mTelephonyManager.isOffhook()).thenReturn(true);
+
+        loadComponentsInStaMode();
+        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+        assertEquals(WifiStateMachine.CONNECT_MODE, mWsm.getOperationalModeForTest());
+        assertEquals("DisconnectedState", getCurrentState().getName());
+        assertNotNull(mPhoneStateListener);
+        verify(mWifiNative).selectTxPowerScenario(eq(WifiNative.TX_POWER_SCENARIO_VOICE_CALL));
+    }
+
+    /**
+     * Test that we invoke the corresponding WifiNative method when
+     * {@link PhoneStateListener#onCallStateChanged(int, String)} is invoked with state
+     * {@link TelephonyManager#CALL_STATE_OFFHOOK}.
+     */
+    @Test
+    public void testVoiceCallSar_enabledTxPowerScenarioCallStateOffHook_WhenWifiOn()
+            throws Exception {
+        when(mWifiNative.selectTxPowerScenario(anyInt())).thenReturn(true);
+        testVoiceCallSar_enabledTxPowerScenario_WifiOn();
+
+        mPhoneStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK, "");
+        mLooper.dispatchAll();
+        verify(mWifiNative).selectTxPowerScenario(eq(WifiNative.TX_POWER_SCENARIO_VOICE_CALL));
+    }
+
+    /**
+     * Test that we invoke the corresponding WifiNative method when
+     * {@link PhoneStateListener#onCallStateChanged(int, String)} is invoked with state
+     * {@link TelephonyManager#CALL_STATE_IDLE}.
+     */
+    @Test
+    public void testVoiceCallSar_enabledTxPowerScenarioCallStateIdle_WhenWifiOn() throws Exception {
+        when(mWifiNative.selectTxPowerScenario(anyInt())).thenReturn(true);
+        testVoiceCallSar_enabledTxPowerScenario_WifiOn();
+
+        mPhoneStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_IDLE, "");
+        mLooper.dispatchAll();
+        verify(mWifiNative, atLeastOnce())
+                .selectTxPowerScenario(eq(WifiNative.TX_POWER_SCENARIO_NORMAL));
+    }
+
+    /**
+     * Test that we invoke the corresponding WifiNative method when
+     * {@link PhoneStateListener#onCallStateChanged(int, String)} is invoked with state
+     * {@link TelephonyManager#CALL_STATE_OFFHOOK}. This test checks if the
+     * {@link WifiNative#selectTxPowerScenario(int)} failure is handled correctly.
+     */
+    @Test
+    public void testVoiceCallSar_enabledTxPowerScenarioCallStateOffHook_WhenWifiOn_Fails()
+            throws Exception {
+        when(mWifiNative.selectTxPowerScenario(anyInt())).thenReturn(false);
+        testVoiceCallSar_enabledTxPowerScenario_WifiOn();
+
+        mPhoneStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK, "");
+        mLooper.dispatchAll();
+        verify(mWifiNative).selectTxPowerScenario(eq(WifiNative.TX_POWER_SCENARIO_VOICE_CALL));
+    }
+
+    /**
+     * Test that we don't invoke the corresponding WifiNative method when
+     * {@link PhoneStateListener#onCallStateChanged(int, String)} is invoked with state
+     * {@link TelephonyManager#CALL_STATE_IDLE} or {@link TelephonyManager#CALL_STATE_OFFHOOK} when
+     * wifi is off (state machine is not in SupplicantStarted state).
+     */
+    @Test
+    public void testVoiceCallSar_enabledTxPowerScenarioCallState_WhenWifiOff() throws Exception {
+        mResources.setBoolean(
+                R.bool.config_wifi_framework_enable_voice_call_sar_tx_power_limit, true);
+        initializeWsm();
+
+        mPhoneStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK, "");
+        mLooper.dispatchAll();
+        verify(mWifiNative, never()).selectTxPowerScenario(anyInt());
+
+        mPhoneStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_IDLE, "");
+        mLooper.dispatchAll();
+        verify(mWifiNative, never()).selectTxPowerScenario(anyInt());
+    }
+
+    /**
+     * Verifies that a network disconnection event will result in WifiStateMachine invoking
+     * {@link WifiConfigManager#removeAllEphemeralOrPasspointConfiguredNetworks()} to remove
+     * any ephemeral or passpoint networks from it's internal database.
+     */
+    @Test
+    public void testDisconnectionRemovesEphemeralAndPasspointNetworks() throws Exception {
+        disconnect();
+        verify(mWifiConfigManager).removeAllEphemeralOrPasspointConfiguredNetworks();
+    }
+
+    /**
+     * Test that the helper method
+     * {@link WifiStateMachine#shouldEvaluateWhetherToSendExplicitlySelected(WifiConfiguration)}
+     * returns true when we connect to the last selected network before expiration of
+     * {@link WifiStateMachine#LAST_SELECTED_NETWORK_EXPIRATION_AGE_MILLIS}.
+     */
+    @Test
+    public void testShouldEvaluateWhetherToSendExplicitlySelected_SameNetworkNotExpired() {
+        long lastSelectedTimestamp = 45666743454L;
+        int lastSelectedNetworkId = 5;
+
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(
+                lastSelectedTimestamp
+                        + WifiStateMachine.LAST_SELECTED_NETWORK_EXPIRATION_AGE_MILLIS - 1);
+        when(mWifiConfigManager.getLastSelectedTimeStamp()).thenReturn(lastSelectedTimestamp);
+        when(mWifiConfigManager.getLastSelectedNetwork()).thenReturn(lastSelectedNetworkId);
+
+        WifiConfiguration currentConfig = new WifiConfiguration();
+        currentConfig.networkId = lastSelectedNetworkId;
+        assertTrue(mWsm.shouldEvaluateWhetherToSendExplicitlySelected(currentConfig));
+    }
+
+    /**
+     * Test that the helper method
+     * {@link WifiStateMachine#shouldEvaluateWhetherToSendExplicitlySelected(WifiConfiguration)}
+     * returns false when we connect to the last selected network after expiration of
+     * {@link WifiStateMachine#LAST_SELECTED_NETWORK_EXPIRATION_AGE_MILLIS}.
+     */
+    @Test
+    public void testShouldEvaluateWhetherToSendExplicitlySelected_SameNetworkExpired() {
+        long lastSelectedTimestamp = 45666743454L;
+        int lastSelectedNetworkId = 5;
+
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(
+                lastSelectedTimestamp
+                        + WifiStateMachine.LAST_SELECTED_NETWORK_EXPIRATION_AGE_MILLIS + 1);
+        when(mWifiConfigManager.getLastSelectedTimeStamp()).thenReturn(lastSelectedTimestamp);
+        when(mWifiConfigManager.getLastSelectedNetwork()).thenReturn(lastSelectedNetworkId);
+
+        WifiConfiguration currentConfig = new WifiConfiguration();
+        currentConfig.networkId = lastSelectedNetworkId;
+        assertFalse(mWsm.shouldEvaluateWhetherToSendExplicitlySelected(currentConfig));
+    }
+
+    /**
+     * Test that the helper method
+     * {@link WifiStateMachine#shouldEvaluateWhetherToSendExplicitlySelected(WifiConfiguration)}
+     * returns false when we connect to a different network to the last selected network.
+     */
+    @Test
+    public void testShouldEvaluateWhetherToSendExplicitlySelected_DifferentNetwork() {
+        long lastSelectedTimestamp = 45666743454L;
+        int lastSelectedNetworkId = 5;
+
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(
+                lastSelectedTimestamp
+                        + WifiStateMachine.LAST_SELECTED_NETWORK_EXPIRATION_AGE_MILLIS - 1);
+        when(mWifiConfigManager.getLastSelectedTimeStamp()).thenReturn(lastSelectedTimestamp);
+        when(mWifiConfigManager.getLastSelectedNetwork()).thenReturn(lastSelectedNetworkId);
+
+        WifiConfiguration currentConfig = new WifiConfiguration();
+        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/WifiVendorHalTest.java b/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java
index 34ddf23..84de9d2 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java
@@ -24,6 +24,7 @@
 import android.hardware.wifi.V1_0.IWifiRttControllerEventCallback;
 import android.hardware.wifi.V1_0.IWifiStaIface;
 import android.hardware.wifi.V1_0.IWifiStaIfaceEventCallback;
+import android.hardware.wifi.V1_0.IfaceType;
 import android.hardware.wifi.V1_0.RttCapabilities;
 import android.hardware.wifi.V1_0.RttConfig;
 import android.hardware.wifi.V1_0.StaApfPacketFilterCapabilities;
@@ -55,6 +56,7 @@
 import android.net.wifi.WifiScanner;
 import android.net.wifi.WifiSsid;
 import android.net.wifi.WifiWakeReasonAndCounts;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.test.TestLooper;
 import android.util.Pair;
@@ -75,8 +77,10 @@
 import java.net.InetAddress;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Random;
+import java.util.Set;
 
 /**
  * Unit tests for {@link com.android.server.wifi.WifiVendorHal}.
@@ -98,6 +102,8 @@
     @Mock
     private IWifiChip mIWifiChip;
     @Mock
+    private android.hardware.wifi.V1_1.IWifiChip mIWifiChipV11;
+    @Mock
     private IWifiStaIface mIWifiStaIface;
     @Mock
     private IWifiRttController mIWifiRttController;
@@ -107,6 +113,21 @@
     private WifiNative.VendorHalDeathEventHandler mVendorHalDeathHandler;
 
     /**
+     * Spy used to return the V1_1 IWifiChip mock object to simulate the 1.1 HAL running on the
+     * device.
+     */
+    private class WifiVendorHalSpyV1_1 extends WifiVendorHal {
+        WifiVendorHalSpyV1_1(HalDeviceManager halDeviceManager, Looper looper) {
+            super(halDeviceManager, looper);
+        }
+
+        @Override
+        protected android.hardware.wifi.V1_1.IWifiChip getWifiChipForV1_1Mockable() {
+            return mIWifiChipV11;
+        }
+    }
+
+    /**
      * Identity function to supply a type to its argument, which is a lambda
      */
     static Answer<WifiStatus> answerWifiStatus(Answer<WifiStatus> statusLambda) {
@@ -560,7 +581,7 @@
      * driven we don't have to work hard to exercise all of it.
      */
     @Test
-    public void testFeatureMaskTranslation() {
+    public void testStaIfaceFeatureMaskTranslation() {
         int caps = (
                 IWifiStaIface.StaIfaceCapabilityMask.BACKGROUND_SCAN
                 | IWifiStaIface.StaIfaceCapabilityMask.LINK_LAYER_STATS
@@ -572,6 +593,70 @@
     }
 
     /**
+     * Test translation to WifiManager.WIFI_FEATURE_*
+     *
+     * Just do a spot-check with a few feature bits here; since the code is table-
+     * driven we don't have to work hard to exercise all of it.
+     */
+    @Test
+    public void testChipFeatureMaskTranslation() {
+        int caps = (
+                android.hardware.wifi.V1_1.IWifiChip.ChipCapabilityMask.SET_TX_POWER_LIMIT
+                        | android.hardware.wifi.V1_1.IWifiChip.ChipCapabilityMask.D2D_RTT
+                        | android.hardware.wifi.V1_1.IWifiChip.ChipCapabilityMask.D2AP_RTT
+        );
+        int expected = (
+                WifiManager.WIFI_FEATURE_TX_POWER_LIMIT
+                        | WifiManager.WIFI_FEATURE_D2D_RTT
+                        | WifiManager.WIFI_FEATURE_D2AP_RTT
+        );
+        assertEquals(expected, mWifiVendorHal.wifiFeatureMaskFromChipCapabilities(caps));
+    }
+
+    /**
+     * Test get supported features. Tests whether we coalesce information from different sources
+     * (IWifiStaIface, IWifiChip and HalDeviceManager) into the bitmask of supported features
+     * correctly.
+     */
+    @Test
+    public void testGetSupportedFeatures() throws Exception {
+        assertTrue(mWifiVendorHal.startVendorHal(true));
+
+        int staIfaceHidlCaps = (
+                IWifiStaIface.StaIfaceCapabilityMask.BACKGROUND_SCAN
+                        | IWifiStaIface.StaIfaceCapabilityMask.LINK_LAYER_STATS
+        );
+        int chipHidlCaps =
+                android.hardware.wifi.V1_1.IWifiChip.ChipCapabilityMask.SET_TX_POWER_LIMIT;
+        Set<Integer>  halDeviceManagerSupportedIfaces = new HashSet<Integer>() {{
+                add(IfaceType.STA);
+                add(IfaceType.P2P);
+            }};
+        int expectedFeatureSet = (
+                WifiManager.WIFI_FEATURE_SCANNER
+                        | WifiManager.WIFI_FEATURE_LINK_LAYER_STATS
+                        | WifiManager.WIFI_FEATURE_TX_POWER_LIMIT
+                        | WifiManager.WIFI_FEATURE_INFRA
+                        | WifiManager.WIFI_FEATURE_P2P
+        );
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(IWifiStaIface.getCapabilitiesCallback cb) throws RemoteException {
+                cb.onValues(mWifiStatusSuccess, staIfaceHidlCaps);
+            }
+        }).when(mIWifiStaIface).getCapabilities(any(IWifiStaIface.getCapabilitiesCallback.class));
+        doAnswer(new AnswerWithArguments() {
+            public void answer(IWifiChip.getCapabilitiesCallback cb) throws RemoteException {
+                cb.onValues(mWifiStatusSuccess, chipHidlCaps);
+            }
+        }).when(mIWifiChip).getCapabilities(any(IWifiChip.getCapabilitiesCallback.class));
+        when(mHalDeviceManager.getSupportedIfaceTypes())
+                .thenReturn(halDeviceManagerSupportedIfaces);
+
+        assertEquals(expectedFeatureSet, mWifiVendorHal.getSupportedFeatureSet());
+    }
+
+    /**
      * Test enablement of link layer stats after startup
      *
      * Request link layer stats before HAL start
@@ -1758,6 +1843,71 @@
         verify(mVendorHalDeathHandler).onDeath();
     }
 
+    /**
+     * Test the new selectTxPowerScenario HIDL method invocation. This should return failure if the
+     * HAL service is exposing the 1.0 interface.
+     */
+    @Test
+    public void testSelectTxPowerScenario() throws RemoteException {
+        assertTrue(mWifiVendorHal.startVendorHal(true));
+        // Should fail because we exposed the 1.0 IWifiChip.
+        assertFalse(
+                mWifiVendorHal.selectTxPowerScenario(WifiNative.TX_POWER_SCENARIO_VOICE_CALL));
+        verify(mIWifiChipV11, never()).selectTxPowerScenario(anyInt());
+        mWifiVendorHal.stopVendorHal();
+
+        // Now expose the 1.1 IWifiChip.
+        mWifiVendorHal = new WifiVendorHalSpyV1_1(mHalDeviceManager, mLooper.getLooper());
+        when(mIWifiChipV11.selectTxPowerScenario(anyInt())).thenReturn(mWifiStatusSuccess);
+
+        assertTrue(mWifiVendorHal.startVendorHal(true));
+        assertTrue(
+                mWifiVendorHal.selectTxPowerScenario(WifiNative.TX_POWER_SCENARIO_VOICE_CALL));
+        verify(mIWifiChipV11).selectTxPowerScenario(
+                eq(android.hardware.wifi.V1_1.IWifiChip.TxPowerScenario.VOICE_CALL));
+        verify(mIWifiChipV11, never()).resetTxPowerScenario();
+        mWifiVendorHal.stopVendorHal();
+    }
+
+    /**
+     * Test the new resetTxPowerScenario HIDL method invocation. This should return failure if the
+     * HAL service is exposing the 1.0 interface.
+     */
+    @Test
+    public void testResetTxPowerScenario() throws RemoteException {
+        assertTrue(mWifiVendorHal.startVendorHal(true));
+        // Should fail because we exposed the 1.0 IWifiChip.
+        assertFalse(mWifiVendorHal.selectTxPowerScenario(WifiNative.TX_POWER_SCENARIO_NORMAL));
+        verify(mIWifiChipV11, never()).resetTxPowerScenario();
+        mWifiVendorHal.stopVendorHal();
+
+        // Now expose the 1.1 IWifiChip.
+        mWifiVendorHal = new WifiVendorHalSpyV1_1(mHalDeviceManager, mLooper.getLooper());
+        when(mIWifiChipV11.resetTxPowerScenario()).thenReturn(mWifiStatusSuccess);
+
+        assertTrue(mWifiVendorHal.startVendorHal(true));
+        assertTrue(mWifiVendorHal.selectTxPowerScenario(WifiNative.TX_POWER_SCENARIO_NORMAL));
+        verify(mIWifiChipV11).resetTxPowerScenario();
+        verify(mIWifiChipV11, never()).selectTxPowerScenario(anyInt());
+        mWifiVendorHal.stopVendorHal();
+    }
+
+    /**
+     * Test the new selectTxPowerScenario HIDL method invocation with a bad scenario index.
+     */
+    @Test
+    public void testInvalidSelectTxPowerScenario() throws RemoteException {
+        // Expose the 1.1 IWifiChip.
+        mWifiVendorHal = new WifiVendorHalSpyV1_1(mHalDeviceManager, mLooper.getLooper());
+        when(mIWifiChipV11.selectTxPowerScenario(anyInt())).thenReturn(mWifiStatusSuccess);
+
+        assertTrue(mWifiVendorHal.startVendorHal(true));
+        assertFalse(mWifiVendorHal.selectTxPowerScenario(-6));
+        verify(mIWifiChipV11, never()).selectTxPowerScenario(anyInt());
+        verify(mIWifiChipV11, never()).resetTxPowerScenario();
+        mWifiVendorHal.stopVendorHal();
+    }
+
     private void startBgScan(WifiNative.ScanEventHandler eventHandler) throws Exception {
         when(mIWifiStaIface.startBackgroundScan(
                 anyInt(), any(StaBackgroundScanParameters.class))).thenReturn(mWifiStatusSuccess);
diff --git a/tests/wifitests/src/com/android/server/wifi/WrongPasswordNotifierTest.java b/tests/wifitests/src/com/android/server/wifi/WrongPasswordNotifierTest.java
new file mode 100644
index 0000000..74bfdeb
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/WrongPasswordNotifierTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.provider.Settings;
+
+import com.android.internal.notification.SystemNotificationChannels;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Answers;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.WrongPasswordNotifier}.
+ */
+public class WrongPasswordNotifierTest {
+    private static final String TEST_SSID = "Test SSID";
+
+    @Mock Context mContext;
+    @Mock Resources mResources;
+    @Mock NotificationManager mNotificationManager;
+    @Mock FrameworkFacade mFrameworkFacade;
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Notification.Builder mNotificationBuilder;
+    WrongPasswordNotifier mWrongPassNotifier;
+
+    /**
+     * Sets up for unit test
+     */
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getSystemService(Context.NOTIFICATION_SERVICE))
+                .thenReturn(mNotificationManager);
+        when(mContext.getResources()).thenReturn(mResources);
+        mWrongPassNotifier =
+                new WrongPasswordNotifier(mContext, mFrameworkFacade);
+    }
+
+    /**
+     * Verify that a wrong password notification will be generated/pushed when a wrong password
+     * error is detected for the current connection.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void onWrongPasswordError() throws Exception {
+        when(mFrameworkFacade.makeNotificationBuilder(any(),
+                eq(SystemNotificationChannels.NETWORK_ALERTS))).thenReturn(mNotificationBuilder);
+        mWrongPassNotifier.onWrongPasswordError(TEST_SSID);
+        verify(mNotificationManager).notify(eq(WrongPasswordNotifier.NOTIFICATION_ID), any());
+        ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
+        verify(mFrameworkFacade).getActivity(
+                any(Context.class), anyInt(), intent.capture(), anyInt());
+        assertEquals(Settings.ACTION_WIFI_SETTINGS, intent.getValue().getAction());
+        assertEquals(TEST_SSID, intent.getValue().getStringExtra("wifi_start_connect_ssid"));
+    }
+
+    /**
+     * Verify that we will attempt to dismiss the wrong password notification when starting a new
+     * connection attempt with the previous connection resulting in a wrong password error.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void onNewConnectionAttemptWithPreviousWrongPasswordError() throws Exception {
+        onWrongPasswordError();
+        reset(mNotificationManager);
+
+        mWrongPassNotifier.onNewConnectionAttempt();
+        verify(mNotificationManager).cancel(any(), eq(WrongPasswordNotifier.NOTIFICATION_ID));
+    }
+
+    /**
+     * Verify that we don't attempt to dismiss the wrong password notification when starting a new
+     * connection attempt with the previous connection not resulting in a wrong password error.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void onNewConnectionAttemptWithoutPreviousWrongPasswordError() throws Exception {
+        mWrongPassNotifier.onNewConnectionAttempt();
+        verify(mNotificationManager, never()).cancel(any(), anyInt());
+    }
+}
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 a2e7c77..8063004 100644
--- a/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareDataPathStateManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareDataPathStateManagerTest.java
@@ -16,14 +16,17 @@
 
 package com.android.server.wifi.aware;
 
-import static android.hardware.wifi.V1_0.NanDataPathChannelCfg.REQUEST_CHANNEL_SETUP;
+import static android.hardware.wifi.V1_0.NanDataPathChannelCfg.CHANNEL_NOT_REQUESTED;
 
 import static org.hamcrest.core.IsEqual.equalTo;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyByte;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyShort;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
@@ -31,8 +34,11 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
+import android.Manifest;
 import android.app.test.TestAlarmManager;
 import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.wifi.V1_0.NanStatusType;
 import android.net.ConnectivityManager;
 import android.net.NetworkCapabilities;
 import android.net.NetworkFactory;
@@ -55,14 +61,16 @@
 import android.net.wifi.aware.WifiAwareSession;
 import android.os.Handler;
 import android.os.INetworkManagementService;
+import android.os.IPowerManager;
 import android.os.Message;
 import android.os.Messenger;
+import android.os.PowerManager;
 import android.os.Process;
 import android.os.test.TestLooper;
 import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Pair;
 
 import com.android.internal.util.AsyncChannel;
+import com.android.server.wifi.util.WifiPermissionsWrapper;
 
 import libcore.util.HexEncoding;
 
@@ -88,6 +96,7 @@
     private TestLooper mMockLooper;
     private Handler mMockLooperHandler;
     private WifiAwareStateManager mDut;
+    @Mock private WifiAwareNativeManager mMockNativeManager;
     @Mock private WifiAwareNativeApi mMockNative;
     @Mock private Context mMockContext;
     @Mock private IWifiAwareManager mMockAwareService;
@@ -96,7 +105,10 @@
     @Mock private WifiAwareDataPathStateManager.NetworkInterfaceWrapper mMockNetworkInterface;
     @Mock private IWifiAwareEventCallback mMockCallback;
     @Mock IWifiAwareDiscoverySessionCallback mMockSessionCallback;
+    @Mock private WifiAwareMetrics mAwareMetricsMock;
+    @Mock private WifiPermissionsWrapper mPermissionsWrapperMock;
     TestAlarmManager mAlarmManager;
+    private PowerManager mMockPowerManager;
 
     @Rule
     public ErrorCollector collector = new ErrorCollector();
@@ -112,39 +124,54 @@
         when(mMockContext.getSystemService(Context.ALARM_SERVICE))
                 .thenReturn(mAlarmManager.getAlarmManager());
 
-        when(mMockContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(mMockCm);
-
         mMockLooper = new TestLooper();
         mMockLooperHandler = new Handler(mMockLooper.getLooper());
 
+        IPowerManager powerManagerService = mock(IPowerManager.class);
+        mMockPowerManager = new PowerManager(mMockContext, powerManagerService,
+                new Handler(mMockLooper.getLooper()));
+
+        when(mMockContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(mMockCm);
+        when(mMockContext.getSystemServiceName(PowerManager.class)).thenReturn(
+                Context.POWER_SERVICE);
+        when(mMockContext.getSystemService(PowerManager.class)).thenReturn(mMockPowerManager);
+
         mDut = new WifiAwareStateManager();
-        mDut.setNative(mMockNative);
-        mDut.start(mMockContext, mMockLooper.getLooper());
+        mDut.setNative(mMockNativeManager, mMockNative);
+        mDut.start(mMockContext, mMockLooper.getLooper(), mAwareMetricsMock,
+                mPermissionsWrapperMock);
         mDut.startLate();
+        mMockLooper.dispatchAll();
 
         when(mMockNative.getCapabilities(anyShort())).thenReturn(true);
         when(mMockNative.enableAndConfigure(anyShort(), any(), anyBoolean(),
-                anyBoolean())).thenReturn(true);
+                anyBoolean(), anyBoolean(), anyBoolean())).thenReturn(true);
         when(mMockNative.disable(anyShort())).thenReturn(true);
-        when(mMockNative.publish(anyShort(), anyInt(), any())).thenReturn(true);
-        when(mMockNative.subscribe(anyShort(), anyInt(), any()))
+        when(mMockNative.publish(anyShort(), anyByte(), any())).thenReturn(true);
+        when(mMockNative.subscribe(anyShort(), anyByte(), any()))
                 .thenReturn(true);
-        when(mMockNative.sendMessage(anyShort(), anyInt(), anyInt(), any(),
+        when(mMockNative.sendMessage(anyShort(), anyByte(), anyInt(), any(),
                 any(), anyInt())).thenReturn(true);
-        when(mMockNative.stopPublish(anyShort(), anyInt())).thenReturn(true);
-        when(mMockNative.stopSubscribe(anyShort(), anyInt())).thenReturn(true);
+        when(mMockNative.stopPublish(anyShort(), anyByte())).thenReturn(true);
+        when(mMockNative.stopSubscribe(anyShort(), anyByte())).thenReturn(true);
         when(mMockNative.createAwareNetworkInterface(anyShort(), any())).thenReturn(true);
         when(mMockNative.deleteAwareNetworkInterface(anyShort(), any())).thenReturn(true);
         when(mMockNative.initiateDataPath(anyShort(), anyInt(), anyInt(), anyInt(),
-                any(), any(), any(), any(),
+                any(), any(), any(), any(), anyBoolean(),
                 any())).thenReturn(true);
         when(mMockNative.respondToDataPathRequest(anyShort(), anyBoolean(), anyInt(), any(),
-                any(), any(), any())).thenReturn(true);
+                any(), any(), anyBoolean(), any())).thenReturn(true);
         when(mMockNative.endDataPath(anyShort(), anyInt())).thenReturn(true);
 
         when(mMockNetworkInterface.configureAgentProperties(any(), any(), anyInt(), any(), any(),
                 any())).thenReturn(true);
 
+        when(mMockPowerManager.isDeviceIdleMode()).thenReturn(false);
+        when(mMockPowerManager.isInteractive()).thenReturn(true);
+
+        when(mPermissionsWrapperMock.getUidPermission(eq(Manifest.permission.CONNECTIVITY_INTERNAL),
+                anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+
         installDataPathStateManagerMocks();
     }
 
@@ -228,6 +255,96 @@
         verifyNoMoreInteractions(mMockNative);
     }
 
+    /**
+     * Validate that trying to specify a PMK without permission results in failure.
+     */
+    @Test
+    public void testDataPathPmkWithoutPermission() throws Exception {
+        final int clientId = 123;
+        final byte pubSubId = 55;
+        final byte[] pmk = "01234567890123456789012345678901".getBytes();
+        final int requestorId = 1341234;
+        final byte[] peerDiscoveryMac = HexEncoding.decode("000102030405".toCharArray(), false);
+
+        InOrder inOrder = inOrder(mMockNative, mMockCm, mMockCallback, mMockSessionCallback);
+        InOrder inOrderM = inOrder(mAwareMetricsMock);
+
+        when(mPermissionsWrapperMock.getUidPermission(eq(Manifest.permission.CONNECTIVITY_INTERNAL),
+                anyInt())).thenReturn(PackageManager.PERMISSION_DENIED);
+
+        // (0) initialize
+        DataPathEndPointInfo res = initDataPathEndPoint(clientId, pubSubId, requestorId,
+                peerDiscoveryMac, inOrder, inOrderM, false);
+
+        // (1) request network
+        NetworkRequest nr = getSessionNetworkRequest(clientId, res.mSessionId, res.mPeerHandle, pmk,
+                null, false);
+
+        Message reqNetworkMsg = Message.obtain();
+        reqNetworkMsg.what = NetworkFactory.CMD_REQUEST_NETWORK;
+        reqNetworkMsg.obj = nr;
+        reqNetworkMsg.arg1 = 0;
+        res.mMessenger.send(reqNetworkMsg);
+        mMockLooper.dispatchAll();
+
+        // failure: no interactions with connectivity manager or native manager
+        verifyNoMoreInteractions(mMockNative, mMockCm, mAwareMetricsMock);
+    }
+
+    /**
+     * Validate that if the data-interfaces are deleted while a data-path is being created, the
+     * process will terminate.
+     */
+    @Test
+    public void testDestroyNdiDuringNdpSetupResponder() throws Exception {
+        final int clientId = 123;
+        final byte pubSubId = 55;
+        final int requestorId = 1341234;
+        final byte[] peerDiscoveryMac = HexEncoding.decode("000102030405".toCharArray(), false);
+        final int ndpId = 3;
+
+        InOrder inOrder = inOrder(mMockNative, mMockCm, mMockCallback, mMockSessionCallback);
+        InOrder inOrderM = inOrder(mAwareMetricsMock);
+
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+
+        // (0) initialize
+        DataPathEndPointInfo res = initDataPathEndPoint(clientId, pubSubId, requestorId,
+                peerDiscoveryMac, inOrder, inOrderM, true);
+
+        // (1) request network
+        NetworkRequest nr = getSessionNetworkRequest(clientId, res.mSessionId, res.mPeerHandle,
+                null, null, true);
+
+        Message reqNetworkMsg = Message.obtain();
+        reqNetworkMsg.what = NetworkFactory.CMD_REQUEST_NETWORK;
+        reqNetworkMsg.obj = nr;
+        reqNetworkMsg.arg1 = 0;
+        res.mMessenger.send(reqNetworkMsg);
+        mMockLooper.dispatchAll();
+
+        // (2) delete interface(s)
+        mDut.deleteAllDataPathInterfaces();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).deleteAwareNetworkInterface(transactionId.capture(),
+                anyString());
+        mDut.onDeleteDataPathInterfaceResponse(transactionId.getValue(), true, 0);
+        mMockLooper.dispatchAll();
+
+        // (3) have responder receive request
+        mDut.onDataPathRequestNotification(pubSubId, peerDiscoveryMac, ndpId);
+        mMockLooper.dispatchAll();
+
+        // (4) verify that responder aborts (i.e. refuses request)
+        inOrder.verify(mMockNative).respondToDataPathRequest(transactionId.capture(), eq(false),
+                eq(ndpId), eq(""), eq(null), eq(null), eq(false), any());
+        mDut.onRespondToDataPathSetupRequestResponse(transactionId.getValue(), true, 0);
+        mMockLooper.dispatchAll();
+
+        // failure if there's further activity
+        verifyNoMoreInteractions(mMockNative, mMockCm, mAwareMetricsMock);
+    }
+
     /*
      * Initiator tests
      */
@@ -238,7 +355,7 @@
      */
     @Test
     public void testDataPathInitiatorMacTokenSuccess() throws Exception {
-        testDataPathInitiatorUtility(false, true, true, true, false);
+        testDataPathInitiatorUtility(false, true, true, false, true, false);
     }
 
     /**
@@ -247,7 +364,7 @@
      */
     @Test(expected = IllegalArgumentException.class)
     public void testDataPathInitiatorNoMacFail() throws Exception {
-        testDataPathInitiatorUtility(false, false, true, true, false);
+        testDataPathInitiatorUtility(false, false, false, true, true, false);
     }
 
     /**
@@ -256,7 +373,7 @@
      */
     @Test
     public void testDataPathInitiatorDirectMacTokenSuccess() throws Exception {
-        testDataPathInitiatorUtility(true, true, true, true, false);
+        testDataPathInitiatorUtility(true, true, true, false, true, false);
     }
 
     /**
@@ -265,7 +382,7 @@
      */
     @Test(expected = IllegalArgumentException.class)
     public void testDataPathInitiatorDirectNoMacTokenFail() throws Exception {
-        testDataPathInitiatorUtility(true, false, true, true, false);
+        testDataPathInitiatorUtility(true, false, false, true, true, false);
     }
 
     /**
@@ -274,7 +391,7 @@
      */
     @Test(expected = IllegalArgumentException.class)
     public void testDataPathInitiatorDirectNoMacNoTokenFail() throws Exception {
-        testDataPathInitiatorUtility(true, false, false, true, false);
+        testDataPathInitiatorUtility(true, false, false, false, true, false);
     }
 
     /**
@@ -283,7 +400,7 @@
      */
     @Test
     public void testDataPathInitiatorNoConfirmationTimeoutFail() throws Exception {
-        testDataPathInitiatorUtility(false, true, true, false, false);
+        testDataPathInitiatorUtility(false, true, true, false, false, false);
     }
 
     /**
@@ -292,7 +409,7 @@
      */
     @Test
     public void testDataPathInitiatorNoConfirmationHalFail() throws Exception {
-        testDataPathInitiatorUtility(false, true, true, true, true);
+        testDataPathInitiatorUtility(false, true, false, true, true, true);
     }
 
     /**
@@ -330,7 +447,7 @@
      */
     @Test
     public void testDataPathResonderMacTokenSuccess() throws Exception {
-        testDataPathResponderUtility(false, true, true, true);
+        testDataPathResponderUtility(false, true, true, false, true);
     }
 
     /**
@@ -339,7 +456,7 @@
      */
     @Test
     public void testDataPathResonderMacNoTokenSuccess() throws Exception {
-        testDataPathResponderUtility(false, true, false, true);
+        testDataPathResponderUtility(false, true, false, false, true);
     }
 
     /**
@@ -348,7 +465,7 @@
      */
     @Test
     public void testDataPathResonderMacTokenNoPeerIdSuccess() throws Exception {
-        testDataPathResponderUtility(false, false, true, true);
+        testDataPathResponderUtility(false, false, false, true, true);
     }
 
     /**
@@ -357,7 +474,7 @@
      */
     @Test
     public void testDataPathResonderMacTokenNoPeerIdNoTokenSuccess() throws Exception {
-        testDataPathResponderUtility(false, false, false, true);
+        testDataPathResponderUtility(false, false, false, false, true);
     }
 
     /**
@@ -366,7 +483,7 @@
      */
     @Test
     public void testDataPathResonderDirectMacTokenSuccess() throws Exception {
-        testDataPathResponderUtility(true, true, true, true);
+        testDataPathResponderUtility(true, true, true, false, true);
     }
 
     /**
@@ -375,7 +492,7 @@
      */
     @Test
     public void testDataPathResonderDirectMacNoTokenSuccess() throws Exception {
-        testDataPathResponderUtility(true, true, false, true);
+        testDataPathResponderUtility(true, true, false, false, true);
     }
 
     /**
@@ -384,7 +501,7 @@
      */
     @Test
     public void testDataPathResonderDirectNoMacTokenSuccess() throws Exception {
-        testDataPathResponderUtility(true, false, true, true);
+        testDataPathResponderUtility(true, false, false, true, true);
     }
 
     /**
@@ -393,7 +510,7 @@
      */
     @Test
     public void testDataPathResonderDirectNoMacNoTokenSuccess() throws Exception {
-        testDataPathResponderUtility(true, false, false, true);
+        testDataPathResponderUtility(true, false, false, false, true);
     }
 
     /**
@@ -402,7 +519,7 @@
      */
     @Test
     public void testDataPathResponderNoConfirmationTimeoutFail() throws Exception {
-        testDataPathResponderUtility(false, true, true, false);
+        testDataPathResponderUtility(false, true, true, false, false);
     }
 
     /**
@@ -436,21 +553,22 @@
 
     private void testDataPathInitiatorResponderMismatchUtility(boolean doPublish) throws Exception {
         final int clientId = 123;
-        final int pubSubId = 11234;
+        final byte pubSubId = 55;
         final int ndpId = 2;
-        final byte[] pmk = "some bytes".getBytes();
-        final PeerHandle peerHandle = new PeerHandle(1341234);
+        final byte[] pmk = "01234567890123456789012345678901".getBytes();
+        final int requestorId = 1341234;
         final byte[] peerDiscoveryMac = HexEncoding.decode("000102030405".toCharArray(), false);
 
         InOrder inOrder = inOrder(mMockNative, mMockCm, mMockCallback, mMockSessionCallback);
+        InOrder inOrderM = inOrder(mAwareMetricsMock);
 
         // (0) initialize
-        Pair<Integer, Messenger> res = initDataPathEndPoint(clientId, pubSubId, peerHandle,
-                peerDiscoveryMac, inOrder, doPublish);
+        DataPathEndPointInfo res = initDataPathEndPoint(clientId, pubSubId, requestorId,
+                peerDiscoveryMac, inOrder, inOrderM, doPublish);
 
         // (1) request network
-        NetworkRequest nr = getSessionNetworkRequest(clientId, res.first, peerHandle, pmk,
-                doPublish);
+        NetworkRequest nr = getSessionNetworkRequest(clientId, res.mSessionId, res.mPeerHandle, pmk,
+                null, doPublish);
 
         // corrupt the network specifier: reverse the role (so it's mis-matched)
         WifiAwareNetworkSpecifier ns =
@@ -471,7 +589,7 @@
         reqNetworkMsg.what = NetworkFactory.CMD_REQUEST_NETWORK;
         reqNetworkMsg.obj = nr;
         reqNetworkMsg.arg1 = 0;
-        res.second.send(reqNetworkMsg);
+        res.mMessenger.send(reqNetworkMsg);
         mMockLooper.dispatchAll();
 
         // consequences of failure:
@@ -482,30 +600,31 @@
             mDut.onDataPathRequestNotification(pubSubId, peerDiscoveryMac, ndpId);
             mMockLooper.dispatchAll();
             inOrder.verify(mMockNative).respondToDataPathRequest(anyShort(), eq(false),
-                    eq(ndpId), eq(""), eq(null), eq(null), any());
+                    eq(ndpId), eq(""), eq(null), eq(null), anyBoolean(), any());
         }
 
-        verifyNoMoreInteractions(mMockNative, mMockCm);
+        verifyNoMoreInteractions(mMockNative, mMockCm, mAwareMetricsMock);
     }
 
     private void testDataPathInitiatorResponderInvalidUidUtility(boolean doPublish,
             boolean isUidSet) throws Exception {
         final int clientId = 123;
-        final int pubSubId = 11234;
+        final byte pubSubId = 56;
         final int ndpId = 2;
-        final byte[] pmk = "some bytes".getBytes();
-        final PeerHandle peerHandle = new PeerHandle(1341234);
+        final byte[] pmk = "01234567890123456789012345678901".getBytes();
+        final int requestorId = 1341234;
         final byte[] peerDiscoveryMac = HexEncoding.decode("000102030405".toCharArray(), false);
 
         InOrder inOrder = inOrder(mMockNative, mMockCm, mMockCallback, mMockSessionCallback);
+        InOrder inOrderM = inOrder(mAwareMetricsMock);
 
         // (0) initialize
-        Pair<Integer, Messenger> res = initDataPathEndPoint(clientId, pubSubId, peerHandle,
-                peerDiscoveryMac, inOrder, doPublish);
+        DataPathEndPointInfo res = initDataPathEndPoint(clientId, pubSubId, requestorId,
+                peerDiscoveryMac, inOrder, inOrderM, doPublish);
 
         // (1) create network request
-        NetworkRequest nr = getSessionNetworkRequest(clientId, res.first, peerHandle, pmk,
-                doPublish);
+        NetworkRequest nr = getSessionNetworkRequest(clientId, res.mSessionId, res.mPeerHandle, pmk,
+                null, doPublish);
 
         // (2) corrupt request's UID
         WifiAwareNetworkSpecifier ns =
@@ -527,7 +646,7 @@
         reqNetworkMsg.what = NetworkFactory.CMD_REQUEST_NETWORK;
         reqNetworkMsg.obj = nr;
         reqNetworkMsg.arg1 = 0;
-        res.second.send(reqNetworkMsg);
+        res.mMessenger.send(reqNetworkMsg);
         mMockLooper.dispatchAll();
 
         // consequences of failure:
@@ -538,20 +657,21 @@
             mDut.onDataPathRequestNotification(pubSubId, peerDiscoveryMac, ndpId);
             mMockLooper.dispatchAll();
             inOrder.verify(mMockNative).respondToDataPathRequest(anyShort(), eq(false),
-                    eq(ndpId), eq(""), eq(null), eq(null), any());
+                    eq(ndpId), eq(""), eq(null), eq(null), anyBoolean(), any());
         }
 
-        verifyNoMoreInteractions(mMockNative, mMockCm);
+        verifyNoMoreInteractions(mMockNative, mMockCm, mAwareMetricsMock);
     }
 
     private void testDataPathInitiatorUtility(boolean useDirect, boolean provideMac,
-            boolean providePmk, boolean getConfirmation, boolean immediateHalFailure)
-            throws Exception {
+            boolean providePmk, boolean providePassphrase, boolean getConfirmation,
+            boolean immediateHalFailure) throws Exception {
         final int clientId = 123;
-        final int pubSubId = 11234;
-        final PeerHandle peerHandle = new PeerHandle(1341234);
+        final byte pubSubId = 58;
+        final int requestorId = 1341234;
         final int ndpId = 2;
-        final byte[] pmk = "some bytes".getBytes();
+        final byte[] pmk = "01234567890123456789012345678901".getBytes();
+        final String passphrase = "some passphrase";
         final String peerToken = "let's go!";
         final byte[] peerDiscoveryMac = HexEncoding.decode("000102030405".toCharArray(), false);
         final byte[] peerDataPathMac = HexEncoding.decode("0A0B0C0D0E0F".toCharArray(), false);
@@ -559,41 +679,53 @@
         ArgumentCaptor<Messenger> messengerCaptor = ArgumentCaptor.forClass(Messenger.class);
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
         InOrder inOrder = inOrder(mMockNative, mMockCm, mMockCallback, mMockSessionCallback);
+        InOrder inOrderM = inOrder(mAwareMetricsMock);
+
+        if (!providePmk) {
+            when(mPermissionsWrapperMock.getUidPermission(
+                    eq(Manifest.permission.CONNECTIVITY_INTERNAL), anyInt())).thenReturn(
+                    PackageManager.PERMISSION_DENIED);
+        }
 
         if (immediateHalFailure) {
             when(mMockNative.initiateDataPath(anyShort(), anyInt(), anyInt(), anyInt(), any(),
-                    any(), any(), any(), any())).thenReturn(false);
+                    any(), any(), any(), anyBoolean(), any())).thenReturn(false);
 
         }
 
         // (0) initialize
-        Pair<Integer, Messenger> res = initDataPathEndPoint(clientId, pubSubId, peerHandle,
-                peerDiscoveryMac, inOrder, false);
+        DataPathEndPointInfo res = initDataPathEndPoint(clientId, pubSubId, requestorId,
+                peerDiscoveryMac, inOrder, inOrderM, false);
 
         // (1) request network
         NetworkRequest nr;
         if (useDirect) {
             nr = getDirectNetworkRequest(clientId,
                     WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR,
-                    provideMac ? peerDiscoveryMac : null, providePmk ? pmk : null);
+                    provideMac ? peerDiscoveryMac : null, providePmk ? pmk : null,
+                    providePassphrase ? passphrase : null);
         } else {
-            nr = getSessionNetworkRequest(clientId, res.first, provideMac ? peerHandle : null,
-                    providePmk ? pmk : null, false);
+            nr = getSessionNetworkRequest(clientId, res.mSessionId,
+                    provideMac ? res.mPeerHandle : null, providePmk ? pmk : null,
+                    providePassphrase ? passphrase : null, false);
         }
 
         Message reqNetworkMsg = Message.obtain();
         reqNetworkMsg.what = NetworkFactory.CMD_REQUEST_NETWORK;
         reqNetworkMsg.obj = nr;
         reqNetworkMsg.arg1 = 0;
-        res.second.send(reqNetworkMsg);
+        res.mMessenger.send(reqNetworkMsg);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).initiateDataPath(transactionId.capture(),
-                eq(useDirect ? 0 : peerHandle.peerId),
-                eq(REQUEST_CHANNEL_SETUP), eq(2437), eq(peerDiscoveryMac),
-                eq(sAwareInterfacePrefix + "0"), eq(pmk), eq(null), any());
+                eq(useDirect ? 0 : requestorId),
+                eq(CHANNEL_NOT_REQUESTED), anyInt(), eq(peerDiscoveryMac),
+                eq(sAwareInterfacePrefix + "0"), eq(providePmk ? pmk : null),
+                eq(providePassphrase ? passphrase : null), eq(useDirect), any());
         if (immediateHalFailure) {
             // short-circuit the rest of this test
-            verifyNoMoreInteractions(mMockNative, mMockCm);
+            inOrderM.verify(mAwareMetricsMock).recordNdpStatus(eq(NanStatusType.INTERNAL_FAILURE),
+                    eq(useDirect), anyLong());
+            verifyNoMoreInteractions(mMockNative, mMockCm, mAwareMetricsMock);
             return;
         }
 
@@ -607,6 +739,9 @@
             mMockLooper.dispatchAll();
             inOrder.verify(mMockCm).registerNetworkAgent(messengerCaptor.capture(), any(), any(),
                     any(), anyInt(), any());
+            inOrderM.verify(mAwareMetricsMock).recordNdpStatus(eq(NanStatusType.SUCCESS),
+                    eq(useDirect), anyLong());
+            inOrderM.verify(mAwareMetricsMock).recordNdpCreation(anyInt(), any());
         } else {
             assertTrue(mAlarmManager.dispatch(
                     WifiAwareStateManager.HAL_DATA_PATH_CONFIRM_TIMEOUT_TAG));
@@ -614,6 +749,8 @@
             inOrder.verify(mMockNative).endDataPath(transactionId.capture(), eq(ndpId));
             mDut.onEndDataPathResponse(transactionId.getValue(), true, 0);
             mMockLooper.dispatchAll();
+            inOrderM.verify(mAwareMetricsMock).recordNdpStatus(eq(NanStatusType.INTERNAL_FAILURE),
+                    eq(useDirect), anyLong());
         }
 
         // (3) end data-path (unless didn't get confirmation)
@@ -621,7 +758,7 @@
             Message endNetworkReqMsg = Message.obtain();
             endNetworkReqMsg.what = NetworkFactory.CMD_CANCEL_REQUEST;
             endNetworkReqMsg.obj = nr;
-            res.second.send(endNetworkReqMsg);
+            res.mMessenger.send(endNetworkReqMsg);
 
             Message endNetworkUsageMsg = Message.obtain();
             endNetworkUsageMsg.what = AsyncChannel.CMD_CHANNEL_DISCONNECTED;
@@ -630,19 +767,23 @@
             mMockLooper.dispatchAll();
             inOrder.verify(mMockNative).endDataPath(transactionId.capture(), eq(ndpId));
             mDut.onEndDataPathResponse(transactionId.getValue(), true, 0);
+            mDut.onDataPathEndNotification(ndpId);
             mMockLooper.dispatchAll();
+            inOrderM.verify(mAwareMetricsMock).recordNdpSessionDuration(anyLong());
         }
 
-        verifyNoMoreInteractions(mMockNative, mMockCm);
+        verifyNoMoreInteractions(mMockNative, mMockCm, mAwareMetricsMock);
     }
 
     private void testDataPathResponderUtility(boolean useDirect, boolean provideMac,
-            boolean providePmk, boolean getConfirmation) throws Exception {
+            boolean providePmk, boolean providePassphrase, boolean getConfirmation)
+            throws Exception {
         final int clientId = 123;
-        final int pubSubId = 11234;
-        final PeerHandle peerHandle = new PeerHandle(1341234);
+        final byte pubSubId = 60;
+        final int requestorId = 1341234;
         final int ndpId = 2;
-        final byte[] pmk = "some bytes".getBytes();
+        final byte[] pmk = "01234567890123456789012345678901".getBytes();
+        final String passphrase = "some passphrase";
         final String peerToken = "let's go!";
         final byte[] peerDiscoveryMac = HexEncoding.decode("000102030405".toCharArray(), false);
         final byte[] peerDataPathMac = HexEncoding.decode("0A0B0C0D0E0F".toCharArray(), false);
@@ -650,35 +791,44 @@
         ArgumentCaptor<Messenger> messengerCaptor = ArgumentCaptor.forClass(Messenger.class);
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
         InOrder inOrder = inOrder(mMockNative, mMockCm, mMockCallback, mMockSessionCallback);
+        InOrder inOrderM = inOrder(mAwareMetricsMock);
+
+        if (providePmk) {
+            when(mPermissionsWrapperMock.getUidPermission(
+                    eq(Manifest.permission.CONNECTIVITY_INTERNAL), anyInt())).thenReturn(
+                    PackageManager.PERMISSION_GRANTED);
+        }
 
         // (0) initialize
-        Pair<Integer, Messenger> res = initDataPathEndPoint(clientId, pubSubId, peerHandle,
-                peerDiscoveryMac, inOrder, true);
+        DataPathEndPointInfo res = initDataPathEndPoint(clientId, pubSubId, requestorId,
+                peerDiscoveryMac, inOrder, inOrderM, true);
 
         // (1) request network
         NetworkRequest nr;
         if (useDirect) {
             nr = getDirectNetworkRequest(clientId,
                     WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER,
-                    provideMac ? peerDiscoveryMac : null, providePmk ? pmk : null);
+                    provideMac ? peerDiscoveryMac : null, providePmk ? pmk : null,
+                    providePassphrase ? passphrase : null);
         } else {
-            nr = getSessionNetworkRequest(clientId, res.first, provideMac ? peerHandle : null,
-                    providePmk ? pmk : null, true);
+            nr = getSessionNetworkRequest(clientId, res.mSessionId,
+                    provideMac ? res.mPeerHandle : null, providePmk ? pmk : null,
+                    providePassphrase ? passphrase : null, true);
         }
 
         Message reqNetworkMsg = Message.obtain();
         reqNetworkMsg.what = NetworkFactory.CMD_REQUEST_NETWORK;
         reqNetworkMsg.obj = nr;
         reqNetworkMsg.arg1 = 0;
-        res.second.send(reqNetworkMsg);
+        res.mMessenger.send(reqNetworkMsg);
         mMockLooper.dispatchAll();
 
         // (2) get request & respond
         mDut.onDataPathRequestNotification(pubSubId, peerDiscoveryMac, ndpId);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).respondToDataPathRequest(transactionId.capture(), eq(true),
-                eq(ndpId), eq(sAwareInterfacePrefix + "0"), eq(providePmk ? pmk : null), eq(null),
-                any());
+                eq(ndpId), eq(sAwareInterfacePrefix + "0"), eq(providePmk ? pmk : null),
+                eq(providePassphrase ? passphrase : null), eq(useDirect), any());
         mDut.onRespondToDataPathSetupRequestResponse(transactionId.getValue(), true, 0);
         mMockLooper.dispatchAll();
 
@@ -689,6 +839,9 @@
             mMockLooper.dispatchAll();
             inOrder.verify(mMockCm).registerNetworkAgent(messengerCaptor.capture(), any(), any(),
                     any(), anyInt(), any());
+            inOrderM.verify(mAwareMetricsMock).recordNdpStatus(eq(NanStatusType.SUCCESS),
+                    eq(useDirect), anyLong());
+            inOrderM.verify(mAwareMetricsMock).recordNdpCreation(anyInt(), any());
         } else {
             assertTrue(mAlarmManager.dispatch(
                     WifiAwareStateManager.HAL_DATA_PATH_CONFIRM_TIMEOUT_TAG));
@@ -696,6 +849,8 @@
             inOrder.verify(mMockNative).endDataPath(transactionId.capture(), eq(ndpId));
             mDut.onEndDataPathResponse(transactionId.getValue(), true, 0);
             mMockLooper.dispatchAll();
+            inOrderM.verify(mAwareMetricsMock).recordNdpStatus(eq(NanStatusType.INTERNAL_FAILURE),
+                    eq(useDirect), anyLong());
         }
 
         // (4) end data-path (unless didn't get confirmation)
@@ -703,7 +858,7 @@
             Message endNetworkMsg = Message.obtain();
             endNetworkMsg.what = NetworkFactory.CMD_CANCEL_REQUEST;
             endNetworkMsg.obj = nr;
-            res.second.send(endNetworkMsg);
+            res.mMessenger.send(endNetworkMsg);
 
             Message endNetworkUsageMsg = Message.obtain();
             endNetworkUsageMsg.what = AsyncChannel.CMD_CHANNEL_DISCONNECTED;
@@ -712,10 +867,12 @@
             mMockLooper.dispatchAll();
             inOrder.verify(mMockNative).endDataPath(transactionId.capture(), eq(ndpId));
             mDut.onEndDataPathResponse(transactionId.getValue(), true, 0);
+            mDut.onDataPathEndNotification(ndpId);
             mMockLooper.dispatchAll();
+            inOrderM.verify(mAwareMetricsMock).recordNdpSessionDuration(anyLong());
         }
 
-        verifyNoMoreInteractions(mMockNative, mMockCm);
+        verifyNoMoreInteractions(mMockNative, mMockCm, mAwareMetricsMock);
     }
 
     private void installDataPathStateManagerMocks() throws Exception {
@@ -733,7 +890,7 @@
     }
 
     private NetworkRequest getSessionNetworkRequest(int clientId, int sessionId,
-            PeerHandle peerHandle, byte[] pmk, boolean doPublish)
+            PeerHandle peerHandle, byte[] pmk, String passphrase, boolean doPublish)
             throws Exception {
         final WifiAwareManager mgr = new WifiAwareManager(mMockContext, mMockAwareService);
         final ConfigRequest configRequest = new ConfigRequest.Builder().build();
@@ -781,10 +938,13 @@
         }
 
         NetworkSpecifier ns;
-        if (pmk == null) {
+        if (pmk == null && passphrase == null) {
             ns = discoverySession.getValue().createNetworkSpecifierOpen(peerHandle);
-        } else {
+        } else if (passphrase == null) {
             ns = discoverySession.getValue().createNetworkSpecifierPmk(peerHandle, pmk);
+        } else {
+            ns = discoverySession.getValue().createNetworkSpecifierPassphrase(peerHandle,
+                    passphrase);
         }
 
         NetworkCapabilities nc = new NetworkCapabilities();
@@ -801,7 +961,7 @@
     }
 
     private NetworkRequest getDirectNetworkRequest(int clientId, int role, byte[] peer,
-            byte[] pmk) throws Exception {
+            byte[] pmk, String passphrase) throws Exception {
         final ConfigRequest configRequest = new ConfigRequest.Builder().build();
         final WifiAwareManager mgr = new WifiAwareManager(mMockContext, mMockAwareService);
 
@@ -820,10 +980,12 @@
         verify(mockCallback).onAttached(sessionCaptor.capture());
 
         NetworkSpecifier ns;
-        if (pmk == null) {
+        if (pmk == null && passphrase == null) {
             ns = sessionCaptor.getValue().createNetworkSpecifierOpen(role, peer);
-        } else {
+        } else if (passphrase == null) {
             ns = sessionCaptor.getValue().createNetworkSpecifierPmk(role, peer, pmk);
+        } else {
+            ns = sessionCaptor.getValue().createNetworkSpecifierPassphrase(role, peer, passphrase);
         }
         NetworkCapabilities nc = new NetworkCapabilities();
         nc.clearAll();
@@ -838,8 +1000,8 @@
         return new NetworkRequest(nc, 0, 0, NetworkRequest.Type.REQUEST);
     }
 
-    private Pair<Integer, Messenger> initDataPathEndPoint(int clientId, int pubSubId,
-            PeerHandle peerHandle, byte[] peerDiscoveryMac, InOrder inOrder,
+    private DataPathEndPointInfo initDataPathEndPoint(int clientId, byte pubSubId,
+            int requestorId, byte[] peerDiscoveryMac, InOrder inOrder, InOrder inOrderM,
             boolean doPublish)
             throws Exception {
         final int pid = 2000;
@@ -854,6 +1016,7 @@
 
         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);
 
@@ -869,9 +1032,23 @@
         mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), capabilities);
         mMockLooper.dispatchAll();
 
-        // (2) enable usage (creates interfaces)
+        // (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,
@@ -879,15 +1056,6 @@
         mDut.onCreateDataPathInterfaceResponse(transactionId.getValue(), true, 0);
         mMockLooper.dispatchAll();
 
-        // (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));
-        mDut.onConfigSuccessResponse(transactionId.getValue());
-        mMockLooper.dispatchAll();
-        inOrder.verify(mMockCallback).onConnectSuccess(clientId);
         if (doPublish) {
             mDut.publish(clientId, publishConfig, mMockSessionCallback);
         } else {
@@ -895,20 +1063,39 @@
         }
         mMockLooper.dispatchAll();
         if (doPublish) {
-            inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishConfig));
+            inOrder.verify(mMockNative).publish(transactionId.capture(), eq((byte) 0),
+                    eq(publishConfig));
         } else {
-            inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0),
+            inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq((byte) 0),
                     eq(subscribeConfig));
         }
         mDut.onSessionConfigSuccessResponse(transactionId.getValue(), doPublish, pubSubId);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockSessionCallback).onSessionStarted(sessionId.capture());
-        mDut.onMessageReceivedNotification(pubSubId, peerHandle.peerId, peerDiscoveryMac,
+        inOrderM.verify(mAwareMetricsMock).recordDiscoverySession(eq(Process.myUid()),
+                eq(doPublish), any());
+        inOrderM.verify(mAwareMetricsMock).recordDiscoveryStatus(Process.myUid(),
+                NanStatusType.SUCCESS, doPublish);
+
+        mDut.onMessageReceivedNotification(pubSubId, requestorId, peerDiscoveryMac,
                 someMsg.getBytes());
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockSessionCallback).onMessageReceived(peerHandle.peerId,
-                someMsg.getBytes());
+        inOrder.verify(mMockSessionCallback).onMessageReceived(peerIdCaptor.capture(),
+                eq(someMsg.getBytes()));
 
-        return new Pair<>(sessionId.getValue(), messengerCaptor.getValue());
+        return new DataPathEndPointInfo(sessionId.getValue(), peerIdCaptor.getValue(),
+                messengerCaptor.getValue());
+    }
+
+    private static class DataPathEndPointInfo {
+        int mSessionId;
+        PeerHandle mPeerHandle;
+        Messenger mMessenger;
+
+        DataPathEndPointInfo(int sessionId, int peerId, Messenger messenger) {
+            mSessionId = sessionId;
+            mPeerHandle = new PeerHandle(peerId);
+            mMessenger = messenger;
+        }
     }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareMetricsTest.java b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareMetricsTest.java
new file mode 100644
index 0000000..8cd1e10
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareMetricsTest.java
@@ -0,0 +1,672 @@
+/*
+ * 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.aware;
+
+import static com.android.server.wifi.aware.WifiAwareMetrics.addNanHalStatusToHistogram;
+import static com.android.server.wifi.aware.WifiAwareMetrics.histogramToProtoArray;
+
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.mockito.Mockito.when;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.hardware.wifi.V1_0.NanStatusType;
+import android.net.wifi.aware.WifiAwareNetworkSpecifier;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+
+import com.android.server.wifi.Clock;
+import com.android.server.wifi.nano.WifiMetricsProto;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ErrorCollector;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Unit test harness for WifiAwareMetrics
+ */
+public class WifiAwareMetricsTest {
+    @Mock Clock mClock;
+    @Mock private Context mMockContext;
+    @Mock private AppOpsManager mMockAppOpsManager;
+    @Rule public ErrorCollector collector = new ErrorCollector();
+
+    private WifiAwareMetrics mDut;
+
+    // Histogram definition: start[i] = b + p * m^i with s sub-buckets, i=0,...,n-1
+
+    /**
+     * Histogram of following buckets, start[i] = 0 + 1 * 10^i with 9 sub-buckets, i=0,...,5
+     * 1 - 10: 9 sub-buckets each of width 1
+     * 10 - 100: 10
+     * 100 - 10e3: 10^2
+     * 10e3 - 10e4: 10^3
+     * 10e4 - 10e5: 10^4
+     * 10e5 - 10e6: 10^5
+     */
+    private static final WifiAwareMetrics.HistParms HIST1 = new WifiAwareMetrics.HistParms(0, 1,
+            10, 9, 6);
+
+    /**
+     * Histogram of following buckets, start[i] = -20 + 2 * 5^i with 40 sub-buckets, i=0,...,2
+     * -18 - -10: 40 sub-bucket each of width 0.2
+     * -10 - 30: 1
+     * 30 - 230: 5
+     */
+    private static final WifiAwareMetrics.HistParms HIST2 = new WifiAwareMetrics.HistParms(-20, 2,
+            5, 40, 3);
+
+    /**
+     * Pre-test configuration. Initialize and install mocks.
+     */
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mMockAppOpsManager);
+        setTime(0);
+
+        mDut = new WifiAwareMetrics(mClock);
+    }
+
+    /**
+     * Validates that recordEnableUsage() and recordDisableUsage() record valid metrics.
+     */
+    @Test
+    public void testEnableDisableUsageMetrics() {
+        WifiMetricsProto.WifiAwareLog log;
+
+        // create 2 records
+        setTime(5);
+        mDut.recordEnableUsage();
+        setTime(10);
+        mDut.recordDisableUsage();
+        setTime(11);
+        mDut.recordEnableUsage();
+        setTime(12);
+        mDut.recordDisableUsage();
+
+        setTime(14);
+        log = mDut.consolidateProto();
+        collector.checkThat(countAllHistogramSamples(log.histogramAwareAvailableDurationMs),
+                equalTo(2));
+        validateProtoHistBucket("Duration[0] #1", log.histogramAwareAvailableDurationMs[0], 1, 2,
+                1);
+        validateProtoHistBucket("Duration[1] #1", log.histogramAwareAvailableDurationMs[1], 5, 6,
+                1);
+        collector.checkThat(log.availableTimeMs, equalTo(6L));
+
+        // create another partial record
+        setTime(15);
+        mDut.recordEnableUsage();
+
+        setTime(17);
+        log = mDut.consolidateProto();
+        collector.checkThat(countAllHistogramSamples(log.histogramAwareAvailableDurationMs),
+                equalTo(2));
+        validateProtoHistBucket("Duration[0] #2", log.histogramAwareAvailableDurationMs[0], 1, 2,
+                1);
+        validateProtoHistBucket("Duration[1] #2", log.histogramAwareAvailableDurationMs[1], 5, 6,
+                1);
+        collector.checkThat(log.availableTimeMs, equalTo(8L)); // the partial record of 2ms
+
+
+        // clear and continue that partial record (verify completed)
+        mDut.clear();
+        setTime(23);
+        mDut.recordDisableUsage();
+
+        log = mDut.consolidateProto();
+        collector.checkThat(countAllHistogramSamples(log.histogramAwareAvailableDurationMs),
+                equalTo(1));
+        validateProtoHistBucket("Duration[0] #3", log.histogramAwareAvailableDurationMs[0], 8, 9,
+                1);
+        collector.checkThat(log.availableTimeMs, equalTo(6L)); // the remnant record of 6ms
+
+        // clear and verify empty records
+        mDut.clear();
+        log = mDut.consolidateProto();
+        collector.checkThat(countAllHistogramSamples(log.histogramAwareAvailableDurationMs),
+                equalTo(0));
+    }
+
+    /**
+     * Validates that recordEnableAware() and recordDisableAware() record valid metrics.
+     */
+    @Test
+    public void testEnableDisableAwareMetrics() {
+        WifiMetricsProto.WifiAwareLog log;
+
+        // create 2 records
+        setTime(5);
+        mDut.recordEnableAware();
+        setTime(10);
+        mDut.recordDisableAware();
+        setTime(11);
+        mDut.recordEnableAware();
+        setTime(12);
+        mDut.recordDisableAware();
+
+        setTime(14);
+        log = mDut.consolidateProto();
+        collector.checkThat(countAllHistogramSamples(log.histogramAwareEnabledDurationMs),
+                equalTo(2));
+        validateProtoHistBucket("Duration[0] #1", log.histogramAwareEnabledDurationMs[0], 1, 2,
+                1);
+        validateProtoHistBucket("Duration[1] #1", log.histogramAwareEnabledDurationMs[1], 5, 6,
+                1);
+        collector.checkThat(log.enabledTimeMs, equalTo(6L));
+
+        // create another partial record
+        setTime(15);
+        mDut.recordEnableAware();
+
+        setTime(17);
+        log = mDut.consolidateProto();
+        collector.checkThat(countAllHistogramSamples(log.histogramAwareEnabledDurationMs),
+                equalTo(2));
+        validateProtoHistBucket("Duration[0] #2", log.histogramAwareEnabledDurationMs[0], 1, 2,
+                1);
+        validateProtoHistBucket("Duration[1] #2", log.histogramAwareEnabledDurationMs[1], 5, 6,
+                1);
+        collector.checkThat(log.enabledTimeMs, equalTo(8L)); // the partial record of 2ms
+
+
+        // clear and continue that partial record (verify completed)
+        mDut.clear();
+        setTime(23);
+        mDut.recordDisableAware();
+
+        log = mDut.consolidateProto();
+        collector.checkThat(countAllHistogramSamples(log.histogramAwareEnabledDurationMs),
+                equalTo(1));
+        validateProtoHistBucket("Duration[0] #3", log.histogramAwareEnabledDurationMs[0], 8, 9,
+                1);
+        collector.checkThat(log.enabledTimeMs, equalTo(6L)); // the remnant record of 6ms
+
+        // clear and verify empty records
+        mDut.clear();
+        log = mDut.consolidateProto();
+        collector.checkThat(countAllHistogramSamples(log.histogramAwareEnabledDurationMs),
+                equalTo(0));
+    }
+
+    @Test
+    public void testAttachSessionMetrics() {
+        final int uid1 = 1005;
+        final int uid2 = 1006;
+        final SparseArray<WifiAwareClientState> clients = new SparseArray<>();
+        WifiMetricsProto.WifiAwareLog log;
+
+        setTime(5);
+
+        // uid1: session 1
+        clients.put(10,
+                new WifiAwareClientState(mMockContext, 10, uid1, 0, null, null, null, false,
+                        mClock.getElapsedSinceBootMillis()));
+        mDut.recordAttachSession(uid1, false, clients);
+
+        // uid1: session 2
+        clients.put(11,
+                new WifiAwareClientState(mMockContext, 11, uid1, 0, null, null, null, false,
+                        mClock.getElapsedSinceBootMillis()));
+        mDut.recordAttachSession(uid1, false, clients);
+
+        // uid2: session 1
+        clients.put(12,
+                new WifiAwareClientState(mMockContext, 12, uid2, 0, null, null, null, false,
+                        mClock.getElapsedSinceBootMillis()));
+        mDut.recordAttachSession(uid2, false, clients);
+
+        // uid2: session 2
+        clients.put(13,
+                new WifiAwareClientState(mMockContext, 13, uid2, 0, null, null, null, true,
+                        mClock.getElapsedSinceBootMillis()));
+        mDut.recordAttachSession(uid2, true, clients);
+
+        // uid2: delete session 1
+        setTime(10);
+        mDut.recordAttachSessionDuration(clients.get(12).getCreationTime());
+        clients.delete(12);
+
+        // uid2: delete session 2
+        setTime(15);
+        mDut.recordAttachSessionDuration(clients.get(13).getCreationTime());
+        clients.delete(13);
+
+        // uid2: session 3
+        clients.put(14,
+                new WifiAwareClientState(mMockContext, 14, uid2, 0, null, null, null, false,
+                        mClock.getElapsedSinceBootMillis()));
+        mDut.recordAttachSession(uid2, false, clients);
+
+        // a few failures
+        mDut.recordAttachStatus(NanStatusType.INTERNAL_FAILURE);
+        mDut.recordAttachStatus(NanStatusType.INTERNAL_FAILURE);
+        mDut.recordAttachStatus(-5); // invalid
+
+        // verify
+        log = mDut.consolidateProto();
+
+        collector.checkThat("numApps", log.numApps, equalTo(2));
+        collector.checkThat("numAppsUsingIdentityCallback", log.numAppsUsingIdentityCallback,
+                equalTo(1));
+        collector.checkThat("maxConcurrentAttachSessionsInApp",
+                log.maxConcurrentAttachSessionsInApp, equalTo(2));
+        collector.checkThat("histogramAttachSessionStatus.length",
+                log.histogramAttachSessionStatus.length, equalTo(3)); // 3 buckets
+        collector.checkThat("histogramAttachDurationMs.length",
+                log.histogramAttachDurationMs.length, equalTo(2));
+        validateProtoHistBucket("Duration[0]", log.histogramAttachDurationMs[0], 5, 6, 1);
+        validateProtoHistBucket("Duration[1]", log.histogramAttachDurationMs[1], 10, 20, 1);
+    }
+
+    @Test
+    public void testDiscoverySessionMetrics() {
+        final int uid1 = 1005;
+        final int uid2 = 1006;
+        final SparseArray<WifiAwareClientState> clients = new SparseArray<>();
+        WifiMetricsProto.WifiAwareLog log;
+
+        setTime(5);
+        WifiAwareClientState client1 = new WifiAwareClientState(mMockContext, 10, uid1, 0, null,
+                null, null, false, 0);
+        WifiAwareClientState client2 = new WifiAwareClientState(mMockContext, 11, uid2, 0, null,
+                null, null, false, 0);
+        clients.put(10, client1);
+        clients.put(11, client2);
+
+        // uid1: publish session 1
+        client1.addSession(new WifiAwareDiscoverySessionState(null, 100, (byte) 0, null, true,
+                mClock.getElapsedSinceBootMillis()));
+        mDut.recordDiscoverySession(uid1, true, clients);
+        mDut.recordDiscoveryStatus(uid1, NanStatusType.SUCCESS, true);
+
+        // uid1: publish session 2
+        client1.addSession(new WifiAwareDiscoverySessionState(null, 101, (byte) 0, null, true,
+                mClock.getElapsedSinceBootMillis()));
+        mDut.recordDiscoverySession(uid1, true, clients);
+        mDut.recordDiscoveryStatus(uid1, NanStatusType.SUCCESS, true);
+
+        // uid2: subscribe session 1
+        client2.addSession(new WifiAwareDiscoverySessionState(null, 102, (byte) 0, null, false,
+                mClock.getElapsedSinceBootMillis()));
+        mDut.recordDiscoverySession(uid2, false, clients);
+        mDut.recordDiscoveryStatus(uid2, NanStatusType.SUCCESS, false);
+
+        // uid2: publish session 2
+        client2.addSession(new WifiAwareDiscoverySessionState(null, 103, (byte) 0, null, true,
+                mClock.getElapsedSinceBootMillis()));
+        mDut.recordDiscoverySession(uid2, false, clients);
+        mDut.recordDiscoveryStatus(uid2, NanStatusType.SUCCESS, false);
+
+        // uid1: delete session 1
+        setTime(10);
+        mDut.recordDiscoverySessionDuration(client1.getSession(100).getCreationTime(),
+                client1.getSession(100).isPublishSession());
+        client1.removeSession(100);
+
+        // uid2: delete session 1
+        setTime(15);
+        mDut.recordDiscoverySessionDuration(client2.getSession(102).getCreationTime(),
+                client2.getSession(102).isPublishSession());
+        client2.removeSession(102);
+
+        // uid2: subscribe session 3
+        mDut.recordDiscoverySession(uid2, false, clients);
+        client2.addSession(new WifiAwareDiscoverySessionState(null, 104, (byte) 0, null, false,
+                mClock.getElapsedSinceBootMillis()));
+
+        // a few failures
+        mDut.recordDiscoveryStatus(uid1, NanStatusType.INTERNAL_FAILURE, true);
+        mDut.recordDiscoveryStatus(uid2, NanStatusType.INTERNAL_FAILURE, false);
+        mDut.recordDiscoveryStatus(uid2, NanStatusType.NO_RESOURCES_AVAILABLE, false);
+        mDut.recordAttachStatus(-5); // invalid
+
+        // verify
+        log = mDut.consolidateProto();
+
+        collector.checkThat("maxConcurrentPublishInApp", log.maxConcurrentPublishInApp, equalTo(2));
+        collector.checkThat("maxConcurrentSubscribeInApp", log.maxConcurrentSubscribeInApp,
+                equalTo(1));
+        collector.checkThat("maxConcurrentDiscoverySessionsInApp",
+                log.maxConcurrentDiscoverySessionsInApp, equalTo(2));
+        collector.checkThat("maxConcurrentPublishInSystem", log.maxConcurrentPublishInSystem,
+                equalTo(3));
+        collector.checkThat("maxConcurrentSubscribeInSystem", log.maxConcurrentSubscribeInSystem,
+                equalTo(1));
+        collector.checkThat("maxConcurrentDiscoverySessionsInSystem",
+                log.maxConcurrentDiscoverySessionsInSystem, equalTo(4));
+        collector.checkThat("histogramPublishStatus.length",
+                log.histogramPublishStatus.length, equalTo(2)); // 2 buckets
+        collector.checkThat("histogramSubscribeStatus.length",
+                log.histogramSubscribeStatus.length, equalTo(3)); // 3 buckets
+        collector.checkThat("numAppsWithDiscoverySessionFailureOutOfResources",
+                log.numAppsWithDiscoverySessionFailureOutOfResources, equalTo(1));
+        validateProtoHistBucket("Publish Duration[0]", log.histogramPublishSessionDurationMs[0], 5,
+                6, 1);
+        validateProtoHistBucket("Subscribe Duration[0]", log.histogramSubscribeSessionDurationMs[0],
+                10, 20, 1);
+    }
+
+    /**
+     * Validate the data-path (NDP & NDI) metrics.
+     */
+    @Test
+    public void testDataPathMetrics() {
+        final int uid1 = 1005;
+        final int uid2 = 1006;
+        final String ndi0 = "aware_data0";
+        final String ndi1 = "aware_data1";
+        Map<WifiAwareNetworkSpecifier, WifiAwareDataPathStateManager.AwareNetworkRequestInformation>
+                networkRequestCache = new HashMap<>();
+        WifiMetricsProto.WifiAwareLog log;
+
+        setTime(5);
+
+        // uid1: ndp (non-secure) on ndi0
+        addNetworkInfoToCache(networkRequestCache, 10, uid1, ndi0, null);
+        mDut.recordNdpCreation(uid1, networkRequestCache);
+        setTime(7); // 2ms creation time
+        mDut.recordNdpStatus(NanStatusType.SUCCESS, false, 5);
+
+        // uid2: ndp (non-secure) on ndi0
+        WifiAwareNetworkSpecifier ns = addNetworkInfoToCache(networkRequestCache, 11, uid2, ndi0,
+                null);
+        mDut.recordNdpCreation(uid2, networkRequestCache);
+        setTime(10); // 3 ms creation time
+        mDut.recordNdpStatus(NanStatusType.SUCCESS, false, 7);
+
+        // uid2: ndp (secure) on ndi1 (OOB)
+        addNetworkInfoToCache(networkRequestCache, 12, uid2, ndi1, "passphrase of some kind");
+        mDut.recordNdpCreation(uid2, networkRequestCache);
+        setTime(25); // 15 ms creation time
+        mDut.recordNdpStatus(NanStatusType.SUCCESS, true, 10);
+
+        // uid2: ndp (secure) on ndi0 (OOB)
+        addNetworkInfoToCache(networkRequestCache, 13, uid2, ndi0, "super secret password");
+        mDut.recordNdpCreation(uid2, networkRequestCache);
+        setTime(36); // 11 ms creation time
+        mDut.recordNdpStatus(NanStatusType.SUCCESS, true, 25);
+
+        // uid2: delete the first NDP
+        networkRequestCache.remove(ns);
+
+        // uid2: ndp (non-secure) on ndi0
+        addNetworkInfoToCache(networkRequestCache, 14, uid2, ndi0, null);
+        mDut.recordNdpCreation(uid2, networkRequestCache);
+        setTime(37); // 1 ms creation time!
+        mDut.recordNdpStatus(NanStatusType.SUCCESS, false, 36);
+
+        // a few error codes
+        mDut.recordNdpStatus(NanStatusType.INTERNAL_FAILURE, false, 0);
+        mDut.recordNdpStatus(NanStatusType.INTERNAL_FAILURE, false, 0);
+        mDut.recordNdpStatus(NanStatusType.NO_RESOURCES_AVAILABLE, false, 0);
+
+        // and some durations
+        setTime(150);
+        mDut.recordNdpSessionDuration(7);   // 143ms
+        mDut.recordNdpSessionDuration(10);  // 140ms
+        mDut.recordNdpSessionDuration(25);  // 125ms
+        mDut.recordNdpSessionDuration(140); // 10ms
+
+        //verify
+        log = mDut.consolidateProto();
+
+        collector.checkThat("maxConcurrentNdiInApp", log.maxConcurrentNdiInApp, equalTo(2));
+        collector.checkThat("maxConcurrentNdiInSystem", log.maxConcurrentNdiInSystem, equalTo(2));
+        collector.checkThat("maxConcurrentNdpInApp", log.maxConcurrentNdpInApp, equalTo(3));
+        collector.checkThat("maxConcurrentNdpInSystem", log.maxConcurrentNdpInSystem, equalTo(4));
+        collector.checkThat("maxConcurrentSecureNdpInApp", log.maxConcurrentSecureNdpInApp,
+                equalTo(2));
+        collector.checkThat("maxConcurrentSecureNdpInSystem", log.maxConcurrentSecureNdpInSystem,
+                equalTo(2));
+        collector.checkThat("maxConcurrentNdpPerNdi", log.maxConcurrentNdpPerNdi, equalTo(3));
+        collector.checkThat("histogramRequestNdpStatus.length",
+                log.histogramRequestNdpStatus.length, equalTo(3));
+        collector.checkThat("histogramRequestNdpOobStatus.length",
+                log.histogramRequestNdpOobStatus.length, equalTo(1));
+
+        collector.checkThat("ndpCreationTimeMsMin", log.ndpCreationTimeMsMin, equalTo(1L));
+        collector.checkThat("ndpCreationTimeMsMax", log.ndpCreationTimeMsMax, equalTo(15L));
+        collector.checkThat("ndpCreationTimeMsSum", log.ndpCreationTimeMsSum, equalTo(32L));
+        collector.checkThat("ndpCreationTimeMsSumOfSq", log.ndpCreationTimeMsSumOfSq,
+                equalTo(360L));
+        collector.checkThat("ndpCreationTimeMsNumSamples", log.ndpCreationTimeMsNumSamples,
+                equalTo(5L));
+        validateProtoHistBucket("Creation[0]", log.histogramNdpCreationTimeMs[0], 1, 2, 1);
+        validateProtoHistBucket("Creation[1]", log.histogramNdpCreationTimeMs[1], 2, 3, 1);
+        validateProtoHistBucket("Creation[2]", log.histogramNdpCreationTimeMs[2], 3, 4, 1);
+        validateProtoHistBucket("Creation[3]", log.histogramNdpCreationTimeMs[3], 10, 20, 2);
+
+        validateProtoHistBucket("Duration[0]", log.histogramNdpSessionDurationMs[0], 10, 20, 1);
+        validateProtoHistBucket("Duration[1]", log.histogramNdpSessionDurationMs[1], 100, 200, 3);
+    }
+
+    /**
+     * Validate that the histogram configuration is initialized correctly: bucket starting points
+     * and sub-bucket widths.
+     */
+    @Test
+    public void testHistParamInit() {
+        collector.checkThat("HIST1.mLog", HIST1.mLog, equalTo(Math.log(10)));
+        collector.checkThat("HIST1.bb[0]", HIST1.bb[0], equalTo(1.0));
+        collector.checkThat("HIST1.bb[1]", HIST1.bb[1], equalTo(10.0));
+        collector.checkThat("HIST1.bb[2]", HIST1.bb[2], equalTo(100.0));
+        collector.checkThat("HIST1.bb[3]", HIST1.bb[3], equalTo(1000.0));
+        collector.checkThat("HIST1.bb[4]", HIST1.bb[4], equalTo(10000.0));
+        collector.checkThat("HIST1.bb[5]", HIST1.bb[5], equalTo(100000.0));
+        collector.checkThat("HIST1.sbw[0]", HIST1.sbw[0], equalTo(1.0));
+        collector.checkThat("HIST1.sbw[1]", HIST1.sbw[1], equalTo(10.0));
+        collector.checkThat("HIST1.sbw[2]", HIST1.sbw[2], equalTo(100.0));
+        collector.checkThat("HIST1.sbw[3]", HIST1.sbw[3], equalTo(1000.0));
+        collector.checkThat("HIST1.sbw[4]", HIST1.sbw[4], equalTo(10000.0));
+        collector.checkThat("HIST1.sbw[5]", HIST1.sbw[5], equalTo(100000.0));
+
+        collector.checkThat("HIST2.mLog", HIST1.mLog, equalTo(Math.log(10)));
+        collector.checkThat("HIST2.bb[0]", HIST2.bb[0], equalTo(-18.0));
+        collector.checkThat("HIST2.bb[1]", HIST2.bb[1], equalTo(-10.0));
+        collector.checkThat("HIST2.bb[2]", HIST2.bb[2], equalTo(30.0));
+        collector.checkThat("HIST2.sbw[0]", HIST2.sbw[0], equalTo(0.2));
+        collector.checkThat("HIST2.sbw[1]", HIST2.sbw[1], equalTo(1.0));
+        collector.checkThat("HIST2.sbw[2]", HIST2.sbw[2], equalTo(5.0));
+    }
+
+    /**
+     * Validate that a set of values are bucketed correctly into the histogram, and that they are
+     * converted to a primitive proto-buffer array correctly.
+     */
+    @Test
+    public void testHistBucketing() {
+        SparseIntArray hist1 = new SparseIntArray();
+        SparseIntArray hist2 = new SparseIntArray();
+
+        bucketValueAndVerify("HIST1: x=", -5, hist1, HIST1, 0, 1);
+        bucketValueAndVerify("HIST1: x=", 0, hist1, HIST1, 0, 2);
+        bucketValueAndVerify("HIST1: x=", 1, hist1, HIST1, 0, 3);
+        bucketValueAndVerify("HIST1: x=", 9, hist1, HIST1, 8, 1);
+        bucketValueAndVerify("HIST1: x=", 10, hist1, HIST1, 9, 1);
+        bucketValueAndVerify("HIST1: x=", 99, hist1, HIST1, 17, 1);
+        bucketValueAndVerify("HIST1: x=", 100, hist1, HIST1, 18, 1);
+        bucketValueAndVerify("HIST1: x=", 989, hist1, HIST1, 26, 1);
+        bucketValueAndVerify("HIST1: x=", 990, hist1, HIST1, 26, 2);
+        bucketValueAndVerify("HIST1: x=", 999, hist1, HIST1, 26, 3);
+        bucketValueAndVerify("HIST1: x=", 1000, hist1, HIST1, 27, 1);
+        bucketValueAndVerify("HIST1: x=", 9899, hist1, HIST1, 35, 1);
+        bucketValueAndVerify("HIST1: x=", 9900, hist1, HIST1, 35, 2);
+        bucketValueAndVerify("HIST1: x=", 9999, hist1, HIST1, 35, 3);
+        bucketValueAndVerify("HIST1: x=", 10000, hist1, HIST1, 36, 1);
+        bucketValueAndVerify("HIST1: x=", 98999, hist1, HIST1, 44, 1);
+        bucketValueAndVerify("HIST1: x=", 99000, hist1, HIST1, 44, 2);
+        bucketValueAndVerify("HIST1: x=", 99999, hist1, HIST1, 44, 3);
+        bucketValueAndVerify("HIST1: x=", 100000, hist1, HIST1, 45, 1);
+        bucketValueAndVerify("HIST1: x=", 989999, hist1, HIST1, 53, 1);
+        bucketValueAndVerify("HIST1: x=", 990000, hist1, HIST1, 53, 2);
+        bucketValueAndVerify("HIST1: x=", 999999, hist1, HIST1, 53, 3);
+        bucketValueAndVerify("HIST1: x=", 1000000, hist1, HIST1, 53, 4);
+        bucketValueAndVerify("HIST1: x=", 1000001, hist1, HIST1, 53, 5);
+        bucketValueAndVerify("HIST1: x=", 5000000, hist1, HIST1, 53, 6);
+        bucketValueAndVerify("HIST1: x=", 10000000, hist1, HIST1, 53, 7);
+
+        WifiMetricsProto.WifiAwareLog.HistogramBucket[] phb1 = histogramToProtoArray(hist1, HIST1);
+        collector.checkThat("Number of buckets #1", phb1.length, equalTo(hist1.size()));
+        validateProtoHistBucket("Bucket1[0]", phb1[0], 1, 2, 3);
+        validateProtoHistBucket("Bucket1[1]", phb1[1], 9, 10, 1);
+        validateProtoHistBucket("Bucket1[2]", phb1[2], 10, 20, 1);
+        validateProtoHistBucket("Bucket1[3]", phb1[3], 90, 100, 1);
+        validateProtoHistBucket("Bucket1[4]", phb1[4], 100, 200, 1);
+        validateProtoHistBucket("Bucket1[5]", phb1[5], 900, 1000, 3);
+        validateProtoHistBucket("Bucket1[6]", phb1[6], 1000, 2000, 1);
+        validateProtoHistBucket("Bucket1[7]", phb1[7], 9000, 10000, 3);
+        validateProtoHistBucket("Bucket1[8]", phb1[8], 10000, 20000, 1);
+        validateProtoHistBucket("Bucket1[9]", phb1[9], 90000, 100000, 3);
+        validateProtoHistBucket("Bucket1[10]", phb1[10], 100000, 200000, 1);
+        validateProtoHistBucket("Bucket1[11]", phb1[11], 900000, 1000000, 7);
+
+        bucketValueAndVerify("HIST2: x=", -20, hist2, HIST2, 0, 1);
+        bucketValueAndVerify("HIST2: x=", -18, hist2, HIST2, 0, 2);
+        bucketValueAndVerify("HIST2: x=", -17, hist2, HIST2, 5, 1);
+        bucketValueAndVerify("HIST2: x=", -11, hist2, HIST2, 35, 1);
+        bucketValueAndVerify("HIST2: x=", -10, hist2, HIST2, 40, 1);
+        bucketValueAndVerify("HIST2: x=", 29, hist2, HIST2, 79, 1);
+        bucketValueAndVerify("HIST2: x=", 30, hist2, HIST2, 80, 1);
+        bucketValueAndVerify("HIST2: x=", 229, hist2, HIST2, 119, 1);
+        bucketValueAndVerify("HIST2: x=", 230, hist2, HIST2, 119, 2);
+        bucketValueAndVerify("HIST2: x=", 300, hist2, HIST2, 119, 3);
+        bucketValueAndVerify("HIST2: x=", 1000000, hist2, HIST2, 119, 4);
+
+        WifiMetricsProto.WifiAwareLog.HistogramBucket[] phb2 = histogramToProtoArray(hist2, HIST2);
+        collector.checkThat("Number of buckets #2", phb2.length, equalTo(hist2.size()));
+        validateProtoHistBucket("Bucket2[0]", phb2[0], -18, -17, 2);
+        validateProtoHistBucket("Bucket2[1]", phb2[1], -17, -16, 1);
+        validateProtoHistBucket("Bucket2[2]", phb2[2], -11, -10, 1);
+        validateProtoHistBucket("Bucket2[3]", phb2[3], -10, -9, 1);
+        validateProtoHistBucket("Bucket2[4]", phb2[4], 29, 30, 1);
+        validateProtoHistBucket("Bucket2[5]", phb2[5], 30, 35, 1);
+        validateProtoHistBucket("Bucket2[6]", phb2[6], 225, 230, 4);
+    }
+
+    /**
+     * Validate the conversion to a NanStatusType proto raw histogram.
+     */
+    @Test
+    public void testNanStatusTypeHistogram() {
+        SparseIntArray statusHistogram = new SparseIntArray();
+
+        addNanHalStatusToHistogram(NanStatusType.SUCCESS, statusHistogram);
+        addNanHalStatusToHistogram(-1, statusHistogram);
+        addNanHalStatusToHistogram(NanStatusType.ALREADY_ENABLED, statusHistogram);
+        addNanHalStatusToHistogram(NanStatusType.SUCCESS, statusHistogram);
+        addNanHalStatusToHistogram(NanStatusType.INTERNAL_FAILURE, statusHistogram);
+        addNanHalStatusToHistogram(NanStatusType.SUCCESS, statusHistogram);
+        addNanHalStatusToHistogram(NanStatusType.INTERNAL_FAILURE, statusHistogram);
+        addNanHalStatusToHistogram(55, statusHistogram);
+        addNanHalStatusToHistogram(65, statusHistogram);
+
+        WifiMetricsProto.WifiAwareLog.NanStatusHistogramBucket[] sh = histogramToProtoArray(
+                statusHistogram);
+        collector.checkThat("Number of buckets", sh.length, equalTo(4));
+        validateNanStatusProtoHistBucket("Bucket[SUCCESS]", sh[0],
+                WifiMetricsProto.WifiAwareLog.SUCCESS, 3);
+        validateNanStatusProtoHistBucket("Bucket[INTERNAL_FAILURE]", sh[1],
+                WifiMetricsProto.WifiAwareLog.INTERNAL_FAILURE, 2);
+        validateNanStatusProtoHistBucket("Bucket[ALREADY_ENABLED]", sh[2],
+                WifiMetricsProto.WifiAwareLog.ALREADY_ENABLED, 1);
+        validateNanStatusProtoHistBucket("Bucket[UNKNOWN_HAL_STATUS]", sh[3],
+                WifiMetricsProto.WifiAwareLog.UNKNOWN_HAL_STATUS, 3);
+    }
+
+    // utilities
+
+    /**
+     * Mock the elapsed time since boot to the input argument.
+     */
+    private void setTime(long timeMs) {
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(timeMs);
+    }
+
+    /**
+     * Sum all the 'count' entries in the histogram array.
+     */
+    private int countAllHistogramSamples(WifiMetricsProto.WifiAwareLog.HistogramBucket[] hba) {
+        int sum = 0;
+        for (WifiMetricsProto.WifiAwareLog.HistogramBucket hb: hba) {
+            sum += hb.count;
+        }
+        return sum;
+    }
+
+    private int countAllHistogramSamples(
+            WifiMetricsProto.WifiAwareLog.NanStatusHistogramBucket[] nshba) {
+        int sum = 0;
+        for (WifiMetricsProto.WifiAwareLog.NanStatusHistogramBucket nshb: nshba) {
+            sum += nshb.count;
+        }
+        return sum;
+    }
+
+    private void bucketValueAndVerify(String logPrefix, long value, SparseIntArray h,
+            WifiAwareMetrics.HistParms hp, int expectedKey, int expectedValue) {
+        WifiAwareMetrics.addLogValueToHistogram(value, h, hp);
+        collector.checkThat(logPrefix + value, h.get(expectedKey), equalTo(expectedValue));
+    }
+
+    private void validateProtoHistBucket(String logPrefix,
+            WifiMetricsProto.WifiAwareLog.HistogramBucket bucket, long start, long end, int count) {
+        collector.checkThat(logPrefix + ": start", bucket.start, equalTo(start));
+        collector.checkThat(logPrefix + ": end", bucket.end, equalTo(end));
+        collector.checkThat(logPrefix + ": count", bucket.count, equalTo(count));
+    }
+
+    private void validateNanStatusProtoHistBucket(String logPrefix,
+            WifiMetricsProto.WifiAwareLog.NanStatusHistogramBucket bucket, int type, int count) {
+        collector.checkThat(logPrefix + ": type", bucket.nanStatusType, equalTo(type));
+        collector.checkThat(logPrefix + ": count", bucket.count, equalTo(count));
+    }
+
+    private WifiAwareNetworkSpecifier addNetworkInfoToCache(
+            Map<WifiAwareNetworkSpecifier, WifiAwareDataPathStateManager
+                    .AwareNetworkRequestInformation> networkRequestCache,
+            int index, int uid, String interfaceName, String passphrase) {
+        WifiAwareNetworkSpecifier ns = new WifiAwareNetworkSpecifier(0, 0, 0, index, 0, null, null,
+                passphrase, 0);
+        WifiAwareDataPathStateManager.AwareNetworkRequestInformation anri =
+                new WifiAwareDataPathStateManager.AwareNetworkRequestInformation();
+        anri.networkSpecifier = ns;
+        anri.state = WifiAwareDataPathStateManager.AwareNetworkRequestInformation
+                .STATE_RESPONDER_CONFIRMED;
+        anri.uid = uid;
+        anri.interfaceName = interfaceName;
+
+        networkRequestCache.put(ns, anri);
+        return ns;
+    }
+
+    private void dumpDut(String prefix) {
+        StringWriter sw = new StringWriter();
+        mDut.dump(null, new PrintWriter(sw), null);
+        Log.e("WifiAwareMetrics", prefix + sw.toString());
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareNativeApiTest.java b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareNativeApiTest.java
new file mode 100644
index 0000000..9564189
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareNativeApiTest.java
@@ -0,0 +1,218 @@
+/*
+ * 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.aware;
+
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyShort;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.wifi.V1_0.IWifiNanIface;
+import android.hardware.wifi.V1_0.NanBandIndex;
+import android.hardware.wifi.V1_0.NanConfigRequest;
+import android.hardware.wifi.V1_0.NanEnableRequest;
+import android.hardware.wifi.V1_0.WifiStatus;
+import android.hardware.wifi.V1_0.WifiStatusCode;
+import android.net.wifi.aware.ConfigRequest;
+import android.os.RemoteException;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ErrorCollector;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.PrintWriter;
+
+/**
+ * Unit test harness for WifiAwareNativeApi
+ */
+public class WifiAwareNativeApiTest {
+    @Mock WifiAwareNativeManager mWifiAwareNativeManagerMock;
+    @Mock IWifiNanIface mIWifiNanIfaceMock;
+
+    @Rule public ErrorCollector collector = new ErrorCollector();
+
+    private WifiAwareNativeApi mDut;
+
+    /**
+     * Initializes mocks.
+     */
+    @Before
+    public void setup() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        when(mWifiAwareNativeManagerMock.getWifiNanIface()).thenReturn(mIWifiNanIfaceMock);
+
+        WifiStatus status = new WifiStatus();
+        status.code = WifiStatusCode.SUCCESS;
+        when(mIWifiNanIfaceMock.enableRequest(anyShort(), any())).thenReturn(status);
+        when(mIWifiNanIfaceMock.configRequest(anyShort(), any())).thenReturn(status);
+
+        mDut = new WifiAwareNativeApi(mWifiAwareNativeManagerMock);
+    }
+
+    /**
+     * Test that the set parameter shell command executor works when parameters are valid.
+     */
+    @Test
+    public void testSetParameterShellCommandSuccess() {
+        setSettableParam(WifiAwareNativeApi.PARAM_DW_ON_IDLE_5GHZ, Integer.toString(1), true);
+    }
+
+    /**
+     * Test that the set parameter shell command executor fails on incorrect name.
+     */
+    @Test
+    public void testSetParameterShellCommandInvalidParameterName() {
+        setSettableParam("XXX", Integer.toString(1), false);
+    }
+
+    /**
+     * Test that the set parameter shell command executor fails on invalid value (not convertible
+     * to an int).
+     */
+    @Test
+    public void testSetParameterShellCommandInvalidValue() {
+        setSettableParam(WifiAwareNativeApi.PARAM_DW_ON_IDLE_5GHZ, "garbage", false);
+    }
+
+    /**
+     * Validate that the configuration parameters used to manage power state behavior is
+     * using default values at the default power state.
+     */
+    @Test
+    public void testEnableAndConfigPowerSettingsDefaults() throws RemoteException {
+        NanConfigRequest config = validateEnableAndConfigure((short) 10,
+                new ConfigRequest.Builder().build(), true, true, true, false);
+
+        collector.checkThat("validDiscoveryWindowIntervalVal-5", false,
+                equalTo(config.bandSpecificConfig[NanBandIndex.NAN_BAND_5GHZ]
+                        .validDiscoveryWindowIntervalVal));
+        collector.checkThat("validDiscoveryWindowIntervalVal-24", false,
+                equalTo(config.bandSpecificConfig[NanBandIndex.NAN_BAND_24GHZ]
+                        .validDiscoveryWindowIntervalVal));
+    }
+
+    /**
+     * Validate that the configuration parameters used to manage power state behavior is
+     * using the specified non-interactive values when in that power state.
+     */
+    @Test
+    public void testEnableAndConfigPowerSettingsNoneInteractive() throws RemoteException {
+        byte interactive5 = 2;
+        byte interactive24 = 3;
+
+        setPowerConfigurationParams(interactive5, interactive24, (byte) -1, (byte) -1);
+        NanConfigRequest config = validateEnableAndConfigure((short) 10,
+                new ConfigRequest.Builder().build(), false, false, false, false);
+
+        collector.checkThat("validDiscoveryWindowIntervalVal-5", true,
+                equalTo(config.bandSpecificConfig[NanBandIndex.NAN_BAND_5GHZ]
+                        .validDiscoveryWindowIntervalVal));
+        collector.checkThat("discoveryWindowIntervalVal-5", interactive5,
+                equalTo(config.bandSpecificConfig[NanBandIndex.NAN_BAND_5GHZ]
+                        .discoveryWindowIntervalVal));
+        collector.checkThat("validDiscoveryWindowIntervalVal-24", true,
+                equalTo(config.bandSpecificConfig[NanBandIndex.NAN_BAND_24GHZ]
+                        .validDiscoveryWindowIntervalVal));
+        collector.checkThat("discoveryWindowIntervalVal-24", interactive24,
+                equalTo(config.bandSpecificConfig[NanBandIndex.NAN_BAND_24GHZ]
+                        .discoveryWindowIntervalVal));
+    }
+
+    /**
+     * Validate that the configuration parameters used to manage power state behavior is
+     * using the specified idle (doze) values when in that power state.
+     */
+    @Test
+    public void testEnableAndConfigPowerSettingsIdle() throws RemoteException {
+        byte idle5 = 2;
+        byte idle24 = -1;
+
+        setPowerConfigurationParams((byte) -1, (byte) -1, idle5, idle24);
+        NanConfigRequest config = validateEnableAndConfigure((short) 10,
+                new ConfigRequest.Builder().build(), false, true, false, true);
+
+        collector.checkThat("validDiscoveryWindowIntervalVal-5", true,
+                equalTo(config.bandSpecificConfig[NanBandIndex.NAN_BAND_5GHZ]
+                        .validDiscoveryWindowIntervalVal));
+        collector.checkThat("discoveryWindowIntervalVal-5", idle5,
+                equalTo(config.bandSpecificConfig[NanBandIndex.NAN_BAND_5GHZ]
+                        .discoveryWindowIntervalVal));
+        collector.checkThat("validDiscoveryWindowIntervalVal-24", false,
+                equalTo(config.bandSpecificConfig[NanBandIndex.NAN_BAND_24GHZ]
+                        .validDiscoveryWindowIntervalVal));
+    }
+
+    // utilities
+
+    private void setPowerConfigurationParams(byte interactive5, byte interactive24, byte idle5,
+            byte idle24) {
+        setSettableParam(WifiAwareNativeApi.PARAM_DW_ON_INACTIVE_5GHZ,
+                Integer.toString(interactive5), true);
+        setSettableParam(WifiAwareNativeApi.PARAM_DW_ON_INACTIVE_24GHZ,
+                Integer.toString(interactive24), true);
+        setSettableParam(WifiAwareNativeApi.PARAM_DW_ON_IDLE_5GHZ, Integer.toString(idle5), true);
+        setSettableParam(WifiAwareNativeApi.PARAM_DW_ON_IDLE_24GHZ, Integer.toString(idle24), true);
+    }
+
+    private void setSettableParam(String name, String value, boolean expectSuccess) {
+        PrintWriter pwMock = mock(PrintWriter.class);
+        WifiAwareShellCommand parentShellMock = mock(WifiAwareShellCommand.class);
+        when(parentShellMock.getNextArgRequired()).thenReturn("set").thenReturn(name).thenReturn(
+                value);
+        when(parentShellMock.getErrPrintWriter()).thenReturn(pwMock);
+
+        collector.checkThat(mDut.onCommand(parentShellMock), equalTo(expectSuccess ? 0 : -1));
+    }
+
+    private NanConfigRequest validateEnableAndConfigure(short transactionId,
+            ConfigRequest configRequest, boolean notifyIdentityChange, boolean initialConfiguration,
+            boolean isInteractive, boolean isIdle) throws RemoteException {
+        mDut.enableAndConfigure(transactionId, configRequest, notifyIdentityChange,
+                initialConfiguration, isInteractive, isIdle);
+
+        ArgumentCaptor<NanEnableRequest> enableReqCaptor = ArgumentCaptor.forClass(
+                NanEnableRequest.class);
+        ArgumentCaptor<NanConfigRequest> configReqCaptor = ArgumentCaptor.forClass(
+                NanConfigRequest.class);
+        NanConfigRequest config;
+
+        if (initialConfiguration) {
+            verify(mIWifiNanIfaceMock).enableRequest(eq(transactionId), enableReqCaptor.capture());
+            config = enableReqCaptor.getValue().configParams;
+        } else {
+            verify(mIWifiNanIfaceMock).configRequest(eq(transactionId), configReqCaptor.capture());
+            config = configReqCaptor.getValue();
+        }
+
+        collector.checkThat("disableDiscoveryAddressChangeIndication", !notifyIdentityChange,
+                equalTo(config.disableDiscoveryAddressChangeIndication));
+        collector.checkThat("disableStartedClusterIndication", !notifyIdentityChange,
+                equalTo(config.disableStartedClusterIndication));
+        collector.checkThat("disableJoinedClusterIndication", !notifyIdentityChange,
+                equalTo(config.disableJoinedClusterIndication));
+
+        return config;
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareNativeManagerTest.java b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareNativeManagerTest.java
index 50a2e0d..ba984b8 100644
--- a/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareNativeManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareNativeManagerTest.java
@@ -73,6 +73,7 @@
 
         mDut = new WifiAwareNativeManager(mWifiAwareStateManagerMock,
                 mHalDeviceManager, mWifiAwareNativeCallback);
+        mDut.start();
 
         mInOrder = inOrder(mWifiAwareStateManagerMock, mHalDeviceManager);
     }
diff --git a/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareServiceImplTest.java b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareServiceImplTest.java
index b5e12d2..d666262 100644
--- a/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareServiceImplTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareServiceImplTest.java
@@ -42,6 +42,8 @@
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 
+import com.android.server.wifi.util.WifiPermissionsWrapper;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
@@ -70,11 +72,15 @@
     @Mock
     private WifiAwareStateManager mAwareStateManagerMock;
     @Mock
+    private WifiAwareShellCommand mWifiAwareShellCommandMock;
+    @Mock
     private IBinder mBinderMock;
     @Mock
     private IWifiAwareEventCallback mCallbackMock;
     @Mock
     private IWifiAwareDiscoverySessionCallback mSessionCallbackMock;
+    @Mock private WifiAwareMetrics mAwareMetricsMock;
+    @Mock private WifiPermissionsWrapper mPermissionsWrapperMock;
 
     private HandlerThread mHandlerThread;
 
@@ -114,8 +120,10 @@
 
         mDut = new WifiAwareServiceImplSpy(mContextMock);
         mDut.fakeUid = mDefaultUid;
-        mDut.start(mHandlerThreadMock, mAwareStateManagerMock);
-        verify(mAwareStateManagerMock).start(eq(mContextMock), any());
+        mDut.start(mHandlerThreadMock, mAwareStateManagerMock, mWifiAwareShellCommandMock,
+                mAwareMetricsMock, mPermissionsWrapperMock);
+        verify(mAwareStateManagerMock).start(eq(mContextMock), any(), eq(mAwareMetricsMock),
+                eq(mPermissionsWrapperMock));
     }
 
     /**
diff --git a/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareStateManagerTest.java b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareStateManagerTest.java
index 2797c0e..faa87df 100644
--- a/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareStateManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareStateManagerTest.java
@@ -23,7 +23,9 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyByte;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyShort;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
@@ -38,8 +40,10 @@
 import android.app.AppOpsManager;
 import android.app.test.MockAnswerUtil;
 import android.app.test.TestAlarmManager;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.hardware.wifi.V1_0.NanStatusType;
 import android.net.ConnectivityManager;
@@ -50,13 +54,18 @@
 import android.net.wifi.aware.PublishConfig;
 import android.net.wifi.aware.SubscribeConfig;
 import android.net.wifi.aware.WifiAwareManager;
+import android.os.Handler;
+import android.os.IPowerManager;
 import android.os.Message;
+import android.os.PowerManager;
 import android.os.UserHandle;
 import android.os.test.TestLooper;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
 import android.util.SparseArray;
 
+import com.android.server.wifi.util.WifiPermissionsWrapper;
+
 import libcore.util.HexEncoding;
 
 import org.junit.Before;
@@ -86,11 +95,16 @@
     private TestLooper mMockLooper;
     private Random mRandomNg = new Random(15687);
     private WifiAwareStateManager mDut;
+    @Mock private WifiAwareNativeManager mMockNativeManager;
     @Mock private WifiAwareNativeApi mMockNative;
     @Mock private Context mMockContext;
     @Mock private AppOpsManager mMockAppOpsManager;
     @Mock private WifiAwareRttStateManager mMockAwareRttStateManager;
+    @Mock private WifiAwareMetrics mAwareMetricsMock;
+    @Mock private WifiPermissionsWrapper mPermissionsWrapperMock;
     TestAlarmManager mAlarmManager;
+    private PowerManager mMockPowerManager;
+    private BroadcastReceiver mPowerBcastReceiver;
     @Mock private WifiAwareDataPathStateManager mMockAwareDataPathStatemanager;
 
     @Rule
@@ -109,9 +123,18 @@
         when(mMockContext.getSystemService(Context.ALARM_SERVICE))
                 .thenReturn(mAlarmManager.getAlarmManager());
 
+        mMockLooper = new TestLooper();
+
+        IPowerManager powerManagerService = mock(IPowerManager.class);
+        mMockPowerManager = new PowerManager(mMockContext, powerManagerService,
+                new Handler(mMockLooper.getLooper()));
+
         when(mMockContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(
                 mock(ConnectivityManager.class));
         when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mMockAppOpsManager);
+        when(mMockContext.getSystemServiceName(PowerManager.class)).thenReturn(
+                Context.POWER_SERVICE);
+        when(mMockContext.getSystemService(PowerManager.class)).thenReturn(mMockPowerManager);
         when(mMockContext.checkPermission(eq(android.Manifest.permission.ACCESS_FINE_LOCATION),
                 anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_DENIED);
         when(mMockContext.checkPermission(eq(Manifest.permission.ACCESS_COARSE_LOCATION),
@@ -120,34 +143,75 @@
                 any())).thenReturn(AppOpsManager.MODE_ERRORED);
         when(mMockAppOpsManager.noteOp(eq(AppOpsManager.OP_COARSE_LOCATION), anyInt(),
                 any())).thenReturn(AppOpsManager.MODE_ERRORED);
+        when(mMockPowerManager.isDeviceIdleMode()).thenReturn(false);
+        when(mMockPowerManager.isInteractive()).thenReturn(true);
 
-        mMockLooper = new TestLooper();
-
+        ArgumentCaptor<BroadcastReceiver> bcastRxCaptor = ArgumentCaptor.forClass(
+                BroadcastReceiver.class);
         mDut = new WifiAwareStateManager();
-        mDut.setNative(mMockNative);
-        mDut.start(mMockContext, mMockLooper.getLooper());
+        mDut.setNative(mMockNativeManager, mMockNative);
+        mDut.start(mMockContext, mMockLooper.getLooper(), mAwareMetricsMock,
+                mPermissionsWrapperMock);
+        mDut.startLate();
+        mMockLooper.dispatchAll();
+        verify(mMockContext).registerReceiver(bcastRxCaptor.capture(), any(IntentFilter.class));
+        mPowerBcastReceiver = bcastRxCaptor.getValue();
         installMocksInStateManager(mDut, mMockAwareRttStateManager, mMockAwareDataPathStatemanager);
 
         when(mMockNative.enableAndConfigure(anyShort(), any(), anyBoolean(),
-                anyBoolean())).thenReturn(true);
+                anyBoolean(), anyBoolean(), anyBoolean())).thenReturn(true);
         when(mMockNative.disable(anyShort())).thenReturn(true);
-        when(mMockNative.publish(anyShort(), anyInt(), any())).thenReturn(true);
-        when(mMockNative.subscribe(anyShort(), anyInt(), any()))
+        when(mMockNative.publish(anyShort(), anyByte(), any())).thenReturn(true);
+        when(mMockNative.subscribe(anyShort(), anyByte(), any()))
                 .thenReturn(true);
-        when(mMockNative.sendMessage(anyShort(), anyInt(), anyInt(), any(),
+        when(mMockNative.sendMessage(anyShort(), anyByte(), anyInt(), any(),
                 any(), anyInt())).thenReturn(true);
-        when(mMockNative.stopPublish(anyShort(), anyInt())).thenReturn(true);
-        when(mMockNative.stopSubscribe(anyShort(), anyInt())).thenReturn(true);
+        when(mMockNative.stopPublish(anyShort(), anyByte())).thenReturn(true);
+        when(mMockNative.stopSubscribe(anyShort(), anyByte())).thenReturn(true);
         when(mMockNative.getCapabilities(anyShort())).thenReturn(true);
     }
 
     /**
+     * Test that the set parameter shell command executor works when parameters are valid.
+     */
+    @Test
+    public void testSetParameterShellCommandSuccess() {
+        setSettableParam(WifiAwareStateManager.PARAM_ON_IDLE_DISABLE_AWARE, Integer.toString(1),
+                true);
+    }
+
+    /**
+     * Test that the set parameter shell command executor fails on incorrect name.
+     */
+    @Test
+    public void testSetParameterShellCommandInvalidParameterName() {
+        setSettableParam("XXX", Integer.toString(1), false);
+    }
+
+    /**
+     * Test that the set parameter shell command executor fails on invalid value (not convertible
+     * to an int).
+     */
+    @Test
+    public void testSetParameterShellCommandInvalidValue() {
+        setSettableParam(WifiAwareStateManager.PARAM_ON_IDLE_DISABLE_AWARE, "garbage", false);
+    }
+
+    /**
      * Validate that Aware data-path interfaces are brought up and down correctly.
      */
     @Test
     public void testAwareDataPathInterfaceUpDown() throws Exception {
+        final int clientId = 12341;
+        final int uid = 1000;
+        final int pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
-        InOrder inOrder = inOrder(mMockContext, mMockNative, mMockAwareDataPathStatemanager);
+        InOrder inOrder = inOrder(mMockContext, mMockNative, mMockAwareDataPathStatemanager,
+                mockCallback);
 
         // (1) enable usage
         mDut.enableUsage();
@@ -156,17 +220,25 @@
         inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
         mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockAwareDataPathStatemanager).createAllInterfaces();
         collector.checkThat("usage enabled", mDut.isUsageEnabled(), equalTo(true));
 
-        // (2) disable usage
-        mDut.disableUsage();
+        // (2) connect (enable Aware)
+        mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockAwareDataPathStatemanager).onAwareDownCleanupDataPaths();
-        inOrder.verify(mMockNative).disable((short) 0);
-        validateCorrectAwareStatusChangeBroadcast(inOrder, false);
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(false), eq(true), eq(true), eq(false));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+        inOrder.verify(mMockAwareDataPathStatemanager).createAllInterfaces();
+
+        // (3) disconnect (disable Aware)
+        mDut.disconnect(clientId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).disable(transactionId.capture());
+        mDut.onDisableResponse(transactionId.getValue(), NanStatusType.SUCCESS);
+        mMockLooper.dispatchAll();
         inOrder.verify(mMockAwareDataPathStatemanager).deleteAllInterfaces();
-        collector.checkThat("usage disabled", mDut.isUsageEnabled(), equalTo(false));
 
         verifyNoMoreInteractions(mMockNative, mMockAwareDataPathStatemanager);
     }
@@ -200,13 +272,15 @@
         mDut.disableUsage();
         mMockLooper.dispatchAll();
         collector.checkThat("usage disabled", mDut.isUsageEnabled(), equalTo(false));
-        inOrder.verify(mMockNative).disable((short) 0);
+        inOrder.verify(mMockNative).disable(transactionId.capture());
+        mDut.onDisableResponse(transactionId.getValue(), NanStatusType.SUCCESS);
         validateCorrectAwareStatusChangeBroadcast(inOrder, false);
 
-        // (3) try connecting and validate that get nothing (app should be aware of non-availability
-        // through state change broadcast and/or query API)
+        // (3) try connecting and validate that get failure callback (though app should be aware of
+        // non-availability through state change broadcast and/or query API)
         mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
         mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectFail(anyInt());
 
         verifyNoMoreInteractions(mMockNative, mockCallback);
     }
@@ -226,12 +300,15 @@
 
         IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        ArgumentCaptor<SparseArray> sparseArrayCaptor = ArgumentCaptor.forClass(SparseArray.class);
         InOrder inOrder = inOrder(mMockContext, mMockNative, mockCallback);
+        InOrder inOrderM = inOrder(mAwareMetricsMock);
 
         // (1) check initial state
         mDut.enableUsage();
         mMockLooper.dispatchAll();
         validateCorrectAwareStatusChangeBroadcast(inOrder, true);
+        inOrderM.verify(mAwareMetricsMock).recordEnableUsage();
         inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
         mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
         mMockLooper.dispatchAll();
@@ -242,22 +319,33 @@
         mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true));
+                eq(false), eq(true), eq(true), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
+        inOrderM.verify(mAwareMetricsMock).recordAttachSession(eq(uid), eq(false),
+                sparseArrayCaptor.capture());
+        collector.checkThat("num of clients", sparseArrayCaptor.getValue().size(), equalTo(1));
 
         // (3) disable usage & verify callbacks
         mDut.disableUsage();
         mMockLooper.dispatchAll();
         collector.checkThat("usage disabled", mDut.isUsageEnabled(), equalTo(false));
-        inOrder.verify(mMockNative).disable((short) 0);
+        inOrder.verify(mMockNative).disable(transactionId.capture());
+        inOrderM.verify(mAwareMetricsMock).recordAttachSessionDuration(anyLong());
+        inOrderM.verify(mAwareMetricsMock).recordDisableAware();
+        inOrderM.verify(mAwareMetricsMock).recordDisableUsage();
         validateCorrectAwareStatusChangeBroadcast(inOrder, false);
         validateInternalClientInfoCleanedUp(clientId);
+        mDut.onDisableResponse(transactionId.getValue(), NanStatusType.SUCCESS);
+        mMockLooper.dispatchAll();
+        inOrderM.verify(mAwareMetricsMock).recordDisableAware();
 
-        // (4) try connecting again and validate that just get an onAwareDown
+        // (4) try connecting again and validate that get a failure
         mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
         mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectFail(anyInt());
+        inOrderM.verify(mAwareMetricsMock).recordAttachStatus(NanStatusType.INTERNAL_FAILURE);
 
         // (5) disable usage again and validate that not much happens
         mDut.disableUsage();
@@ -267,6 +355,7 @@
         // (6) enable usage
         mDut.enableUsage();
         mMockLooper.dispatchAll();
+        inOrderM.verify(mAwareMetricsMock).recordEnableUsage();
         collector.checkThat("usage enabled", mDut.isUsageEnabled(), equalTo(true));
         validateCorrectAwareStatusChangeBroadcast(inOrder, true);
 
@@ -274,12 +363,15 @@
         mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true));
+                eq(false), eq(true), eq(true), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
+        inOrderM.verify(mAwareMetricsMock).recordAttachSession(eq(uid), eq(false),
+                sparseArrayCaptor.capture());
+        collector.checkThat("num of clients", sparseArrayCaptor.getValue().size(), equalTo(1));
 
-        verifyNoMoreInteractions(mMockNative, mockCallback);
+        verifyNoMoreInteractions(mMockNative, mockCallback, mAwareMetricsMock);
     }
 
     /**
@@ -298,7 +390,7 @@
         InOrder inOrder = inOrder(mMockContext, mMockNative, mockCallback);
 
         when(mMockNative.enableAndConfigure(anyShort(), any(), anyBoolean(),
-                anyBoolean())).thenReturn(false);
+                anyBoolean(), eq(true), eq(false))).thenReturn(false);
 
         // (1) check initial state
         mDut.enableUsage();
@@ -312,7 +404,7 @@
         mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true));
+                eq(false), eq(true), eq(true), eq(false));
         inOrder.verify(mockCallback).onConnectFail(NanStatusType.INTERNAL_FAILURE);
 
         validateInternalClientInfoCleanedUp(clientId);
@@ -356,7 +448,7 @@
         mDut.connect(clientId1, uid, pid, callingPackage, mockCallback1, configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionIdCapture.capture(),
-                eq(configRequest), eq(false), eq(true));
+                eq(configRequest), eq(false), eq(true), eq(true), eq(false));
         short transactionId = transactionIdCapture.getValue();
         mDut.onConfigSuccessResponse(transactionId);
         mMockLooper.dispatchAll();
@@ -365,7 +457,7 @@
         mDut.connect(clientId2, uid, pid, callingPackage, mockCallback2, configRequest, true);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionIdCapture.capture(),
-                eq(configRequest), eq(true), eq(false));
+                eq(configRequest), eq(true), eq(false), eq(true), eq(false));
         transactionId = transactionIdCapture.getValue();
         mDut.onConfigSuccessResponse(transactionId);
         mMockLooper.dispatchAll();
@@ -430,10 +522,12 @@
                 IWifiAwareDiscoverySessionCallback.class);
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
         InOrder inOrder = inOrder(mMockNative, mockCallback, mockSessionCallback);
+        InOrder inOrderM = inOrder(mAwareMetricsMock);
 
         mDut.enableUsage();
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        inOrderM.verify(mAwareMetricsMock).recordEnableUsage();
         mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
         mMockLooper.dispatchAll();
 
@@ -441,29 +535,35 @@
         mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true));
+                eq(false), eq(true), eq(true), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
+        inOrderM.verify(mAwareMetricsMock).recordAttachSession(eq(uid), eq(false), any());
 
         // (2) publish + timeout
         mDut.publish(clientId, publishConfig, mockSessionCallback);
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).publish(anyShort(), eq(0), eq(publishConfig));
+        inOrder.verify(mMockNative).publish(anyShort(), eq((byte) 0), eq(publishConfig));
         assertTrue(mAlarmManager.dispatch(WifiAwareStateManager.HAL_COMMAND_TIMEOUT_TAG));
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onSessionConfigFail(NanStatusType.INTERNAL_FAILURE);
+        inOrderM.verify(mAwareMetricsMock).recordDiscoveryStatus(uid,
+                NanStatusType.INTERNAL_FAILURE, true);
         validateInternalNoSessions(clientId);
 
         // (3) publish + success
         mDut.publish(clientId, publishConfig, mockSessionCallback);
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishConfig));
-        mDut.onSessionConfigSuccessResponse(transactionId.getValue(), true, 9999);
+        inOrder.verify(mMockNative).publish(transactionId.capture(), eq((byte) 0),
+                eq(publishConfig));
+        mDut.onSessionConfigSuccessResponse(transactionId.getValue(), true, (byte) 99);
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onSessionStarted(anyInt());
+        inOrderM.verify(mAwareMetricsMock).recordDiscoverySession(eq(uid), eq(true), any());
+        inOrderM.verify(mAwareMetricsMock).recordDiscoveryStatus(uid, NanStatusType.SUCCESS, true);
 
-        verifyNoMoreInteractions(mMockNative, mockCallback, mockSessionCallback);
+        verifyNoMoreInteractions(mMockNative, mockCallback, mockSessionCallback, mAwareMetricsMock);
     }
 
     /**
@@ -486,10 +586,12 @@
                 IWifiAwareDiscoverySessionCallback.class);
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
         InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mMockNative);
+        InOrder inOrderM = inOrder(mAwareMetricsMock);
 
         mDut.enableUsage();
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        inOrderM.verify(mAwareMetricsMock).recordEnableUsage();
         mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
         mMockLooper.dispatchAll();
 
@@ -497,33 +599,38 @@
         mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                eq(configRequest), eq(false), eq(true));
+                eq(configRequest), eq(false), eq(true), eq(true), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
+        inOrderM.verify(mAwareMetricsMock).recordAttachSession(eq(uid), eq(false), any());
 
         // (1) initial publish
         mDut.publish(clientId, publishConfig, mockSessionCallback);
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishConfig));
+        inOrder.verify(mMockNative).publish(transactionId.capture(), eq((byte) 0),
+                eq(publishConfig));
 
         // (2) publish failure callback (i.e. firmware tried and failed)
         mDut.onSessionConfigFailResponse(transactionId.getValue(), true, reasonFail);
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onSessionConfigFail(reasonFail);
+        inOrderM.verify(mAwareMetricsMock).recordDiscoveryStatus(uid, reasonFail, true);
         validateInternalNoSessions(clientId);
 
         // (3) publish and get immediate failure (i.e. HAL failed)
-        when(mMockNative.publish(anyShort(), anyInt(), any())).thenReturn(false);
+        when(mMockNative.publish(anyShort(), anyByte(), any())).thenReturn(false);
 
         mDut.publish(clientId, publishConfig, mockSessionCallback);
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishConfig));
+        inOrder.verify(mMockNative).publish(transactionId.capture(), eq((byte) 0),
+                eq(publishConfig));
 
         inOrder.verify(mockSessionCallback).onSessionConfigFail(reasonFail);
+        inOrderM.verify(mAwareMetricsMock).recordDiscoveryStatus(uid, reasonFail, true);
         validateInternalNoSessions(clientId);
 
-        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mMockNative);
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mMockNative, mAwareMetricsMock);
     }
 
     /**
@@ -539,7 +646,7 @@
         final int pid = 2000;
         final String callingPackage = "com.google.somePackage";
         final int reasonTerminate = NanStatusType.SUCCESS;
-        final int publishId = 15;
+        final byte publishId = 15;
 
         ConfigRequest configRequest = new ConfigRequest.Builder().build();
         PublishConfig publishConfig = new PublishConfig.Builder().build();
@@ -550,10 +657,12 @@
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
         ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
         InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mMockNative);
+        InOrder inOrderM = inOrder(mAwareMetricsMock);
 
         mDut.enableUsage();
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        inOrderM.verify(mAwareMetricsMock).recordEnableUsage();
         mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
         mMockLooper.dispatchAll();
 
@@ -561,25 +670,30 @@
         mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                eq(configRequest), eq(false), eq(true));
+                eq(configRequest), eq(false), eq(true), eq(true), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
+        inOrderM.verify(mAwareMetricsMock).recordAttachSession(eq(uid), eq(false), any());
 
         // (1) initial publish
         mDut.publish(clientId, publishConfig, mockSessionCallback);
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishConfig));
+        inOrder.verify(mMockNative).publish(transactionId.capture(), eq((byte) 0),
+                eq(publishConfig));
 
         // (2) publish success
         mDut.onSessionConfigSuccessResponse(transactionId.getValue(), true, publishId);
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
+        inOrderM.verify(mAwareMetricsMock).recordDiscoverySession(eq(uid), eq(true), any());
+        inOrderM.verify(mAwareMetricsMock).recordDiscoveryStatus(uid, NanStatusType.SUCCESS, true);
 
         // (3) publish termination (from firmware - not app!)
         mDut.onSessionTerminatedNotification(publishId, reasonTerminate, true);
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onSessionTerminated(reasonTerminate);
+        inOrderM.verify(mAwareMetricsMock).recordDiscoverySessionDuration(anyLong(), eq(true));
 
         // (4) app update session (race condition: app didn't get termination
         // yet)
@@ -597,14 +711,15 @@
 
         validateInternalSessionInfoCleanedUp(clientId, sessionId.getValue());
 
-        verifyNoMoreInteractions(mockSessionCallback, mMockNative);
+        verifyNoMoreInteractions(mockSessionCallback, mMockNative, mAwareMetricsMock);
     }
 
     /**
      * Validate the publish flow: (1) initial publish + (2) success + (3) update + (4) update
      * fails (callback from firmware) + (5) update + (6). Expected: session is still alive after
      * update failure so second update succeeds (no callbacks) + (7) update + immediate failure from
-     * HAL.
+     * HAL + (8) update + failure for invalid ID (which should clean-up state) + (9) another update
+     * - should get no response.
      */
     @Test
     public void testPublishUpdateFail() throws Exception {
@@ -612,7 +727,7 @@
         final int uid = 1000;
         final int pid = 2000;
         final String callingPackage = "com.google.somePackage";
-        final int publishId = 15;
+        final byte publishId = 15;
         final int reasonFail = NanStatusType.INTERNAL_FAILURE;
 
         ConfigRequest configRequest = new ConfigRequest.Builder().build();
@@ -624,10 +739,12 @@
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
         ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
         InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mMockNative);
+        InOrder inOrderM = inOrder(mAwareMetricsMock);
 
         mDut.enableUsage();
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        inOrderM.verify(mAwareMetricsMock).recordEnableUsage();
         mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
         mMockLooper.dispatchAll();
 
@@ -635,20 +752,24 @@
         mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true));
+                eq(false), eq(true), eq(true), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
+        inOrderM.verify(mAwareMetricsMock).recordAttachSession(eq(uid), eq(false), any());
 
         // (1) initial publish
         mDut.publish(clientId, publishConfig, mockSessionCallback);
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishConfig));
+        inOrder.verify(mMockNative).publish(transactionId.capture(), eq((byte) 0),
+                eq(publishConfig));
 
         // (2) publish success
         mDut.onSessionConfigSuccessResponse(transactionId.getValue(), true, publishId);
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
+        inOrderM.verify(mAwareMetricsMock).recordDiscoverySession(eq(uid), eq(true), any());
+        inOrderM.verify(mAwareMetricsMock).recordDiscoveryStatus(uid, NanStatusType.SUCCESS, true);
 
         // (3) update publish
         mDut.updatePublish(clientId, sessionId.getValue(), publishConfig);
@@ -660,6 +781,7 @@
         mDut.onSessionConfigFailResponse(transactionId.getValue(), true, reasonFail);
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onSessionConfigFail(reasonFail);
+        inOrderM.verify(mAwareMetricsMock).recordDiscoveryStatus(uid, reasonFail, true);
 
         // (5) another update publish
         mDut.updatePublish(clientId, sessionId.getValue(), publishConfig);
@@ -671,17 +793,37 @@
         mDut.onSessionConfigSuccessResponse(transactionId.getValue(), true, publishId);
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onSessionConfigSuccess();
+        inOrderM.verify(mAwareMetricsMock).recordDiscoveryStatus(uid, NanStatusType.SUCCESS, true);
 
         // (7) another update + immediate failure
-        when(mMockNative.publish(anyShort(), anyInt(), any())).thenReturn(false);
+        when(mMockNative.publish(anyShort(), anyByte(), any())).thenReturn(false);
 
         mDut.updatePublish(clientId, sessionId.getValue(), publishConfig);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).publish(transactionId.capture(), eq(publishId),
                 eq(publishConfig));
         inOrder.verify(mockSessionCallback).onSessionConfigFail(reasonFail);
+        inOrderM.verify(mAwareMetricsMock).recordDiscoveryStatus(uid, reasonFail, true);
 
-        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mMockNative);
+        // (8) an update with bad ID failure
+        when(mMockNative.publish(anyShort(), anyByte(), any())).thenReturn(true);
+
+        mDut.updatePublish(clientId, sessionId.getValue(), publishConfig);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(publishId),
+                eq(publishConfig));
+        mDut.onSessionConfigFailResponse(transactionId.getValue(), true,
+                NanStatusType.INVALID_SESSION_ID);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSessionConfigFail(NanStatusType.INVALID_SESSION_ID);
+        inOrderM.verify(mAwareMetricsMock).recordDiscoveryStatus(uid,
+                NanStatusType.INVALID_SESSION_ID, true);
+
+        // (9) try updating again - do nothing/get nothing
+        mDut.updatePublish(clientId, sessionId.getValue(), publishConfig);
+        mMockLooper.dispatchAll();
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mMockNative, mAwareMetricsMock);
     }
 
     /**
@@ -696,7 +838,7 @@
         final int uid = 1000;
         final int pid = 2000;
         final String callingPackage = "com.google.somePackage";
-        final int publishId = 15;
+        final byte publishId = 15;
 
         ConfigRequest configRequest = new ConfigRequest.Builder().build();
         PublishConfig publishConfig = new PublishConfig.Builder().build();
@@ -706,10 +848,12 @@
                 IWifiAwareDiscoverySessionCallback.class);
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
         InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mMockNative);
+        InOrder inOrderM = inOrder(mAwareMetricsMock);
 
         mDut.enableUsage();
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        inOrderM.verify(mAwareMetricsMock).recordEnableUsage();
         mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
         mMockLooper.dispatchAll();
 
@@ -717,15 +861,17 @@
         mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true));
+                eq(false), eq(true), eq(true), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
+        inOrderM.verify(mAwareMetricsMock).recordAttachSession(eq(uid), eq(false), any());
 
         // (1) initial publish
         mDut.publish(clientId, publishConfig, mockSessionCallback);
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishConfig));
+        inOrder.verify(mMockNative).publish(transactionId.capture(), eq((byte) 0),
+                eq(publishConfig));
 
         // (2) disconnect (but doesn't get executed until get response for
         // publish command)
@@ -737,11 +883,15 @@
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onSessionStarted(anyInt());
         inOrder.verify(mMockNative).stopPublish(transactionId.capture(), eq(publishId));
-        inOrder.verify(mMockNative).disable((short) 0);
+        inOrder.verify(mMockNative).disable(anyShort());
+        inOrderM.verify(mAwareMetricsMock).recordDiscoverySession(eq(uid), eq(true), any());
+        inOrderM.verify(mAwareMetricsMock).recordDiscoveryStatus(uid, NanStatusType.SUCCESS, true);
+        inOrderM.verify(mAwareMetricsMock).recordAttachSessionDuration(anyLong());
+        inOrderM.verify(mAwareMetricsMock).recordDiscoverySessionDuration(anyLong(), eq(true));
 
         validateInternalClientInfoCleanedUp(clientId);
 
-        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mMockNative);
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mMockNative, mAwareMetricsMock);
     }
 
     /**
@@ -775,7 +925,7 @@
         mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true));
+                eq(false), eq(true), eq(true), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -783,7 +933,8 @@
         // (1) initial subscribe
         mDut.subscribe(clientId, subscribeConfig, mockSessionCallback);
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeConfig));
+        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq((byte) 0),
+                eq(subscribeConfig));
 
         // (2) subscribe failure
         mDut.onSessionConfigFailResponse(transactionId.getValue(), false, reasonFail);
@@ -792,12 +943,13 @@
         validateInternalNoSessions(clientId);
 
         // (3) subscribe and get immediate failure (i.e. HAL failed)
-        when(mMockNative.subscribe(anyShort(), anyInt(), any()))
+        when(mMockNative.subscribe(anyShort(), anyByte(), any()))
                 .thenReturn(false);
 
         mDut.subscribe(clientId, subscribeConfig, mockSessionCallback);
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeConfig));
+        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq((byte) 0),
+                eq(subscribeConfig));
 
         inOrder.verify(mockSessionCallback).onSessionConfigFail(reasonFail);
         validateInternalNoSessions(clientId);
@@ -818,7 +970,7 @@
         final int pid = 2000;
         final String callingPackage = "com.google.somePackage";
         final int reasonTerminate = NanStatusType.SUCCESS;
-        final int subscribeId = 15;
+        final byte subscribeId = 15;
 
         ConfigRequest configRequest = new ConfigRequest.Builder().build();
         SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
@@ -840,7 +992,7 @@
         mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true));
+                eq(false), eq(true), eq(true), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -848,7 +1000,8 @@
         // (1) initial subscribe
         mDut.subscribe(clientId, subscribeConfig, mockSessionCallback);
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeConfig));
+        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq((byte) 0),
+                eq(subscribeConfig));
 
         // (2) subscribe success
         mDut.onSessionConfigSuccessResponse(transactionId.getValue(), false, subscribeId);
@@ -890,7 +1043,7 @@
         final int uid = 1000;
         final int pid = 2000;
         final String callingPackage = "com.google.somePackage";
-        final int subscribeId = 15;
+        final byte subscribeId = 15;
         final int reasonFail = NanStatusType.INTERNAL_FAILURE;
 
         ConfigRequest configRequest = new ConfigRequest.Builder().build();
@@ -913,7 +1066,7 @@
         mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true));
+                eq(false), eq(true), eq(true), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -921,7 +1074,8 @@
         // (1) initial subscribe
         mDut.subscribe(clientId, subscribeConfig, mockSessionCallback);
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeConfig));
+        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq((byte) 0),
+                eq(subscribeConfig));
 
         // (2) subscribe success
         mDut.onSessionConfigSuccessResponse(transactionId.getValue(), false, subscribeId);
@@ -951,7 +1105,7 @@
         inOrder.verify(mockSessionCallback).onSessionConfigSuccess();
 
         // (7) another update + immediate failure
-        when(mMockNative.subscribe(anyShort(), anyInt(), any()))
+        when(mMockNative.subscribe(anyShort(), anyByte(), any()))
                 .thenReturn(false);
 
         mDut.updateSubscribe(clientId, sessionId.getValue(), subscribeConfig);
@@ -975,7 +1129,7 @@
         final int uid = 1000;
         final int pid = 2000;
         final String callingPackage = "com.google.somePackage";
-        final int subscribeId = 15;
+        final byte subscribeId = 15;
 
         ConfigRequest configRequest = new ConfigRequest.Builder().build();
         SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
@@ -996,7 +1150,7 @@
         mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true));
+                eq(false), eq(true), eq(true), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -1004,7 +1158,8 @@
         // (1) initial subscribe
         mDut.subscribe(clientId, subscribeConfig, mockSessionCallback);
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeConfig));
+        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq((byte) 0),
+                eq(subscribeConfig));
 
         // (2) disconnect (but doesn't get executed until get response for
         // subscribe command)
@@ -1016,7 +1171,7 @@
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onSessionStarted(anyInt());
         inOrder.verify(mMockNative).stopSubscribe((short) 0, subscribeId);
-        inOrder.verify(mMockNative).disable((short) 0);
+        inOrder.verify(mMockNative).disable(anyShort());
 
         validateInternalClientInfoCleanedUp(clientId);
 
@@ -1036,7 +1191,7 @@
         final String serviceName = "some-service-name";
         final String ssi = "some much longer and more arbitrary data";
         final int reasonFail = NanStatusType.INTERNAL_FAILURE;
-        final int subscribeId = 15;
+        final byte subscribeId = 15;
         final int requestorId = 22;
         final byte[] peerMac = HexEncoding.decode("060708090A0B".toCharArray(), false);
         final String peerSsi = "some peer ssi data";
@@ -1056,6 +1211,7 @@
                 IWifiAwareDiscoverySessionCallback.class);
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
         ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> peerIdCaptor = ArgumentCaptor.forClass(Integer.class);
         InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mMockNative);
 
         mDut.enableUsage();
@@ -1068,7 +1224,7 @@
         mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                eq(configRequest), eq(false), eq(true));
+                eq(configRequest), eq(false), eq(true), eq(true), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -1076,7 +1232,8 @@
         // (1) subscribe
         mDut.subscribe(clientId, subscribeConfig, mockSessionCallback);
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeConfig));
+        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq((byte) 0),
+                eq(subscribeConfig));
         mDut.onSessionConfigSuccessResponse(transactionId.getValue(), false, subscribeId);
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
@@ -1085,16 +1242,18 @@
         mDut.onMatchNotification(subscribeId, requestorId, peerMac, peerSsi.getBytes(),
                 peerMatchFilter.getBytes());
         mMockLooper.dispatchAll();
-        inOrder.verify(mockSessionCallback).onMatch(requestorId, peerSsi.getBytes(),
-                peerMatchFilter.getBytes());
+        inOrder.verify(mockSessionCallback).onMatch(peerIdCaptor.capture(), eq(peerSsi.getBytes()),
+                eq(peerMatchFilter.getBytes()));
 
         // (3) message Rx
         mDut.onMessageReceivedNotification(subscribeId, requestorId, peerMac, peerMsg.getBytes());
         mMockLooper.dispatchAll();
-        inOrder.verify(mockSessionCallback).onMessageReceived(requestorId, peerMsg.getBytes());
+        inOrder.verify(mockSessionCallback).onMessageReceived(peerIdCaptor.getValue(),
+                peerMsg.getBytes());
 
         // (4) message Tx successful queuing
-        mDut.sendMessage(clientId, sessionId.getValue(), requestorId, ssi.getBytes(), messageId, 0);
+        mDut.sendMessage(clientId, sessionId.getValue(), peerIdCaptor.getValue(), ssi.getBytes(),
+                messageId, 0);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(subscribeId),
                 eq(requestorId), eq(peerMac), eq(ssi.getBytes()), eq(messageId));
@@ -1103,8 +1262,8 @@
         mMockLooper.dispatchAll();
 
         // (5) message Tx successful queuing
-        mDut.sendMessage(clientId, sessionId.getValue(), requestorId, ssi.getBytes(), messageId2,
-                0);
+        mDut.sendMessage(clientId, sessionId.getValue(), peerIdCaptor.getValue(), ssi.getBytes(),
+                messageId2, 0);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(subscribeId),
                 eq(requestorId), eq(peerMac), eq(ssi.getBytes()), eq(messageId2));
@@ -1138,9 +1297,9 @@
         final int clusterHigh = 7;
         final int masterPref = 0;
         final String serviceName = "some-service-name";
-        final int publishId = 88;
-        final int peerId1 = 568;
-        final int peerId2 = 873;
+        final byte publishId = 88;
+        final int requestorId1 = 568;
+        final int requestorId2 = 873;
         final byte[] peerMac1 = HexEncoding.decode("000102030405".toCharArray(), false);
         final byte[] peerMac2 = HexEncoding.decode("060708090A0B".toCharArray(), false);
         final String msgFromPeer1 = "hey from 000102...";
@@ -1159,6 +1318,7 @@
 
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
         ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> peerIdCaptor = ArgumentCaptor.forClass(Integer.class);
         IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
         IWifiAwareDiscoverySessionCallback mockSessionCallback = mock(
                 IWifiAwareDiscoverySessionCallback.class);
@@ -1174,7 +1334,7 @@
         mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true));
+                eq(false), eq(true), eq(true), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -1182,24 +1342,31 @@
         // (2) publish
         mDut.publish(clientId, publishConfig, mockSessionCallback);
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishConfig));
+        inOrder.verify(mMockNative).publish(transactionId.capture(), eq((byte) 0),
+                eq(publishConfig));
         mDut.onSessionConfigSuccessResponse(transactionId.getValue(), true, publishId);
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
 
         // (3) message received from peers 1 & 2
-        mDut.onMessageReceivedNotification(publishId, peerId1, peerMac1, msgFromPeer1.getBytes());
-        mDut.onMessageReceivedNotification(publishId, peerId2, peerMac2, msgFromPeer2.getBytes());
+        mDut.onMessageReceivedNotification(publishId, requestorId1, peerMac1,
+                msgFromPeer1.getBytes());
+        mDut.onMessageReceivedNotification(publishId, requestorId2, peerMac2,
+                msgFromPeer2.getBytes());
         mMockLooper.dispatchAll();
-        inOrder.verify(mockSessionCallback).onMessageReceived(peerId1, msgFromPeer1.getBytes());
-        inOrder.verify(mockSessionCallback).onMessageReceived(peerId2, msgFromPeer2.getBytes());
+        inOrder.verify(mockSessionCallback).onMessageReceived(peerIdCaptor.capture(),
+                eq(msgFromPeer1.getBytes()));
+        int peerId1 = peerIdCaptor.getValue();
+        inOrder.verify(mockSessionCallback).onMessageReceived(peerIdCaptor.capture(),
+                eq(msgFromPeer2.getBytes()));
+        int peerId2 = peerIdCaptor.getValue();
 
         // (4) sending messages back to same peers: one Tx fails, other succeeds
         mDut.sendMessage(clientId, sessionId.getValue(), peerId2, msgToPeer2.getBytes(),
                 msgToPeerId2, 0);
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(publishId), eq(peerId2),
-                eq(peerMac2), eq(msgToPeer2.getBytes()), eq(msgToPeerId2));
+        inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(publishId),
+                eq(requestorId2), eq(peerMac2), eq(msgToPeer2.getBytes()), eq(msgToPeerId2));
         short transactionIdVal = transactionId.getValue();
         mDut.onMessageSendQueuedSuccessResponse(transactionIdVal);
         mDut.onMessageSendSuccessNotification(transactionIdVal);
@@ -1208,8 +1375,8 @@
                 msgToPeerId1, 0);
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onMessageSendSuccess(msgToPeerId2);
-        inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(publishId), eq(peerId1),
-                eq(peerMac1), eq(msgToPeer1.getBytes()), eq(msgToPeerId1));
+        inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(publishId),
+                eq(requestorId1), eq(peerMac1), eq(msgToPeer1.getBytes()), eq(msgToPeerId1));
         transactionIdVal = transactionId.getValue();
         mDut.onMessageSendQueuedSuccessResponse(transactionIdVal);
         mDut.onMessageSendFailNotification(transactionIdVal, reason);
@@ -1235,8 +1402,8 @@
         final int clusterHigh = 7;
         final int masterPref = 0;
         final String serviceName = "some-service-name";
-        final int publishId = 88;
-        final int peerId = 568;
+        final byte publishId = 88;
+        final int requestorId = 568;
         final byte[] peerMacOrig = HexEncoding.decode("000102030405".toCharArray(), false);
         final byte[] peerMacLater = HexEncoding.decode("060708090A0B".toCharArray(), false);
         final String msgFromPeer1 = "hey from 000102...";
@@ -1253,6 +1420,7 @@
 
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
         ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> peerId = ArgumentCaptor.forClass(Integer.class);
         IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
         IWifiAwareDiscoverySessionCallback mockSessionCallback = mock(
                 IWifiAwareDiscoverySessionCallback.class);
@@ -1268,7 +1436,7 @@
         mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true));
+                eq(false), eq(true), eq(true), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -1276,19 +1444,24 @@
         // (2) publish
         mDut.publish(clientId, publishConfig, mockSessionCallback);
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishConfig));
+        inOrder.verify(mMockNative).publish(transactionId.capture(), eq((byte) 0),
+                eq(publishConfig));
         mDut.onSessionConfigSuccessResponse(transactionId.getValue(), true, publishId);
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
 
         // (3) message received & responded to
-        mDut.onMessageReceivedNotification(publishId, peerId, peerMacOrig, msgFromPeer1.getBytes());
-        mDut.sendMessage(clientId, sessionId.getValue(), peerId, msgToPeer1.getBytes(),
+        mDut.onMessageReceivedNotification(publishId, requestorId, peerMacOrig,
+                msgFromPeer1.getBytes());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onMessageReceived(peerId.capture(),
+                eq(msgFromPeer1.getBytes()));
+        mDut.sendMessage(clientId, sessionId.getValue(), peerId.getValue(), msgToPeer1.getBytes(),
                 msgToPeerId1, 0);
         mMockLooper.dispatchAll();
-        inOrder.verify(mockSessionCallback).onMessageReceived(peerId, msgFromPeer1.getBytes());
-        inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(publishId), eq(peerId),
-                eq(peerMacOrig), eq(msgToPeer1.getBytes()), eq(msgToPeerId1));
+        inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(publishId),
+                eq(requestorId), eq(peerMacOrig), eq(msgToPeer1.getBytes()),
+                eq(msgToPeerId1));
         mDut.onMessageSendQueuedSuccessResponse(transactionId.getValue());
         mDut.onMessageSendSuccessNotification(transactionId.getValue());
         mMockLooper.dispatchAll();
@@ -1296,14 +1469,17 @@
         validateInternalSendMessageQueuesCleanedUp(msgToPeerId1);
 
         // (4) message received with same peer ID but different MAC
-        mDut.onMessageReceivedNotification(publishId, peerId, peerMacLater,
+        mDut.onMessageReceivedNotification(publishId, requestorId, peerMacLater,
                 msgFromPeer2.getBytes());
-        mDut.sendMessage(clientId, sessionId.getValue(), peerId, msgToPeer2.getBytes(),
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onMessageReceived(peerId.capture(),
+                eq(msgFromPeer2.getBytes()));
+        mDut.sendMessage(clientId, sessionId.getValue(), peerId.getValue(), msgToPeer2.getBytes(),
                 msgToPeerId2, 0);
         mMockLooper.dispatchAll();
-        inOrder.verify(mockSessionCallback).onMessageReceived(peerId, msgFromPeer2.getBytes());
-        inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(publishId), eq(peerId),
-                eq(peerMacLater), eq(msgToPeer2.getBytes()), eq(msgToPeerId2));
+        inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(publishId),
+                eq(requestorId), eq(peerMacLater), eq(msgToPeer2.getBytes()),
+                eq(msgToPeerId2));
         mDut.onMessageSendQueuedSuccessResponse(transactionId.getValue());
         mDut.onMessageSendSuccessNotification(transactionId.getValue());
         mMockLooper.dispatchAll();
@@ -1324,7 +1500,7 @@
         final int pid = 2000;
         final String callingPackage = "com.google.somePackage";
         final String ssi = "some much longer and more arbitrary data";
-        final int subscribeId = 15;
+        final byte subscribeId = 15;
         final int requestorId = 22;
         final byte[] peerMac = HexEncoding.decode("060708090A0B".toCharArray(), false);
         final String peerSsi = "some peer ssi data";
@@ -1339,6 +1515,7 @@
                 IWifiAwareDiscoverySessionCallback.class);
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
         ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> peerIdCaptor = ArgumentCaptor.forClass(Integer.class);
         InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mMockNative);
 
         mDut.enableUsage();
@@ -1351,7 +1528,7 @@
         mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true));
+                eq(false), eq(true), eq(true), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -1359,18 +1536,19 @@
         // (2) subscribe & match
         mDut.subscribe(clientId, subscribeConfig, mockSessionCallback);
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeConfig));
+        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq((byte) 0),
+                eq(subscribeConfig));
         mDut.onSessionConfigSuccessResponse(transactionId.getValue(), false, subscribeId);
         mDut.onMatchNotification(subscribeId, requestorId, peerMac, peerSsi.getBytes(),
                 peerMatchFilter.getBytes());
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
-        inOrder.verify(mockSessionCallback).onMatch(requestorId, peerSsi.getBytes(),
-                peerMatchFilter.getBytes());
+        inOrder.verify(mockSessionCallback).onMatch(peerIdCaptor.capture(), eq(peerSsi.getBytes()),
+                eq(peerMatchFilter.getBytes()));
 
         // (3) send message to invalid peer ID
-        mDut.sendMessage(clientId, sessionId.getValue(), requestorId + 5, ssi.getBytes(),
-                messageId, 0);
+        mDut.sendMessage(clientId, sessionId.getValue(), peerIdCaptor.getValue() + 5,
+                ssi.getBytes(), messageId, 0);
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onMessageSendFail(messageId,
                 NanStatusType.INTERNAL_FAILURE);
@@ -1391,7 +1569,7 @@
         final int pid = 2000;
         final String callingPackage = "com.google.somePackage";
         final String ssi = "some much longer and more arbitrary data";
-        final int subscribeId = 15;
+        final byte subscribeId = 15;
         final int requestorId = 22;
         final byte[] peerMac = HexEncoding.decode("060708090A0B".toCharArray(), false);
         final String peerSsi = "some peer ssi data";
@@ -1406,6 +1584,7 @@
                 IWifiAwareDiscoverySessionCallback.class);
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
         ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> peerIdCaptor = ArgumentCaptor.forClass(Integer.class);
         InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mMockNative);
 
         mDut.enableUsage();
@@ -1418,7 +1597,7 @@
         mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true));
+                eq(false), eq(true), eq(true), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -1426,17 +1605,18 @@
         // (2) subscribe & match
         mDut.subscribe(clientId, subscribeConfig, mockSessionCallback);
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeConfig));
+        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq((byte) 0),
+                eq(subscribeConfig));
         mDut.onSessionConfigSuccessResponse(transactionId.getValue(), false, subscribeId);
         mDut.onMatchNotification(subscribeId, requestorId, peerMac, peerSsi.getBytes(),
                 peerMatchFilter.getBytes());
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
-        inOrder.verify(mockSessionCallback).onMatch(requestorId, peerSsi.getBytes(),
-                peerMatchFilter.getBytes());
+        inOrder.verify(mockSessionCallback).onMatch(peerIdCaptor.capture(), eq(peerSsi.getBytes()),
+                eq(peerMatchFilter.getBytes()));
 
         // (3) send 2 messages and enqueue successfully
-        mDut.sendMessage(clientId, sessionId.getValue(), requestorId, ssi.getBytes(),
+        mDut.sendMessage(clientId, sessionId.getValue(), peerIdCaptor.getValue(), ssi.getBytes(),
                 messageId, 0);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(subscribeId),
@@ -1445,7 +1625,7 @@
         mDut.onMessageSendQueuedSuccessResponse(transactionId1);
         mMockLooper.dispatchAll();
 
-        mDut.sendMessage(clientId, sessionId.getValue(), requestorId, ssi.getBytes(),
+        mDut.sendMessage(clientId, sessionId.getValue(), peerIdCaptor.getValue(), ssi.getBytes(),
                 messageId + 1, 0);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(subscribeId),
@@ -1455,8 +1635,8 @@
         mMockLooper.dispatchAll();
 
         // (4) send a message and get a queueing failure (not queue full)
-        mDut.sendMessage(clientId, sessionId.getValue(), requestorId, ssi.getBytes(), messageId + 2,
-                0);
+        mDut.sendMessage(clientId, sessionId.getValue(), peerIdCaptor.getValue(), ssi.getBytes(),
+                messageId + 2, 0);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(subscribeId),
                 eq(requestorId), eq(peerMac), eq(ssi.getBytes()), eq(messageId + 2));
@@ -1468,11 +1648,11 @@
         validateInternalSendMessageQueuesCleanedUp(messageId + 2);
 
         // (5) send a message and get an immediate failure (configure first)
-        when(mMockNative.sendMessage(anyShort(), anyInt(), anyInt(), any(),
+        when(mMockNative.sendMessage(anyShort(), anyByte(), anyInt(), any(),
                 any(), anyInt())).thenReturn(false);
 
-        mDut.sendMessage(clientId, sessionId.getValue(), requestorId, ssi.getBytes(), messageId + 3,
-                0);
+        mDut.sendMessage(clientId, sessionId.getValue(), peerIdCaptor.getValue(), ssi.getBytes(),
+                messageId + 3, 0);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(subscribeId),
                 eq(requestorId), eq(peerMac), eq(ssi.getBytes()), eq(messageId + 3));
@@ -1514,7 +1694,7 @@
         final int pid = 2000;
         final String callingPackage = "com.google.somePackage";
         final String ssi = "some much longer and more arbitrary data";
-        final int subscribeId = 15;
+        final byte subscribeId = 15;
         final int requestorId = 22;
         final byte[] peerMac = HexEncoding.decode("060708090A0B".toCharArray(), false);
         final String peerSsi = "some peer ssi data";
@@ -1530,6 +1710,7 @@
                 IWifiAwareDiscoverySessionCallback.class);
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
         ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> peerIdCaptor = ArgumentCaptor.forClass(Integer.class);
         InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mMockNative);
 
         mDut.enableUsage();
@@ -1542,7 +1723,7 @@
         mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true));
+                eq(false), eq(true), eq(true), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -1550,17 +1731,18 @@
         // (2) subscribe & match
         mDut.subscribe(clientId, subscribeConfig, mockSessionCallback);
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeConfig));
+        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq((byte) 0),
+                eq(subscribeConfig));
         mDut.onSessionConfigSuccessResponse(transactionId.getValue(), false, subscribeId);
         mDut.onMatchNotification(subscribeId, requestorId, peerMac, peerSsi.getBytes(),
                 peerMatchFilter.getBytes());
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
-        inOrder.verify(mockSessionCallback).onMatch(requestorId, peerSsi.getBytes(),
-                peerMatchFilter.getBytes());
+        inOrder.verify(mockSessionCallback).onMatch(peerIdCaptor.capture(), eq(peerSsi.getBytes()),
+                eq(peerMatchFilter.getBytes()));
 
         // (3) send message and enqueue successfully
-        mDut.sendMessage(clientId, sessionId.getValue(), requestorId, ssi.getBytes(),
+        mDut.sendMessage(clientId, sessionId.getValue(), peerIdCaptor.getValue(), ssi.getBytes(),
                 messageId, retryCount);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(subscribeId),
@@ -1599,7 +1781,7 @@
         final int pid = 2000;
         final String callingPackage = "com.google.somePackage";
         final String ssi = "some much longer and more arbitrary data";
-        final int subscribeId = 15;
+        final byte subscribeId = 15;
         final int requestorId = 22;
         final byte[] peerMac = HexEncoding.decode("060708090A0B".toCharArray(), false);
         final String peerSsi = "some peer ssi data";
@@ -1615,6 +1797,7 @@
                 IWifiAwareDiscoverySessionCallback.class);
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
         ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> peerIdCaptor = ArgumentCaptor.forClass(Integer.class);
         InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mMockNative);
 
         mDut.enableUsage();
@@ -1627,7 +1810,7 @@
         mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true));
+                eq(false), eq(true), eq(true), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -1635,18 +1818,19 @@
         // (2) subscribe & match
         mDut.subscribe(clientId, subscribeConfig, mockSessionCallback);
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeConfig));
+        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq((byte) 0),
+                eq(subscribeConfig));
         mDut.onSessionConfigSuccessResponse(transactionId.getValue(), false, subscribeId);
         mDut.onMatchNotification(subscribeId, requestorId, peerMac, peerSsi.getBytes(),
                 peerMatchFilter.getBytes());
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
-        inOrder.verify(mockSessionCallback).onMatch(requestorId, peerSsi.getBytes(),
-                peerMatchFilter.getBytes());
+        inOrder.verify(mockSessionCallback).onMatch(peerIdCaptor.capture(), eq(peerSsi.getBytes()),
+                eq(peerMatchFilter.getBytes()));
 
         // (3) send message and enqueue successfully
-        mDut.sendMessage(clientId, sessionId.getValue(), requestorId, ssi.getBytes(), messageId,
-                retryCount);
+        mDut.sendMessage(clientId, sessionId.getValue(), peerIdCaptor.getValue(), ssi.getBytes(),
+                messageId, retryCount);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(subscribeId),
                 eq(requestorId), eq(peerMac), eq(ssi.getBytes()), eq(messageId));
@@ -1684,7 +1868,7 @@
         final int pid = 2000;
         final String callingPackage = "com.google.somePackage";
         final String serviceName = "some-service-name";
-        final int subscribeId = 15;
+        final byte subscribeId = 15;
         final int requestorId = 22;
         final byte[] peerMac = HexEncoding.decode("060708090A0B".toCharArray(), false);
         final int messageIdBase = 6948;
@@ -1701,6 +1885,7 @@
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
         ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
         ArgumentCaptor<Integer> messageIdCaptor = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> peerIdCaptor = ArgumentCaptor.forClass(Integer.class);
         InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mMockNative);
 
         mDut.enableUsage();
@@ -1713,7 +1898,7 @@
         mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                eq(configRequest), eq(false), eq(true));
+                eq(configRequest), eq(false), eq(true), eq(true), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -1721,7 +1906,8 @@
         // (1) subscribe
         mDut.subscribe(clientId, subscribeConfig, mockSessionCallback);
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeConfig));
+        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq((byte) 0),
+                eq(subscribeConfig));
         mDut.onSessionConfigSuccessResponse(transactionId.getValue(), false, subscribeId);
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
@@ -1729,18 +1915,18 @@
         // (2) match
         mDut.onMatchNotification(subscribeId, requestorId, peerMac, null, null);
         mMockLooper.dispatchAll();
-        inOrder.verify(mockSessionCallback).onMatch(requestorId, null, null);
+        inOrder.verify(mockSessionCallback).onMatch(peerIdCaptor.capture(), isNull(), isNull());
 
         // (3) transmit messages
         SendMessageQueueModelAnswer answerObj = new SendMessageQueueModelAnswer(queueDepth,
                 null, null, null);
-        when(mMockNative.sendMessage(anyShort(), anyInt(), anyInt(), any(),
+        when(mMockNative.sendMessage(anyShort(), anyByte(), anyInt(), any(),
                 any(), anyInt())).thenAnswer(answerObj);
 
         int remainingMessages = numberOfMessages;
         for (int i = 0; i < numberOfMessages; ++i) {
-            mDut.sendMessage(clientId, sessionId.getValue(), requestorId, null, messageIdBase + i,
-                    0);
+            mDut.sendMessage(clientId, sessionId.getValue(), peerIdCaptor.getValue(), null,
+                    messageIdBase + i, 0);
             mMockLooper.dispatchAll();
             // at 1/2 interval have the system simulate transmitting a queued message over-the-air
             if (i % 2 == 1) {
@@ -1779,7 +1965,7 @@
         final int pid = 2000;
         final String callingPackage = "com.google.somePackage";
         final String serviceName = "some-service-name";
-        final int subscribeId = 15;
+        final byte subscribeId = 15;
         final int requestorId = 22;
         final byte[] peerMac = HexEncoding.decode("060708090A0B".toCharArray(), false);
         final int messageIdBase = 6948;
@@ -1796,7 +1982,7 @@
                 IWifiAwareDiscoverySessionCallback.class);
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
         ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
-        ArgumentCaptor<Integer> messageIdCaptor = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> peerIdCaptor = ArgumentCaptor.forClass(Integer.class);
         InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mMockNative);
 
         mDut.enableUsage();
@@ -1809,7 +1995,7 @@
         mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                eq(configRequest), eq(false), eq(true));
+                eq(configRequest), eq(false), eq(true), eq(true), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -1817,7 +2003,8 @@
         // (1) subscribe
         mDut.subscribe(clientId, subscribeConfig, mockSessionCallback);
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeConfig));
+        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq((byte) 0),
+                eq(subscribeConfig));
         mDut.onSessionConfigSuccessResponse(transactionId.getValue(), false, subscribeId);
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
@@ -1825,7 +2012,7 @@
         // (2) match
         mDut.onMatchNotification(subscribeId, requestorId, peerMac, null, null);
         mMockLooper.dispatchAll();
-        inOrder.verify(mockSessionCallback).onMatch(requestorId, null, null);
+        inOrder.verify(mockSessionCallback).onMatch(peerIdCaptor.capture(), isNull(), isNull());
 
         // (3) transmit messages: configure a mix of failures/success
         Set<Integer> failQueueCommandImmediately = new HashSet<>();
@@ -1874,12 +2061,12 @@
 
         SendMessageQueueModelAnswer answerObj = new SendMessageQueueModelAnswer(queueDepth,
                 failQueueCommandImmediately, failQueueCommandLater, numberOfRetries);
-        when(mMockNative.sendMessage(anyShort(), anyInt(), anyInt(), any(),
+        when(mMockNative.sendMessage(anyShort(), anyByte(), anyInt(), any(),
                 any(), anyInt())).thenAnswer(answerObj);
 
         for (int i = 0; i < numberOfMessages; ++i) {
-            mDut.sendMessage(clientId, sessionId.getValue(), requestorId, null, messageIdBase + i,
-                    retransmitCount);
+            mDut.sendMessage(clientId, sessionId.getValue(), peerIdCaptor.getValue(), null,
+                    messageIdBase + i, retransmitCount);
             mMockLooper.dispatchAll();
         }
 
@@ -1908,7 +2095,7 @@
         final String callingPackage = "com.google.somePackage";
         final String serviceName = "some-service-name";
         final String ssi = "some much longer and more arbitrary data";
-        final int subscribeId = 15;
+        final byte subscribeId = 15;
         final int requestorId = 22;
         final byte[] peerMac = HexEncoding.decode("060708090A0B".toCharArray(), false);
         final String peerSsi = "some peer ssi data";
@@ -1926,6 +2113,7 @@
                 IWifiAwareDiscoverySessionCallback.class);
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
         ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> peerIdCaptor = ArgumentCaptor.forClass(Integer.class);
         ArgumentCaptor<byte[]> byteArrayCaptor = ArgumentCaptor.forClass(byte[].class);
         InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mMockNative);
 
@@ -1939,7 +2127,7 @@
         mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                eq(configRequest), eq(false), eq(true));
+                eq(configRequest), eq(false), eq(true), eq(true), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -1947,7 +2135,8 @@
         // (1) subscribe
         mDut.subscribe(clientId, subscribeConfig, mockSessionCallback);
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeConfig));
+        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq((byte) 0),
+                eq(subscribeConfig));
         mDut.onSessionConfigSuccessResponse(transactionId.getValue(), false, subscribeId);
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
@@ -1956,11 +2145,12 @@
         mDut.onMatchNotification(subscribeId, requestorId, peerMac, peerSsi.getBytes(),
                 peerMatchFilter.getBytes());
         mMockLooper.dispatchAll();
-        inOrder.verify(mockSessionCallback).onMatch(requestorId, peerSsi.getBytes(),
-                peerMatchFilter.getBytes());
+        inOrder.verify(mockSessionCallback).onMatch(peerIdCaptor.capture(), eq(peerSsi.getBytes()),
+                eq(peerMatchFilter.getBytes()));
 
         // (3) message null Tx successful queuing
-        mDut.sendMessage(clientId, sessionId.getValue(), requestorId, null, messageId, 0);
+        mDut.sendMessage(clientId, sessionId.getValue(), peerIdCaptor.getValue(), null, messageId,
+                0);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(subscribeId),
                 eq(requestorId), eq(peerMac), isNull(byte[].class), eq(messageId));
@@ -1975,7 +2165,8 @@
         validateInternalSendMessageQueuesCleanedUp(messageId);
 
         // (5) message byte[0] Tx successful queuing
-        mDut.sendMessage(clientId, sessionId.getValue(), requestorId, new byte[0], messageId, 0);
+        mDut.sendMessage(clientId, sessionId.getValue(), peerIdCaptor.getValue(), new byte[0],
+                messageId, 0);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(subscribeId),
                 eq(requestorId), eq(peerMac), eq(new byte[0]), eq(messageId));
@@ -1990,7 +2181,8 @@
         validateInternalSendMessageQueuesCleanedUp(messageId);
 
         // (7) message "" Tx successful queuing
-        mDut.sendMessage(clientId, sessionId.getValue(), requestorId, "".getBytes(), messageId, 0);
+        mDut.sendMessage(clientId, sessionId.getValue(), peerIdCaptor.getValue(), "".getBytes(),
+                messageId, 0);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(subscribeId),
                 eq(requestorId), eq(peerMac), byteArrayCaptor.capture(), eq(messageId));
@@ -2039,7 +2231,7 @@
             }
         }
 
-        public boolean answer(short transactionId, int pubSubId, int requestorInstanceId,
+        public boolean answer(short transactionId, byte pubSubId, int requestorInstanceId,
                 byte[] dest, byte[] message, int messageId) throws Exception {
             if (mFailQueueCommandImmediately != null && mFailQueueCommandImmediately.contains(
                     messageId)) {
@@ -2113,17 +2305,13 @@
         final int uid = 1000;
         final int pid = 2000;
         final String callingPackage = "com.google.somePackage";
-        final int subscribeId = 15;
+        final byte subscribeId = 15;
         final int requestorId = 22;
         final byte[] peerMac = HexEncoding.decode("060708090A0B".toCharArray(), false);
         final String peerSsi = "some peer ssi data";
         final String peerMatchFilter = "filter binary array represented as string";
         final int rangingId = 18423;
         final RttManager.RttParams[] params = new RttManager.RttParams[2];
-        params[0] = new RttManager.RttParams();
-        params[0].bssid = Integer.toString(requestorId);
-        params[1] = new RttManager.RttParams();
-        params[1].bssid = Integer.toString(requestorId + 5);
 
         ConfigRequest configRequest = new ConfigRequest.Builder().build();
         SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
@@ -2134,6 +2322,7 @@
 
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
         ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> peerIdIdCaptor = ArgumentCaptor.forClass(Integer.class);
         ArgumentCaptor<WifiAwareClientState> clientCaptor =
                 ArgumentCaptor.forClass(WifiAwareClientState.class);
         ArgumentCaptor<RttManager.RttParams[]> rttParamsCaptor =
@@ -2152,7 +2341,7 @@
         mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true));
+                eq(false), eq(true), eq(true), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -2160,21 +2349,27 @@
         // (2) subscribe & match
         mDut.subscribe(clientId, subscribeConfig, mockSessionCallback);
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeConfig));
+        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq((byte) 0),
+                eq(subscribeConfig));
         mDut.onSessionConfigSuccessResponse(transactionId.getValue(), false, subscribeId);
         mDut.onMatchNotification(subscribeId, requestorId, peerMac, peerSsi.getBytes(),
                 peerMatchFilter.getBytes());
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
-        inOrder.verify(mockSessionCallback).onMatch(requestorId, peerSsi.getBytes(),
-                peerMatchFilter.getBytes());
+        inOrder.verify(mockSessionCallback).onMatch(peerIdIdCaptor.capture(),
+                eq(peerSsi.getBytes()), eq(peerMatchFilter.getBytes()));
 
         // (3) start ranging: pass along a valid peer ID and an invalid one
+        params[0] = new RttManager.RttParams();
+        params[0].bssid = Integer.toString(peerIdIdCaptor.getValue());
+        params[1] = new RttManager.RttParams();
+        params[1].bssid = Integer.toString(peerIdIdCaptor.getValue() + 5);
+
         mDut.startRanging(clientId, sessionId.getValue(), params, rangingId);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockAwareRttStateManager).startRanging(eq(rangingId),
                 clientCaptor.capture(), rttParamsCaptor.capture());
-        collector.checkThat("RttParams[0].bssid", "06:07:08:09:0A:0B",
+        collector.checkThat("RttParams[0].bssid", "06:07:08:09:0a:0b",
                 equalTo(rttParamsCaptor.getValue()[0].bssid));
         collector.checkThat("RttParams[1].bssid", "", equalTo(rttParamsCaptor.getValue()[1].bssid));
 
@@ -2243,7 +2438,7 @@
         mDut.connect(clientId1, uid, pid, callingPackage, mockCallback1, configRequest1, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                crCapture.capture(), eq(false), eq(true));
+                crCapture.capture(), eq(false), eq(true), eq(true), eq(false));
         collector.checkThat("merge: stage 1", crCapture.getValue(), equalTo(configRequest1));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
@@ -2259,7 +2454,7 @@
         mDut.connect(clientId3, uid, pid, callingPackage, mockCallback3, configRequest3, true);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                crCapture.capture(), eq(true), eq(false));
+                crCapture.capture(), eq(true), eq(false), eq(true), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback3).onConnectSuccess(clientId3);
@@ -2280,7 +2475,7 @@
         mMockLooper.dispatchAll();
         validateInternalClientInfoCleanedUp(clientId3);
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                crCapture.capture(), eq(false), eq(false));
+                crCapture.capture(), eq(false), eq(false), eq(true), eq(false));
 
         collector.checkThat("configRequest1", configRequest1, equalTo(crCapture.getValue()));
 
@@ -2291,12 +2486,81 @@
         mDut.disconnect(clientId1);
         mMockLooper.dispatchAll();
         validateInternalClientInfoCleanedUp(clientId1);
-        inOrder.verify(mMockNative).disable((short) 0);
+        inOrder.verify(mMockNative).disable(anyShort());
 
         verifyNoMoreInteractions(mMockNative, mockCallback1, mockCallback2, mockCallback3);
     }
 
     /**
+     * Validate that identical configuration but with different identity callback requirements
+     * trigger the correct HAL sequence.
+     * 1. Attach w/o identity -> enable
+     * 2. Attach w/o identity -> nop
+     * 3. Attach w/ identity -> re-configure
+     * 4. Attach w/o identity -> nop
+     * 5. Attach w/ identity -> nop
+     */
+    @Test
+    public void testConfigsIdentityCallback() throws Exception {
+        int clientId = 9999;
+        final int uid = 1000;
+        final int pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+
+        InOrder inOrder = inOrder(mMockNative, mockCallback);
+
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
+        mMockLooper.dispatchAll();
+
+        // (1) attach w/o identity
+        mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
+                any(ConfigRequest.class), eq(false), eq(true), eq(true), eq(false));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+
+        // (2) attach w/o identity
+        ++clientId;
+        mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+
+        // (3) attach w/ identity
+        ++clientId;
+        mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, true);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
+                any(ConfigRequest.class), eq(true), eq(false), eq(true), eq(false));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+
+        // (4) attach w/o identity
+        ++clientId;
+        mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+
+        // (5) attach w/ identity
+        ++clientId;
+        mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, true);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+
+        verifyNoMoreInteractions(mMockNative, mockCallback);
+    }
+
+    /**
      * Summary: disconnect a client while there are pending transactions.
      */
     @Test
@@ -2310,7 +2574,7 @@
         final int masterPref = 111;
         final String serviceName = "some-service-name";
         final String ssi = "some much longer and more arbitrary data";
-        final int publishId = 22;
+        final byte publishId = 22;
 
         ConfigRequest configRequest = new ConfigRequest.Builder().setClusterLow(clusterLow)
                 .setClusterHigh(clusterHigh).setMasterPreference(masterPref).build();
@@ -2335,7 +2599,7 @@
         mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true));
+                eq(false), eq(true), eq(true), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -2343,7 +2607,8 @@
         // (2) publish (no response yet)
         mDut.publish(clientId, publishConfig, mockSessionCallback);
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishConfig));
+        inOrder.verify(mMockNative).publish(transactionId.capture(), eq((byte) 0),
+                eq(publishConfig));
 
         // (3) disconnect (but doesn't get executed until get a RESPONSE to the
         // previous publish)
@@ -2355,7 +2620,7 @@
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onSessionStarted(anyInt());
         inOrder.verify(mMockNative).stopPublish((short) 0, publishId);
-        inOrder.verify(mMockNative).disable((short) 0);
+        inOrder.verify(mMockNative).disable(anyShort());
 
         validateInternalClientInfoCleanedUp(clientId);
 
@@ -2409,7 +2674,7 @@
         mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true));
+                eq(false), eq(true), eq(true), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -2417,7 +2682,8 @@
         // (2) publish - no response
         mDut.publish(clientId, publishConfig, mockPublishSessionCallback);
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishConfig));
+        inOrder.verify(mMockNative).publish(transactionId.capture(), eq((byte) 0),
+                eq(publishConfig));
 
         verifyNoMoreInteractions(mMockNative, mockCallback, mockPublishSessionCallback);
     }
@@ -2452,7 +2718,7 @@
         mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true));
+                eq(false), eq(true), eq(true), eq(false));
 
         verifyNoMoreInteractions(mMockNative, mockCallback, mockSessionCallback);
     }
@@ -2464,7 +2730,7 @@
      */
     @Test
     public void testInvalidCallbackIdParameters() throws Exception {
-        final int pubSubId = 1235;
+        final byte pubSubId = 125;
         final int clientId = 132;
         final int uid = 1000;
         final int pid = 2000;
@@ -2486,7 +2752,7 @@
         mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true));
+                eq(false), eq(true), eq(true), eq(false));
         short transactionIdConfig = transactionId.getValue();
         mDut.onConfigSuccessResponse(transactionIdConfig);
         mMockLooper.dispatchAll();
@@ -2519,7 +2785,7 @@
         final int uid = 1000;
         final int pid = 2000;
         final String callingPackage = "com.google.somePackage";
-        final int publishId = 25;
+        final byte publishId = 25;
 
         ConfigRequest configRequest = new ConfigRequest.Builder().build();
         PublishConfig publishConfig = new PublishConfig.Builder().build();
@@ -2542,7 +2808,7 @@
         mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true));
+                eq(false), eq(true), eq(true), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -2550,7 +2816,8 @@
         // (2) publish
         mDut.publish(clientId, publishConfig, mockSessionCallback);
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishConfig));
+        inOrder.verify(mMockNative).publish(transactionId.capture(), eq((byte) 0),
+                eq(publishConfig));
         mDut.onSessionConfigSuccessResponse(transactionId.getValue(), true, publishId);
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
@@ -2573,7 +2840,7 @@
         final int uid = 1000;
         final int pid = 2000;
         final String callingPackage = "com.google.somePackage";
-        final int subscribeId = 25;
+        final byte subscribeId = 25;
 
         ConfigRequest configRequest = new ConfigRequest.Builder().build();
         PublishConfig publishConfig = new PublishConfig.Builder().build();
@@ -2596,7 +2863,7 @@
         mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                eq(configRequest), eq(false), eq(true));
+                eq(configRequest), eq(false), eq(true), eq(true), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -2604,7 +2871,8 @@
         // (2) subscribe
         mDut.subscribe(clientId, subscribeConfig, mockSessionCallback);
         mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeConfig));
+        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq((byte) 0),
+                eq(subscribeConfig));
         mDut.onSessionConfigSuccessResponse(transactionId.getValue(), false, subscribeId);
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
@@ -2648,7 +2916,7 @@
         mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
         mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                eq(configRequest), eq(false), eq(true));
+                eq(configRequest), eq(false), eq(true), eq(true), eq(false));
         mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onConnectSuccess(clientId);
@@ -2658,10 +2926,11 @@
             // (2) publish
             mDut.publish(clientId, publishConfig, mockSessionCallback);
             mMockLooper.dispatchAll();
-            inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishConfig));
+            inOrder.verify(mMockNative).publish(transactionId.capture(), eq((byte) 0),
+                    eq(publishConfig));
 
             // (3) publish-success
-            mDut.onSessionConfigSuccessResponse(transactionId.getValue(), true, i + 1);
+            mDut.onSessionConfigSuccessResponse(transactionId.getValue(), true, (byte) (i + 1));
             mMockLooper.dispatchAll();
             inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
 
@@ -2672,6 +2941,121 @@
         }
     }
 
+    /**
+     * Validate configuration changes on power state changes when Aware is not disabled on doze.
+     */
+    @Test
+    public void testConfigOnPowerStateChanges() throws Exception {
+        final int clientId = 188;
+        final int uid = 1000;
+        final int pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+
+        setSettableParam(WifiAwareStateManager.PARAM_ON_IDLE_DISABLE_AWARE, Integer.toString(0),
+                true);
+
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
+        InOrder inOrder = inOrder(mMockNative, mockCallback);
+
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
+        mMockLooper.dispatchAll();
+
+        // (1) connect
+        mDut.connect(clientId, uid, pid, callingPackage, mockCallback, 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(mockCallback).onConnectSuccess(clientId);
+
+        // (2) power state change: SCREEN OFF
+        simulatePowerStateChangeInteractive(false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
+                eq(configRequest), eq(false), eq(false), eq(false), eq(false));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+
+        // (3) power state change: DOZE
+        simulatePowerStateChangeDoze(true);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
+                eq(configRequest), eq(false), eq(false), eq(false), eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+
+        // (4) restore power state to default
+        simulatePowerStateChangeInteractive(true); // effectively treated as no-doze
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
+                eq(configRequest), eq(false), eq(false), eq(true), eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+
+        verifyNoMoreInteractions(mMockNative, mockCallback);
+    }
+
+    /**
+     * Validate aware enable/disable during doze transitions.
+     */
+    @Test
+    public void testEnableDisableOnDoze() throws Exception {
+        final int clientId = 188;
+        final int uid = 1000;
+        final int pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+
+        setSettableParam(WifiAwareStateManager.PARAM_ON_IDLE_DISABLE_AWARE, Integer.toString(1),
+                true);
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
+        InOrder inOrder = inOrder(mMockContext, mMockNativeManager, mMockNative, mockCallback);
+        inOrder.verify(mMockNativeManager).start();
+
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
+        mMockLooper.dispatchAll();
+
+        // (1) connect
+        mDut.connect(clientId, uid, pid, callingPackage, mockCallback, 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(mockCallback).onConnectSuccess(clientId);
+
+        // (3) power state change: DOZE
+        simulatePowerStateChangeDoze(true);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).disable(transactionId.capture());
+        mDut.onDisableResponse(transactionId.getValue(), NanStatusType.SUCCESS);
+        validateCorrectAwareStatusChangeBroadcast(inOrder, false);
+
+        // (4) power state change: SCREEN ON (but DOZE still on - fakish but expect no changes)
+        simulatePowerStateChangeInteractive(false);
+        mMockLooper.dispatchAll();
+
+        // (5) power state change: DOZE OFF
+        simulatePowerStateChangeDoze(false);
+        mMockLooper.dispatchAll();
+        validateCorrectAwareStatusChangeBroadcast(inOrder, true);
+
+        verifyNoMoreInteractions(mMockNativeManager, mMockNative, mockCallback);
+    }
+
     /*
      * Tests of internal state of WifiAwareStateManager: very limited (not usually
      * a good idea). However, these test that the internal state is cleaned-up
@@ -2749,6 +3133,16 @@
     /*
      * Utilities
      */
+    private void setSettableParam(String name, String value, boolean expectSuccess) {
+        PrintWriter pwMock = mock(PrintWriter.class);
+        WifiAwareShellCommand parentShellMock = mock(WifiAwareShellCommand.class);
+        when(parentShellMock.getNextArgRequired()).thenReturn("set").thenReturn(name).thenReturn(
+                value);
+        when(parentShellMock.getErrPrintWriter()).thenReturn(pwMock);
+
+        collector.checkThat(mDut.onCommand(parentShellMock), equalTo(expectSuccess ? 0 : -1));
+    }
+
     private void dumpDut(String prefix) {
         StringWriter sw = new StringWriter();
         mDut.dump(null, new PrintWriter(sw), null);
@@ -2823,6 +3217,29 @@
         }
     }
 
+    /**
+     * Simulate power state change due to doze. Changes the power manager return values and
+     * dispatches a broadcast.
+     */
+    private void simulatePowerStateChangeDoze(boolean isDozeOn) {
+        when(mMockPowerManager.isDeviceIdleMode()).thenReturn(isDozeOn);
+
+        Intent intent = new Intent(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+        mPowerBcastReceiver.onReceive(mMockContext, intent);
+    }
+
+    /**
+     * Simulate power state change due to interactive mode change (screen on/off). Changes the power
+     * manager return values and dispatches a broadcast.
+     */
+    private void simulatePowerStateChangeInteractive(boolean isInteractive) {
+        when(mMockPowerManager.isInteractive()).thenReturn(isInteractive);
+
+        Intent intent = new Intent(
+                isInteractive ? Intent.ACTION_SCREEN_ON : Intent.ACTION_SCREEN_OFF);
+        mPowerBcastReceiver.onReceive(mMockContext, intent);
+    }
+
     private static Capabilities getCapabilities() {
         Capabilities cap = new Capabilities();
         cap.maxConcurrentAwareClusters = 1;
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointConfigStoreDataTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointConfigStoreDataTest.java
index 8e808ef..7e05c3d 100644
--- a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointConfigStoreDataTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointConfigStoreDataTest.java
@@ -60,6 +60,7 @@
     private static final String TEST_CLIENT_PRIVATE_KEY_ALIAS = "ClientPrivateKey";
     private static final long TEST_PROVIDER_ID = 1;
     private static final int TEST_CREATOR_UID = 1234;
+    private static final boolean TEST_HAS_EVER_CONNECTED = true;
 
     @Mock WifiKeyStore mKeyStore;
     @Mock SIMAccessor mSimAccessor;
@@ -238,7 +239,7 @@
         providerList.add(new PasspointProvider(createFullPasspointConfiguration(),
                 mKeyStore, mSimAccessor, TEST_PROVIDER_ID, TEST_CREATOR_UID,
                 TEST_CA_CERTIFICATE_ALIAS, TEST_CLIENT_CERTIFICATE_ALIAS,
-                TEST_CLIENT_PRIVATE_KEY_ALIAS));
+                TEST_CLIENT_PRIVATE_KEY_ALIAS, TEST_HAS_EVER_CONNECTED));
 
         // Serialize data for user store.
         when(mDataSource.getProviders()).thenReturn(providerList);
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java
index 70ae354..01566c2 100644
--- a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java
@@ -18,9 +18,7 @@
 
 import static android.net.wifi.WifiManager.ACTION_PASSPOINT_DEAUTH_IMMINENT;
 import static android.net.wifi.WifiManager.ACTION_PASSPOINT_ICON;
-import static android.net.wifi.WifiManager.ACTION_PASSPOINT_OSU_PROVIDERS_LIST;
 import static android.net.wifi.WifiManager.ACTION_PASSPOINT_SUBSCRIPTION_REMEDIATION;
-import static android.net.wifi.WifiManager.EXTRA_ANQP_ELEMENT_DATA;
 import static android.net.wifi.WifiManager.EXTRA_BSSID_LONG;
 import static android.net.wifi.WifiManager.EXTRA_DELAY;
 import static android.net.wifi.WifiManager.EXTRA_ESS;
@@ -49,10 +47,13 @@
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.drawable.Icon;
+import android.net.Uri;
 import android.net.wifi.EAPConstants;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiEnterpriseConfig;
+import android.net.wifi.WifiSsid;
+import android.net.wifi.hotspot2.OsuProvider;
 import android.net.wifi.hotspot2.PasspointConfiguration;
 import android.net.wifi.hotspot2.pps.Credential;
 import android.net.wifi.hotspot2.pps.HomeSp;
@@ -68,11 +69,14 @@
 import com.android.server.wifi.WifiConfigManager;
 import com.android.server.wifi.WifiConfigStore;
 import com.android.server.wifi.WifiKeyStore;
+import com.android.server.wifi.WifiMetrics;
 import com.android.server.wifi.WifiNative;
 import com.android.server.wifi.hotspot2.anqp.ANQPElement;
 import com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType;
 import com.android.server.wifi.hotspot2.anqp.DomainNameElement;
-import com.android.server.wifi.hotspot2.anqp.RawByteElement;
+import com.android.server.wifi.hotspot2.anqp.HSOsuProvidersElement;
+import com.android.server.wifi.hotspot2.anqp.I18Name;
+import com.android.server.wifi.hotspot2.anqp.OsuProviderInfo;
 import com.android.server.wifi.util.ScanResultUtil;
 
 import org.junit.Before;
@@ -87,6 +91,7 @@
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 
 /**
@@ -124,6 +129,7 @@
     @Mock WifiConfigManager mWifiConfigManager;
     @Mock WifiConfigStore mWifiConfigStore;
     @Mock PasspointConfigStoreData.DataSource mDataSource;
+    @Mock WifiMetrics mWifiMetrics;
     PasspointManager mManager;
 
     /** Sets up test. */
@@ -135,7 +141,7 @@
                 .thenReturn(mAnqpRequestManager);
         when(mObjectFactory.makeCertificateVerifier()).thenReturn(mCertVerifier);
         mManager = new PasspointManager(mContext, mWifiNative, mWifiKeyStore, mClock,
-                mSimAccessor, mObjectFactory, mWifiConfigManager, mWifiConfigStore);
+                mSimAccessor, mObjectFactory, mWifiConfigManager, mWifiConfigStore, mWifiMetrics);
         ArgumentCaptor<PasspointEventHandler.Callbacks> callbacks =
                 ArgumentCaptor.forClass(PasspointEventHandler.Callbacks.class);
         verify(mObjectFactory).makePasspointEventHandler(any(WifiNative.class),
@@ -268,6 +274,7 @@
         scanResult.SSID = TEST_SSID;
         scanResult.BSSID = TEST_BSSID_STRING;
         scanResult.hessid = TEST_HESSID;
+        scanResult.flags = ScanResult.FLAG_PASSPOINT_NETWORK;
         return scanResult;
     }
 
@@ -291,36 +298,6 @@
     }
 
     /**
-     * Verify that the ANQP elements will be added to the AQNP cache and an
-     * {@link WifiManager#ACTION_PASSPOINT_OSU_PROVIDER_LIST} intent will be broadcasted when
-     * receiving an ANQP response containing OSU Providers element.
-     *
-     * @throws Exception
-     */
-    @Test
-    public void anqpResponseWithOSUProviders() throws Exception {
-        Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
-        byte[] testData = new byte[] {0x12, 0x34, 0x56, 0x78};
-        anqpElementMap.put(ANQPElementType.HSOSUProviders,
-                new RawByteElement(ANQPElementType.HSOSUProviders, testData));
-
-        when(mAnqpRequestManager.onRequestCompleted(TEST_BSSID, true)).thenReturn(TEST_ANQP_KEY);
-        mCallbacks.onANQPResponse(TEST_BSSID, anqpElementMap);
-        verify(mAnqpCache).addEntry(TEST_ANQP_KEY, anqpElementMap);
-
-        // Verify the broadcast intent for OSU providers.
-        ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
-        verify(mContext).sendBroadcastAsUser(intent.capture(), eq(UserHandle.ALL),
-                eq(android.Manifest.permission.ACCESS_WIFI_STATE));
-        assertEquals(ACTION_PASSPOINT_OSU_PROVIDERS_LIST, intent.getValue().getAction());
-        assertTrue(intent.getValue().getExtras().containsKey(EXTRA_BSSID_LONG));
-        assertEquals(TEST_BSSID, intent.getValue().getExtras().getLong(EXTRA_BSSID_LONG));
-        assertTrue(intent.getValue().getExtras().containsKey(EXTRA_ANQP_ELEMENT_DATA));
-        assertTrue(Arrays.equals(testData,
-                intent.getValue().getExtras().getByteArray(EXTRA_ANQP_ELEMENT_DATA)));
-    }
-
-    /**
      * Verify that no ANQP elements will be added to the ANQP cache on receiving a successful
      * response for a request that's not sent by us.
      *
@@ -436,6 +413,8 @@
     @Test
     public void addProviderWithNullConfig() throws Exception {
         assertFalse(mManager.addOrUpdateProvider(null, TEST_CREATOR_UID));
+        verify(mWifiMetrics).incrementNumPasspointProviderInstallation();
+        verify(mWifiMetrics, never()).incrementNumPasspointProviderInstallSuccess();
     }
 
     /**
@@ -446,6 +425,8 @@
     @Test
     public void addProviderWithEmptyConfig() throws Exception {
         assertFalse(mManager.addOrUpdateProvider(new PasspointConfiguration(), TEST_CREATOR_UID));
+        verify(mWifiMetrics).incrementNumPasspointProviderInstallation();
+        verify(mWifiMetrics, never()).incrementNumPasspointProviderInstallSuccess();
     }
 
     /**
@@ -460,6 +441,8 @@
         // EAP-TLS not allowed for user credential.
         config.getCredential().getUserCredential().setEapType(EAPConstants.EAP_TLS);
         assertFalse(mManager.addOrUpdateProvider(config, TEST_CREATOR_UID));
+        verify(mWifiMetrics).incrementNumPasspointProviderInstallation();
+        verify(mWifiMetrics, never()).incrementNumPasspointProviderInstallSuccess();
     }
 
     /**
@@ -476,6 +459,9 @@
         assertTrue(mManager.addOrUpdateProvider(config, TEST_CREATOR_UID));
         verifyInstalledConfig(config);
         verify(mWifiConfigManager).saveToStore(true);
+        verify(mWifiMetrics).incrementNumPasspointProviderInstallation();
+        verify(mWifiMetrics).incrementNumPasspointProviderInstallSuccess();
+        reset(mWifiMetrics);
         reset(mWifiConfigManager);
 
         // Verify content in the data source.
@@ -489,6 +475,8 @@
         assertTrue(mManager.removeProvider(TEST_FQDN));
         verify(provider).uninstallCertsAndKeys();
         verify(mWifiConfigManager).saveToStore(true);
+        verify(mWifiMetrics).incrementNumPasspointProviderUninstallation();
+        verify(mWifiMetrics).incrementNumPasspointProviderUninstallSuccess();
         assertTrue(mManager.getProviderConfigs().isEmpty());
 
         // Verify content in the data source.
@@ -511,6 +499,9 @@
         assertTrue(mManager.addOrUpdateProvider(config, TEST_CREATOR_UID));
         verifyInstalledConfig(config);
         verify(mWifiConfigManager).saveToStore(true);
+        verify(mWifiMetrics).incrementNumPasspointProviderInstallation();
+        verify(mWifiMetrics).incrementNumPasspointProviderInstallSuccess();
+        reset(mWifiMetrics);
         reset(mWifiConfigManager);
 
         // Verify content in the data source.
@@ -524,6 +515,8 @@
         assertTrue(mManager.removeProvider(TEST_FQDN));
         verify(provider).uninstallCertsAndKeys();
         verify(mWifiConfigManager).saveToStore(true);
+        verify(mWifiMetrics).incrementNumPasspointProviderUninstallation();
+        verify(mWifiMetrics).incrementNumPasspointProviderUninstallSuccess();
         assertTrue(mManager.getProviderConfigs().isEmpty());
 
         // Verify content in the data source.
@@ -549,6 +542,9 @@
         assertTrue(mManager.addOrUpdateProvider(origConfig, TEST_CREATOR_UID));
         verifyInstalledConfig(origConfig);
         verify(mWifiConfigManager).saveToStore(true);
+        verify(mWifiMetrics).incrementNumPasspointProviderInstallation();
+        verify(mWifiMetrics).incrementNumPasspointProviderInstallSuccess();
+        reset(mWifiMetrics);
         reset(mWifiConfigManager);
 
         // Verify data source content.
@@ -566,6 +562,8 @@
         assertTrue(mManager.addOrUpdateProvider(newConfig, TEST_CREATOR_UID));
         verifyInstalledConfig(newConfig);
         verify(mWifiConfigManager).saveToStore(true);
+        verify(mWifiMetrics).incrementNumPasspointProviderInstallation();
+        verify(mWifiMetrics).incrementNumPasspointProviderInstallSuccess();
 
         // Verify data source content.
         List<PasspointProvider> newProviders = mDataSource.getProviders();
@@ -588,6 +586,8 @@
         when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore),
                 eq(mSimAccessor), anyLong(), eq(TEST_CREATOR_UID))).thenReturn(provider);
         assertFalse(mManager.addOrUpdateProvider(config, TEST_CREATOR_UID));
+        verify(mWifiMetrics).incrementNumPasspointProviderInstallation();
+        verify(mWifiMetrics, never()).incrementNumPasspointProviderInstallSuccess();
     }
 
     /**
@@ -601,6 +601,8 @@
         doThrow(new GeneralSecurityException())
                 .when(mCertVerifier).verifyCaCert(any(X509Certificate.class));
         assertFalse(mManager.addOrUpdateProvider(config, TEST_CREATOR_UID));
+        verify(mWifiMetrics).incrementNumPasspointProviderInstallation();
+        verify(mWifiMetrics, never()).incrementNumPasspointProviderInstallSuccess();
     }
 
     /**
@@ -619,6 +621,8 @@
         assertTrue(mManager.addOrUpdateProvider(config, TEST_CREATOR_UID));
         verify(mCertVerifier, never()).verifyCaCert(any(X509Certificate.class));
         verifyInstalledConfig(config);
+        verify(mWifiMetrics).incrementNumPasspointProviderInstallation();
+        verify(mWifiMetrics).incrementNumPasspointProviderInstallSuccess();
     }
 
     /**
@@ -629,6 +633,8 @@
     @Test
     public void removeNonExistingProvider() throws Exception {
         assertFalse(mManager.removeProvider(TEST_FQDN));
+        verify(mWifiMetrics).incrementNumPasspointProviderUninstallation();
+        verify(mWifiMetrics, never()).incrementNumPasspointProviderUninstallSuccess();
     }
 
     /**
@@ -803,8 +809,8 @@
     }
 
     /**
-     * Verify that a {@code null} will returned when trying to get a matching
-     * {@link WifiConfiguration} a {@code null} {@link ScanResult}.
+     * Verify that a {@code null} will be returned when trying to get a matching
+     * {@link WifiConfiguration} for a {@code null} {@link ScanResult}.
      *
      * @throws Exception
      */
@@ -814,6 +820,132 @@
     }
 
     /**
+     * Verify that a {@code null} will be returned when trying to get a matching
+     * {@link WifiConfiguration} for a {@link ScanResult} with a {@code null} BSSID.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void getMatchingWifiConfigWithNullBSSID() throws Exception {
+        ScanResult scanResult = createTestScanResult();
+        scanResult.BSSID = null;
+        assertNull(mManager.getMatchingWifiConfig(scanResult));
+    }
+
+    /**
+     * Verify that a {@code null} will be returned when trying to get a matching
+     * {@link WifiConfiguration} for a {@link ScanResult} with an invalid BSSID.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void getMatchingWifiConfigWithInvalidBSSID() throws Exception {
+        ScanResult scanResult = createTestScanResult();
+        scanResult.BSSID = "asdfdasfas";
+        assertNull(mManager.getMatchingWifiConfig(scanResult));
+    }
+
+    /**
+     * Verify that a {@code null} will be returned when trying to get a matching
+     * {@link WifiConfiguration} for a non-Passpoint AP.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void getMatchingWifiConfigForNonPasspointAP() throws Exception {
+        ScanResult scanResult = createTestScanResult();
+        scanResult.flags = 0;
+        assertNull(mManager.getMatchingWifiConfig(scanResult));
+    }
+
+    /**
+     * Verify that an empty list will be returned when retrieving OSU providers for an AP with
+     * null scan result.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void getMatchingOsuProvidersForNullScanResult() throws Exception {
+        assertTrue(mManager.getMatchingOsuProviders(null).isEmpty());
+    }
+
+    /**
+     * Verify that an empty list will be returned when retrieving OSU providers for an AP with
+     * invalid BSSID.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void getMatchingOsuProvidersForInvalidBSSID() throws Exception {
+        ScanResult scanResult = createTestScanResult();
+        scanResult.BSSID = "asdfdasfas";
+        assertTrue(mManager.getMatchingOsuProviders(scanResult).isEmpty());
+    }
+
+    /**
+     * Verify that an empty list will be returned when retrieving OSU providers for a
+     * non-Passpoint AP.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void getMatchingOsuProvidersForNonPasspointAP() throws Exception {
+        ScanResult scanResult = createTestScanResult();
+        scanResult.flags = 0;
+        assertTrue(mManager.getMatchingOsuProviders(scanResult).isEmpty());
+    }
+
+    /**
+     * Verify that an empty list will be returned when no match is found from the ANQP cache.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void getMatchingOsuProviderWithNoMatch() throws Exception {
+        when(mAnqpCache.getEntry(TEST_ANQP_KEY)).thenReturn(null);
+        assertTrue(mManager.getMatchingOsuProviders(createTestScanResult()).isEmpty());
+    }
+
+    /**
+     * Verify that an expected provider list will be returned when a match is found from
+     * the ANQP cache.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void getMatchingOsuProvidersWithMatch() throws Exception {
+        // Test data.
+        WifiSsid osuSsid = WifiSsid.createFromAsciiEncoded("Test SSID");
+        String friendlyName = "Test Provider";
+        String serviceDescription = "Dummy Service";
+        Uri serverUri = Uri.parse("https://test.com");
+        String nai = "access.test.com";
+        List<Integer> methodList = Arrays.asList(1);
+        List<I18Name> friendlyNames = Arrays.asList(
+                new I18Name(Locale.ENGLISH.getLanguage(), Locale.ENGLISH, friendlyName));
+        List<I18Name> serviceDescriptions = Arrays.asList(
+                new I18Name(Locale.ENGLISH.getLanguage(), Locale.ENGLISH, serviceDescription));
+
+        // Setup OSU providers ANQP element.
+        List<OsuProviderInfo> providerInfoList = new ArrayList<>();
+        providerInfoList.add(new OsuProviderInfo(
+                friendlyNames, serverUri, methodList, null, nai, serviceDescriptions));
+        Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
+        anqpElementMap.put(ANQPElementType.HSOSUProviders,
+                new HSOsuProvidersElement(osuSsid, providerInfoList));
+        ANQPData entry = new ANQPData(mClock, anqpElementMap);
+
+        // Setup expectation.
+        OsuProvider provider = new OsuProvider(
+                osuSsid, friendlyName, serviceDescription, serverUri, nai, methodList, null);
+        List<OsuProvider> expectedList = new ArrayList<>();
+        expectedList.add(provider);
+
+        when(mAnqpCache.getEntry(TEST_ANQP_KEY)).thenReturn(entry);
+        assertEquals(expectedList, mManager.getMatchingOsuProviders(createTestScanResult()));
+    }
+
+    /**
      * Verify that the provider list maintained by the PasspointManager after the list is updated
      * in the data source.
      *
@@ -1083,4 +1215,62 @@
 
         assertFalse(PasspointManager.addLegacyPasspointConfig(wifiConfig));
     }
+
+    /**
+     * Verify that the provider's "hasEverConnected" flag will be set to true and the associated
+     * metric is updated after the provider was used to successfully connect to a Passpoint
+     * network for the first time.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void providerNetworkConnectedFirstTime() throws Exception {
+        PasspointProvider provider = addTestProvider();
+        when(provider.getHasEverConnected()).thenReturn(false);
+        mManager.onPasspointNetworkConnected(TEST_FQDN);
+        verify(provider).setHasEverConnected(eq(true));
+    }
+
+    /**
+     * Verify that the provider's "hasEverConnected" flag the associated metric is not updated
+     * after the provider was used to successfully connect to a Passpoint network for non-first
+     * time.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void providerNetworkConnectedNotFirstTime() throws Exception {
+        PasspointProvider provider = addTestProvider();
+        when(provider.getHasEverConnected()).thenReturn(true);
+        mManager.onPasspointNetworkConnected(TEST_FQDN);
+        verify(provider, never()).setHasEverConnected(anyBoolean());
+    }
+
+    /**
+     * Verify that the expected Passpoint metrics are updated when
+     * {@link PasspointManager#updateMetrics} is invoked.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void updateMetrics() throws Exception {
+        PasspointProvider provider = addTestProvider();
+
+        // Provider have not provided a successful network connection.
+        int expectedInstalledProviders = 1;
+        int expectedConnectedProviders = 0;
+        when(provider.getHasEverConnected()).thenReturn(false);
+        mManager.updateMetrics();
+        verify(mWifiMetrics).updateSavedPasspointProfiles(
+                eq(expectedInstalledProviders), eq(expectedConnectedProviders));
+        reset(provider);
+        reset(mWifiMetrics);
+
+        // Provider have provided a successful network connection.
+        expectedConnectedProviders = 1;
+        when(provider.getHasEverConnected()).thenReturn(true);
+        mManager.updateMetrics();
+        verify(mWifiMetrics).updateSavedPasspointProfiles(
+                eq(expectedInstalledProviders), eq(expectedConnectedProviders));
+    }
 }
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 2224486..9f61ca0 100644
--- a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointNetworkEvaluatorTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointNetworkEvaluatorTest.java
@@ -333,4 +333,30 @@
         assertEquals(ScanResultUtil.createQuotedSSID(TEST_SSID2), config.SSID);
         assertEquals(TEST_NETWORK_ID, config.networkId);
     }
+
+    /**
+     * Verify that null will be returned when matching a SIM credential provider without SIM
+     * card installed.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void evaluateScanMatchingSIMProviderWithoutSIMCard() throws Exception {
+        // Setup ScanDetail and match providers.
+        List<ScanDetail> scanDetails = Arrays.asList(new ScanDetail[] {
+                generateScanDetail(TEST_SSID1)});
+        PasspointProvider testProvider = mock(PasspointProvider.class);
+        Pair<PasspointProvider, PasspointMatch> homeProvider = Pair.create(
+                testProvider, PasspointMatch.HomeProvider);
+
+        List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
+        when(mPasspointManager.matchProvider(any(ScanResult.class))).thenReturn(homeProvider);
+        when(testProvider.isSimCredential()).thenReturn(true);
+        when(mWifiConfigManager.isSimPresent()).thenReturn(false);
+        assertEquals(null, mEvaluator.evaluateNetworks(
+                scanDetails, null, null, false, false, connectableNetworks));
+        assertTrue(connectableNetworks.isEmpty());
+        verify(testProvider, never()).getWifiConfig();
+
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProviderTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProviderTest.java
index c416a96..9ee9fc6 100644
--- a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProviderTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProviderTest.java
@@ -898,4 +898,92 @@
         assertEquals(passpointConfig, PasspointProvider.convertFromWifiConfig(wifiConfig));
     }
 
+    /**
+     * Verify that {@link PasspointProvider#isSimCredential} will return true for provider that's
+     * backed by a SIM credential.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void providerBackedBySimCredential() throws Exception {
+        // Test data.
+        String fqdn = "test.com";
+        String friendlyName = "Friendly Name";
+        long[] rcOIs = new long[] {0x1234L, 0x2345L};
+        String realm = "realm.com";
+        String imsi = "1234*";
+
+        // Create provider with SIM credential.
+        PasspointConfiguration config = new PasspointConfiguration();
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFqdn(fqdn);
+        homeSp.setFriendlyName(friendlyName);
+        homeSp.setRoamingConsortiumOis(rcOIs);
+        config.setHomeSp(homeSp);
+        Credential credential = new Credential();
+        credential.setRealm(realm);
+        Credential.SimCredential simCredential = new Credential.SimCredential();
+        simCredential.setImsi(imsi);
+        simCredential.setEapType(EAPConstants.EAP_SIM);
+        credential.setSimCredential(simCredential);
+        config.setCredential(credential);
+        mProvider = createProvider(config);
+
+        assertTrue(mProvider.isSimCredential());
+    }
+
+    /**
+     * Verify that {@link PasspointProvider#isSimCredential} will return false for provider that's
+     * not backed by a SIM credential.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void providerNotBackedBySimCredential() throws Exception {
+        // Test data.
+        String fqdn = "test.com";
+        String friendlyName = "Friendly Name";
+        long[] rcOIs = new long[] {0x1234L, 0x2345L};
+        String realm = "realm.com";
+
+        // Create provider with certificate credential.
+        PasspointConfiguration config = new PasspointConfiguration();
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFqdn(fqdn);
+        homeSp.setFriendlyName(friendlyName);
+        homeSp.setRoamingConsortiumOis(rcOIs);
+        config.setHomeSp(homeSp);
+        Credential credential = new Credential();
+        Credential.CertificateCredential certCredential = new Credential.CertificateCredential();
+        certCredential.setCertType(Credential.CertificateCredential.CERT_TYPE_X509V3);
+        credential.setCertCredential(certCredential);
+        credential.setRealm(realm);
+        config.setCredential(credential);
+        mProvider = createProvider(config);
+
+        assertFalse(mProvider.isSimCredential());
+    }
+
+    /**
+     * Verify that hasEverConnected flag is set correctly using
+     * {@link PasspointProvider#setHasEverConnected}.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void setHasEverConnected() throws Exception {
+        PasspointConfiguration config = new PasspointConfiguration();
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFqdn("test1");
+        config.setHomeSp(homeSp);
+        Credential credential = new Credential();
+        credential.setUserCredential(new Credential.UserCredential());
+        config.setCredential(credential);
+        mProvider = createProvider(config);
+        verifyInstalledConfig(config, true);
+
+        assertFalse(mProvider.getHasEverConnected());
+        mProvider.setHasEverConnected(true);
+        assertTrue(mProvider.getHasEverConnected());
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/ANQPParserTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/ANQPParserTest.java
index 8f019e0..59332e4 100644
--- a/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/ANQPParserTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/ANQPParserTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 
+import android.net.wifi.WifiSsid;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import org.junit.Test;
@@ -216,6 +217,22 @@
     }
 
     /**
+     * Helper function for generating payload for a Hotspot 2.0 OSU Providers List ANQP
+     * element.
+     *
+     * @param osuSsidBytes Bytes of OSU SSID
+     * @return byte[]
+     */
+    private static byte[] getHSOsuProvidersPayload(byte[] osuSsidBytes) throws IOException {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        out.write((byte) osuSsidBytes.length);
+        out.write(osuSsidBytes);
+        out.write((byte) 1);
+        out.write(OsuProviderInfoTestUtil.TEST_OSU_PROVIDER_INFO_RAW_BYTES);
+        return out.toByteArray();
+    }
+
+    /**
      * Helper function for generating payload for a list of I18Name.
      *
      * @param language Array of language
@@ -473,10 +490,12 @@
      */
     @Test
     public void parseHSOUSProvidersElement() throws Exception {
-        byte[] data = new byte[10];
+        byte[] osuSsidBytes = "Test SSID".getBytes(StandardCharsets.UTF_8);
+        byte[] data = getHSOsuProvidersPayload(osuSsidBytes);
 
-        RawByteElement expected =
-                new RawByteElement(Constants.ANQPElementType.HSOSUProviders, data);
+        HSOsuProvidersElement expected = new HSOsuProvidersElement(
+                WifiSsid.createFromByteArray(osuSsidBytes),
+                Arrays.asList(OsuProviderInfoTestUtil.TEST_OSU_PROVIDER_INFO));
 
         ByteBuffer buffer = ByteBuffer.wrap(data);
         assertEquals(expected,
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/HSIconFileElementTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/HSIconFileElementTest.java
new file mode 100644
index 0000000..c72d1d0
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/HSIconFileElementTest.java
@@ -0,0 +1,112 @@
+/*
+ * 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.anqp;
+
+import static org.junit.Assert.assertEquals;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.anqp.HSIconFileElement}.
+ */
+@SmallTest
+public class HSIconFileElementTest {
+    private static final String TEST_ICON_TYPE = "png";
+    private static final byte[] TEST_ICON_DATA = new byte[8];
+
+    /**
+     * Utility function for generating test data.
+     *
+     * @param statusCode Status code of the icon file download
+     * @return byte[]
+     */
+    private static byte[] getTestData(int statusCode) throws IOException {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        out.write((byte) statusCode);
+        if (statusCode != HSIconFileElement.STATUS_CODE_SUCCESS) {
+            // No need to write other data if status code is not success.
+            return out.toByteArray();
+        }
+
+        byte[] iconTypeBytes = TEST_ICON_TYPE.getBytes(StandardCharsets.US_ASCII);
+        out.write(iconTypeBytes.length);
+        out.write(iconTypeBytes);
+        out.write(TEST_ICON_DATA.length & 0xFF);
+        out.write((TEST_ICON_DATA.length >> 8) & 0xFF);
+        out.write(TEST_ICON_DATA);
+        return out.toByteArray();
+    }
+
+    /**
+     * Verify that BufferUnderflowException will be thrown when parsing an empty buffer.
+     * @throws Exception
+     */
+    @Test(expected = BufferUnderflowException.class)
+    public void parseEmptyBuffer() throws Exception {
+        HSIconFileElement.parse(ByteBuffer.allocate(0));
+    }
+
+    /**
+     * Verify that BufferUnderflowException will be thrown when parsing a truncated buffer
+     * (missing a byte at the end).
+     *
+     * @throws Exception
+     */
+    @Test(expected = BufferUnderflowException.class)
+    public void parseTruncatedBuffer() throws Exception {
+        ByteBuffer buffer = ByteBuffer.wrap(getTestData(HSIconFileElement.STATUS_CODE_SUCCESS));
+        buffer.limit(buffer.remaining() - 1);
+        HSIconFileElement.parse(buffer);
+    }
+
+    /**
+     * Verify that an expected {@link HSIconFileElement} is returned when parsing a buffer
+     * containing icon data.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseBufferWithIconData() throws Exception {
+        ByteBuffer buffer = ByteBuffer.wrap(getTestData(HSIconFileElement.STATUS_CODE_SUCCESS));
+        HSIconFileElement expected = new HSIconFileElement(
+                HSIconFileElement.STATUS_CODE_SUCCESS, TEST_ICON_TYPE, TEST_ICON_DATA);
+        assertEquals(expected, HSIconFileElement.parse(buffer));
+    }
+
+    /**
+     * Verify that an expected {@link HSIconFileElement} is returned when parsing a buffer
+     * without icon data (icon file not found).
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseBufferWithoutIconData() throws Exception {
+        ByteBuffer buffer =
+                ByteBuffer.wrap(getTestData(HSIconFileElement.STATUS_CODE_FILE_NOT_FOUND));
+        HSIconFileElement expected =
+                new HSIconFileElement(HSIconFileElement.STATUS_CODE_FILE_NOT_FOUND, null, null);
+        assertEquals(expected, HSIconFileElement.parse(buffer));
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/HSOsuProvidersElementTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/HSOsuProvidersElementTest.java
new file mode 100644
index 0000000..c4f7387
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/HSOsuProvidersElementTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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.anqp;
+
+import static org.junit.Assert.assertEquals;
+
+import android.net.wifi.WifiSsid;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.anqp.HSOsuProvidersElement}.
+ */
+@SmallTest
+public class HSOsuProvidersElementTest {
+    private static final byte[] TEST_OSU_SSID_BYTES = "Test SSID".getBytes(StandardCharsets.UTF_8);
+    private static final WifiSsid TEST_OSU_SSID =
+            WifiSsid.createFromByteArray(TEST_OSU_SSID_BYTES);
+    private static final List<OsuProviderInfo> TEST_PROVIDER_LIST =
+            Arrays.asList(OsuProviderInfoTestUtil.TEST_OSU_PROVIDER_INFO);
+
+    private static final HSOsuProvidersElement TEST_OSU_PROVIDERS_ELEMENT =
+            new HSOsuProvidersElement(TEST_OSU_SSID, TEST_PROVIDER_LIST);
+
+    /**
+     * Utility function for generating test data.
+     *
+     * @param osuSsidBytes The OSU SSID bytes
+     * @return byte[]
+     */
+    private static byte[] getTestData(byte[] osuSsidBytes) {
+        try {
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+            out.write((byte) osuSsidBytes.length);
+            out.write(osuSsidBytes);
+            out.write((byte) TEST_PROVIDER_LIST.size());
+            out.write(OsuProviderInfoTestUtil.TEST_OSU_PROVIDER_INFO_RAW_BYTES);
+            return out.toByteArray();
+        } catch (IOException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Verify that BufferUnderflowException will be thrown when parsing an empty buffer.
+     * @throws Exception
+     */
+    @Test(expected = BufferUnderflowException.class)
+    public void parseEmptyBuffer() throws Exception {
+        HSOsuProvidersElement.parse(ByteBuffer.allocate(0));
+    }
+
+    /**
+     * Verify that BufferUnderflowException will be thrown when parsing a truncated buffer
+     * (missing a byte at the end).
+     *
+     * @throws Exception
+     */
+    @Test(expected = BufferUnderflowException.class)
+    public void parseTruncatedBuffer() throws Exception {
+        ByteBuffer buffer = ByteBuffer.wrap(getTestData(TEST_OSU_SSID_BYTES));
+        buffer.limit(buffer.remaining() - 1);
+        HSOsuProvidersElement.parse(buffer);
+    }
+
+    /**
+     * Verify that ProtocolException will be thrown when parsing a buffer containing an
+     * invalid OSU SSID.
+     *
+     * @throws Exception
+     */
+    @Test(expected = ProtocolException.class)
+    public void parseBufferWithInvalidLength() throws Exception {
+        byte[] invalidSsidBytes = new byte[HSOsuProvidersElement.MAXIMUM_OSU_SSID_LENGTH + 1];
+        ByteBuffer buffer = ByteBuffer.wrap(getTestData(invalidSsidBytes));
+        HSOsuProvidersElement.parse(buffer);
+    }
+
+    /**
+     * Verify that an expected {@link HSOsuProvidersElement} will be returned when parsing a buffer
+     * containing pre-defined test data.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseBufferWithTestData() throws Exception {
+        ByteBuffer buffer = ByteBuffer.wrap(getTestData(TEST_OSU_SSID_BYTES));
+        assertEquals(TEST_OSU_PROVIDERS_ELEMENT, HSOsuProvidersElement.parse(buffer));
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/IconInfoTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/IconInfoTest.java
new file mode 100644
index 0000000..429b36a
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/IconInfoTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.anqp;
+
+import static org.junit.Assert.assertEquals;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.anqp.IconInfo}.
+ */
+@SmallTest
+public class IconInfoTest {
+    /**
+     * Verify that BufferUnderflowException will be thrown when parsing an empty buffer.
+     * @throws Exception
+     */
+    @Test(expected = BufferUnderflowException.class)
+    public void parseEmptyBuffer() throws Exception {
+        IconInfo.parse(ByteBuffer.allocate(0));
+    }
+
+    /**
+     * Verify that BufferUnderflowException will be thrown when parsing a truncated buffer
+     * (missing a byte at the end).
+     *
+     * @throws Exception
+     */
+    @Test(expected = BufferUnderflowException.class)
+    public void parseTruncatedBuffer() throws Exception {
+        ByteBuffer buffer = ByteBuffer.wrap(IconInfoTestUtil.TEST_ICON_INFO_RAW_BYTES);
+        buffer.limit(buffer.remaining() - 1);
+        IconInfo.parse(buffer);
+    }
+
+    /**
+     * Verify that an expected {@link IconInfo} will be returned when parsing a buffer containing
+     * pre-defined test data.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseBufferWithTestData() throws Exception {
+        ByteBuffer buffer = ByteBuffer.wrap(IconInfoTestUtil.TEST_ICON_INFO_RAW_BYTES);
+        assertEquals(IconInfoTestUtil.TEST_ICON_INFO, IconInfo.parse(buffer));
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/IconInfoTestUtil.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/IconInfoTestUtil.java
new file mode 100644
index 0000000..fa0e9f8
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/IconInfoTestUtil.java
@@ -0,0 +1,89 @@
+/*
+ * 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.anqp;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Utility class containing test data and object for {@link IconInfo}.
+ */
+public class IconInfoTestUtil {
+    // Test data
+    private static final int TEST_WIDTH = 9811;
+    private static final int TEST_HEIGHT = 4523;
+    private static final String TEST_LANGUAGE = "en";
+    private static final String TEST_TYPE = "png";
+    private static final String TEST_FILENAME = "testicon.png";
+
+    /**
+     * {@link IconInfo} object with pre-defined test data.
+     */
+    public static final IconInfo TEST_ICON_INFO =
+            new IconInfo(TEST_WIDTH, TEST_HEIGHT, TEST_LANGUAGE, TEST_TYPE, TEST_FILENAME);
+
+    /**
+     * Raw bytes of icon info with pre-defined test data.
+     */
+    public static final byte[] TEST_ICON_INFO_RAW_BYTES = getTestData();
+
+    /**
+     * Generate raw bytes based on the pre-defined test data.
+     *
+     * @return array of bytes
+     */
+    private static byte[] getTestData() {
+        try {
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+            writeShortLE(out, TEST_WIDTH);
+            writeShortLE(out, TEST_HEIGHT);
+            out.write(TEST_LANGUAGE.getBytes(StandardCharsets.US_ASCII));
+            out.write((byte) 0);    // Padding for language code.
+            writeByteArrayWithLength(out, TEST_TYPE.getBytes(StandardCharsets.US_ASCII));
+            writeByteArrayWithLength(out, TEST_FILENAME.getBytes(StandardCharsets.UTF_8));
+            return out.toByteArray();
+        } catch (IOException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Write the lower 2-bytes of an integer to the given output stream in Little-Endian.
+     *
+     * @param out The output stream to write to
+     * @param value The integer value to write
+     */
+    private static void writeShortLE(ByteArrayOutputStream out, int value) {
+        out.write(value & 0xFF);
+        out.write((value >> 8) & 0xFF);
+    }
+
+    /**
+     * Write the given byte array to the given output stream, the array data is prefixed with a
+     * byte specifying the length of the byte array.
+     *
+     * @param out The output stream to write to
+     * @param data The byte array to write
+     * @throws IOException
+     */
+    private static void writeByteArrayWithLength(ByteArrayOutputStream out, byte[] data)
+            throws IOException {
+        out.write((byte) data.length);
+        out.write(data);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/OsuProviderInfoTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/OsuProviderInfoTest.java
new file mode 100644
index 0000000..8ef93f0
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/OsuProviderInfoTest.java
@@ -0,0 +1,207 @@
+/*
+ * 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.anqp;
+
+import static org.junit.Assert.assertEquals;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.anqp.OsuProviderInfo}.
+ */
+@SmallTest
+public class OsuProviderInfoTest {
+    /**
+     * Verify that BufferUnderflowException will be thrown when parsing an empty buffer.
+     * @throws Exception
+     */
+    @Test(expected = BufferUnderflowException.class)
+    public void parseEmptyBuffer() throws Exception {
+        OsuProviderInfo.parse(ByteBuffer.allocate(0));
+    }
+
+    /**
+     * Verify that BufferUnderflowException will be thrown when parsing a truncated buffer
+     * (missing a byte at the end).
+     *
+     * @throws Exception
+     */
+    @Test(expected = BufferUnderflowException.class)
+    public void parseTruncatedBuffer() throws Exception {
+        ByteBuffer buffer = ByteBuffer.wrap(
+                OsuProviderInfoTestUtil.TEST_OSU_PROVIDER_INFO_RAW_BYTES);
+        buffer.limit(buffer.remaining() - 1);
+        OsuProviderInfo.parse(buffer);
+    }
+
+    /**
+     * Verify that ProtocolException will be thrown when parsing a buffer containing an
+     * invalid length value.
+     *
+     * @throws Exception
+     */
+    @Test(expected = ProtocolException.class)
+    public void parseBufferWithInvalidLength() throws Exception {
+        ByteBuffer buffer = ByteBuffer.wrap(
+                OsuProviderInfoTestUtil.TEST_OSU_PROVIDER_INFO_RAW_BYTES_WITH_INVALID_LENGTH);
+        OsuProviderInfo.parse(buffer);
+    }
+
+    /**
+     * Verify that an expected {@link OsuProviderInfo} will be returned when parsing a buffer
+     * containing pre-defined test data.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseBufferWithTestData() throws Exception {
+        ByteBuffer buffer = ByteBuffer.wrap(
+                OsuProviderInfoTestUtil.TEST_OSU_PROVIDER_INFO_RAW_BYTES);
+        assertEquals(OsuProviderInfoTestUtil.TEST_OSU_PROVIDER_INFO,
+                OsuProviderInfo.parse(buffer));
+    }
+
+    /**
+     * Verify that when a provider contained multiple friendly names in different languages, the
+     * friendly name that's in default language is returned.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void getFriendlyNameMatchingDefaultLocale() throws Exception {
+        List<I18Name> friendlyNames = new ArrayList<>();
+        Locale defaultLocale = Locale.getDefault();
+        Locale nonDefaultLocale = Locale.FRENCH;
+        if (defaultLocale.equals(nonDefaultLocale)) {
+            nonDefaultLocale = Locale.ENGLISH;
+        }
+        String nonDefaultString = "Non-default";
+        String defaultString = "Default";
+        friendlyNames.add(
+                new I18Name(nonDefaultLocale.getLanguage(), nonDefaultLocale, nonDefaultString));
+        friendlyNames.add(new I18Name(defaultLocale.getLanguage(), defaultLocale, defaultString));
+        OsuProviderInfo providerInfo =
+                new OsuProviderInfo(friendlyNames, null, null, null, null, null);
+        assertEquals(defaultString, providerInfo.getFriendlyName());
+    }
+
+    /**
+     * Verify that when a provider contained multiple friendly names where no friendly name
+     * is in default language, the first name in the list is returned.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void getFriendlyNameNotMatchingDefaultLocale() throws Exception {
+        List<I18Name> friendlyNames = new ArrayList<>();
+        Locale nonDefaultLocale = Locale.FRENCH;
+        if (nonDefaultLocale.equals(Locale.getDefault())) {
+            nonDefaultLocale = Locale.ENGLISH;
+        }
+        String firstString = "First name";
+        String secondString = "Second name";
+        friendlyNames.add(
+                new I18Name(nonDefaultLocale.getLanguage(), nonDefaultLocale, firstString));
+        friendlyNames.add(
+                new I18Name(nonDefaultLocale.getLanguage(), nonDefaultLocale, secondString));
+        OsuProviderInfo providerInfo =
+                new OsuProviderInfo(friendlyNames, null, null, null, null, null);
+        assertEquals(firstString, providerInfo.getFriendlyName());
+    }
+
+    /**
+     * Verify that null will be returned for a provider containing empty friendly name list.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void getFriendlyNameWithEmptyList() throws Exception {
+        OsuProviderInfo providerInfo =
+                new OsuProviderInfo(new ArrayList<I18Name>(), null, null, null, null, null);
+        assertEquals(null, providerInfo.getFriendlyName());
+    }
+
+    /**
+     * Verify that when a provider contained multiple service descriptions in different languages,
+     * the service description that's in default language is returned.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void getServiceDescriptionMatchingDefaultLocale() throws Exception {
+        List<I18Name> serviceDescriptions = new ArrayList<>();
+        Locale defaultLocale = Locale.getDefault();
+        Locale nonDefaultLocale = Locale.FRENCH;
+        if (defaultLocale.equals(nonDefaultLocale)) {
+            nonDefaultLocale = Locale.ENGLISH;
+        }
+        String nonDefaultString = "Non-default";
+        String defaultString = "Default";
+        serviceDescriptions.add(
+                new I18Name(nonDefaultLocale.getLanguage(), nonDefaultLocale, nonDefaultString));
+        serviceDescriptions.add(
+                new I18Name(defaultLocale.getLanguage(), defaultLocale, defaultString));
+        OsuProviderInfo providerInfo =
+                new OsuProviderInfo(null, null, null, null, null, serviceDescriptions);
+        assertEquals(defaultString, providerInfo.getServiceDescription());
+    }
+
+    /**
+     * Verify that when a provider contained multiple service descriptions where none of them
+     * is in default language, the first element in the list is returned.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void getServiceDescriptionNotMatchingDefaultLocale() throws Exception {
+        List<I18Name> serviceDescriptions = new ArrayList<>();
+        Locale nonDefaultLocale = Locale.FRENCH;
+        if (nonDefaultLocale.equals(Locale.getDefault())) {
+            nonDefaultLocale = Locale.ENGLISH;
+        }
+        String firstString = "First name";
+        String secondString = "Second name";
+        serviceDescriptions.add(
+                new I18Name(nonDefaultLocale.getLanguage(), nonDefaultLocale, firstString));
+        serviceDescriptions.add(
+                new I18Name(nonDefaultLocale.getLanguage(), nonDefaultLocale, secondString));
+        OsuProviderInfo providerInfo =
+                new OsuProviderInfo(null, null, null, null, null, serviceDescriptions);
+        assertEquals(firstString, providerInfo.getServiceDescription());
+    }
+
+    /**
+     * Verify that null will be returned for a provider containing empty friendly name list.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void getServiceDescriptionWithEmptyList() throws Exception {
+        OsuProviderInfo providerInfo =
+                new OsuProviderInfo(null, null, null, null, null, new ArrayList<I18Name>());
+        assertEquals(null, providerInfo.getServiceDescription());
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/OsuProviderInfoTestUtil.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/OsuProviderInfoTestUtil.java
new file mode 100644
index 0000000..2b4c631
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/OsuProviderInfoTestUtil.java
@@ -0,0 +1,181 @@
+/*
+ * 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.anqp;
+
+import android.net.Uri;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Utility class containing test data and object for {@link OsuProviderInfo}.
+ */
+public class OsuProviderInfoTestUtil {
+    // Test data
+    private static final List<I18Name> TEST_FRIENDLY_NAMES =
+            Arrays.asList(new I18Name("en", Locale.forLanguageTag("en"), "FriendlyName"));
+    private static final String TEST_SERVER_URI = "test.server.com";
+    private static final List<Integer> TEST_METHOD_LIST = Arrays.asList(0, 1);
+    private static final List<IconInfo> TEST_ICON_INFO_LIST =
+            Arrays.asList(IconInfoTestUtil.TEST_ICON_INFO);
+    private static final String TEST_NAI = "network_access@test.com";
+    private static final List<I18Name> TEST_SERVICE_DESCRIPTIONS =
+            Arrays.asList(new I18Name("en", Locale.forLanguageTag("en"), "Test Service"));
+
+    /**
+     * {@link IconInfo} object with pre-defined test data.
+     */
+    public static final OsuProviderInfo TEST_OSU_PROVIDER_INFO =
+            new OsuProviderInfo(TEST_FRIENDLY_NAMES, Uri.parse(TEST_SERVER_URI), TEST_METHOD_LIST,
+                    TEST_ICON_INFO_LIST, TEST_NAI, TEST_SERVICE_DESCRIPTIONS);
+
+    /**
+     * Raw bytes of icon info with pre-defined test data.
+     */
+    public static final byte[] TEST_OSU_PROVIDER_INFO_RAW_BYTES = getTestData();
+
+    public static final byte[] TEST_OSU_PROVIDER_INFO_RAW_BYTES_WITH_INVALID_LENGTH =
+            getTestDataWithInvalidLength();
+
+    /**
+     * Generate and return the raw data based on pre-defined test data.
+     *
+     * @return byte[]
+     */
+    private static byte[] getTestData() {
+        try {
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+            byte[] payload = getTestPayload();
+            writeShortLE(out, payload.length);
+            out.write(payload);
+            return out.toByteArray();
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    /**
+     * Generate and return the raw data based on pre-defined test data.
+     *
+     * @return byte[]
+     */
+    private static byte[] getTestDataWithInvalidLength() {
+        try {
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+            byte[] payload = getTestPayload();
+            // Set length to less than the minimum required.
+            writeShortLE(out, OsuProviderInfo.MINIMUM_LENGTH - 1);
+            out.write(payload);
+            return out.toByteArray();
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    /**
+     * Generate and return the payload containing OSU provider test data, excluding the length
+     * field.
+     *
+     * @return byte[]
+     * @throws IOException
+     */
+    private static byte[] getTestPayload() throws IOException {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+        // Write friendly name list.
+        byte[] friendlyNamesData = getI18NameListData(TEST_FRIENDLY_NAMES);
+        writeShortLE(out, friendlyNamesData.length);
+        out.write(friendlyNamesData);
+
+        // Write server URI.
+        writeByteArrayWithLength(out, TEST_SERVER_URI.getBytes(StandardCharsets.UTF_8));
+
+        // Write method list.
+        out.write((byte) TEST_METHOD_LIST.size());
+        for (Integer method : TEST_METHOD_LIST) {
+            out.write((byte) method.intValue());
+        }
+
+        // Write icon info list.
+        writeShortLE(out, IconInfoTestUtil.TEST_ICON_INFO_RAW_BYTES.length);
+        out.write(IconInfoTestUtil.TEST_ICON_INFO_RAW_BYTES);
+
+        // Write NAI.
+        writeByteArrayWithLength(out, TEST_NAI.getBytes(StandardCharsets.UTF_8));
+
+        // Write service descriptions.
+        byte[] serviceDescriptionsData = getI18NameListData(TEST_SERVICE_DESCRIPTIONS);
+        writeShortLE(out, serviceDescriptionsData.length);
+        out.write(serviceDescriptionsData);
+
+        return out.toByteArray();
+    }
+
+    private static byte[] getI18NameListData(List<I18Name> nameList) throws IOException {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        for (I18Name name : nameList) {
+            byte[] data = getI18NameData(name);
+            out.write((byte) data.length);
+            out.write(data);
+        }
+        return out.toByteArray();
+    }
+
+    /**
+     * Format the raw bytes for the given {@link I18Name}.
+     *
+     * @param value The {@link I18Name} to serialize
+     * @return byte[]
+     * @throws IOException
+     */
+    private static byte[] getI18NameData(I18Name value) throws IOException {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        out.write(value.getLanguage().getBytes(StandardCharsets.US_ASCII));
+        out.write((byte) 0);    // Padding for language code.
+        out.write(value.getText().getBytes(StandardCharsets.UTF_8));
+        return out.toByteArray();
+    }
+
+    /**
+     * Write the lower 2-bytes of an integer to the given output stream in Little-Endian.
+     *
+     * @param out The output stream to write to
+     * @param value The integer value to write
+     */
+    private static void writeShortLE(ByteArrayOutputStream out, int value) {
+        out.write(value & 0xFF);
+        out.write((value >> 8) & 0xFF);
+    }
+
+    /**
+     * Write the given byte array to the given output stream, the array data is prefixed with a
+     * byte specifying the length of the byte array.
+     *
+     * @param out The output stream to write to
+     * @param data The byte array to write
+     * @throws IOException
+     */
+    private static void writeByteArrayWithLength(ByteArrayOutputStream out, byte[] data)
+            throws IOException {
+        out.write((byte) data.length);
+        out.write(data);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/p2p/SupplicantP2pIfaceHalTest.java b/tests/wifitests/src/com/android/server/wifi/p2p/SupplicantP2pIfaceHalTest.java
index bcce0ac..24f4854 100644
--- a/tests/wifitests/src/com/android/server/wifi/p2p/SupplicantP2pIfaceHalTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/p2p/SupplicantP2pIfaceHalTest.java
@@ -1030,7 +1030,7 @@
         // Default value when service is not initialized.
         assertNull(mDut.getSsid(mPeerMacAddress));
         executeAndValidateInitializationSequence(false, false, false);
-        assertEquals(mSsid, mDut.getSsid(mPeerMacAddress));
+        assertEquals(NativeUtil.removeEnclosingQuotes(mSsid), mDut.getSsid(mPeerMacAddress));
     }
 
     /**
diff --git a/tests/wifitests/src/com/android/server/wifi/scanner/WifiScanningServiceTest.java b/tests/wifitests/src/com/android/server/wifi/scanner/WifiScanningServiceTest.java
index 85b19fe..01835cd 100644
--- a/tests/wifitests/src/com/android/server/wifi/scanner/WifiScanningServiceTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/scanner/WifiScanningServiceTest.java
@@ -55,6 +55,7 @@
 import com.android.server.wifi.WifiInjector;
 import com.android.server.wifi.WifiMetrics;
 import com.android.server.wifi.WifiNative;
+import com.android.server.wifi.aware.WifiAwareMetrics;
 import com.android.server.wifi.nano.WifiMetricsProto;
 import com.android.server.wifi.util.WifiAsyncChannel;
 
@@ -114,7 +115,7 @@
                 new int[]{5600, 5650, 5660});
 
         mLooper = new TestLooper();
-        mWifiMetrics = new WifiMetrics(mClock, mLooper.getLooper());
+        mWifiMetrics = new WifiMetrics(mClock, mLooper.getLooper(), new WifiAwareMetrics(mClock));
         when(mWifiScannerImplFactory
                 .create(any(), any(), any()))
                 .thenReturn(mWifiScannerImpl);
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 3a5f6b9..ed7c582 100644
--- a/tests/wifitests/src/com/android/server/wifi/scanner/WificondScannerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/scanner/WificondScannerTest.java
@@ -37,8 +37,12 @@
 import org.junit.Test;
 import org.mockito.InOrder;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Set;
+import java.util.regex.Pattern;
 
 /**
  * Unit tests for {@link com.android.server.wifi.scanner.WificondScannerImpl}.
@@ -554,6 +558,28 @@
     }
 
     /**
+     * Test that dump() of WificondScannerImpl dumps native scan results.
+     */
+    @Test
+    public void dumpContainsNativeScanResults() {
+        assertDumpContainsRequestLog("Latest native scan results:");
+    }
+
+    private void assertDumpContainsRequestLog(String log) {
+        String objectDump = dumpObject();
+        Pattern logLineRegex = Pattern.compile(".*" + log + ".*");
+        assertTrue("dump did not contain log = " + log + "\n " + objectDump + "\n",
+                logLineRegex.matcher(objectDump).find());
+    }
+
+    private String dumpObject() {
+        StringWriter stringWriter = new StringWriter();
+        mScanner.dump(new FileDescriptor(), new PrintWriter(stringWriter),
+                new String[0]);
+        return stringWriter.toString();
+    }
+
+    /**
      * Run a test with the given settings where all native scans succeed
      * This will execute expectedPeriods.length scan periods by first
      * starting the scan settings and then dispatching the scan period alarm to start the
diff --git a/tests/wifitests/src/com/android/server/wifi/util/ScanResultUtilTest.java b/tests/wifitests/src/com/android/server/wifi/util/ScanResultUtilTest.java
index 7b03698..92cae3b 100644
--- a/tests/wifitests/src/com/android/server/wifi/util/ScanResultUtilTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/util/ScanResultUtilTest.java
@@ -85,32 +85,6 @@
     }
 
     @Test
-    public void testScanResultMatchingWithNetwork() {
-        final String ssid = "Another SSid";
-        WifiConfiguration config = new WifiConfiguration();
-        config.SSID = ScanResultUtil.createQuotedSSID(ssid);
-        ScanResult scanResult = new ScanResult(ssid, "ab:cd:01:ef:45:89", 1245, 0, "",
-                -78, 2450, 1025, 22, 33, 20, 0, 0, true);
-
-        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
-        scanResult.capabilities = "";
-        assertTrue(ScanResultUtil.doesScanResultMatchWithNetwork(scanResult, config));
-
-        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
-        config.wepKeys[0] = "45592364648547";
-        scanResult.capabilities = "WEP";
-        assertTrue(ScanResultUtil.doesScanResultMatchWithNetwork(scanResult, config));
-
-        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
-        scanResult.capabilities = "PSK";
-        assertTrue(ScanResultUtil.doesScanResultMatchWithNetwork(scanResult, config));
-
-        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
-        scanResult.capabilities = "EAP";
-        assertTrue(ScanResultUtil.doesScanResultMatchWithNetwork(scanResult, config));
-    }
-
-    @Test
     public void testNetworkCreationFromScanResult() {
         final String ssid = "Another SSid";
         ScanResult scanResult = new ScanResult(ssid, "ab:cd:01:ef:45:89", 1245, 0, "",