WifiScanningServiceImpl: Add ClientInfo null checks am: 5382acb5eb
am: 0df9502510

Change-Id: Ibd4c01c4a32935d2a1d72ef179dbca9d8b3ff2c7
diff --git a/libwifi_hal/Android.mk b/libwifi_hal/Android.mk
new file mode 100644
index 0000000..f39617c
--- /dev/null
+++ b/libwifi_hal/Android.mk
@@ -0,0 +1,128 @@
+# 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)
+ifneq ($(TARGET_BUILD_PDK), true)
+
+wifi_hal_cflags := \
+    -Wall \
+    -Werror \
+    -Wextra \
+    -Winit-self \
+    -Wno-unused-function \
+    -Wno-unused-parameter \
+    -Wshadow \
+    -Wunused-variable \
+    -Wwrite-strings
+ifdef WIFI_DRIVER_MODULE_PATH
+wifi_hal_cflags += -DWIFI_DRIVER_MODULE_PATH=\"$(WIFI_DRIVER_MODULE_PATH)\"
+endif
+ifdef WIFI_DRIVER_MODULE_ARG
+wifi_hal_cflags += -DWIFI_DRIVER_MODULE_ARG=\"$(WIFI_DRIVER_MODULE_ARG)\"
+endif
+ifdef WIFI_DRIVER_MODULE_NAME
+wifi_hal_cflags += -DWIFI_DRIVER_MODULE_NAME=\"$(WIFI_DRIVER_MODULE_NAME)\"
+endif
+ifdef WIFI_DRIVER_FW_PATH_STA
+wifi_hal_cflags += -DWIFI_DRIVER_FW_PATH_STA=\"$(WIFI_DRIVER_FW_PATH_STA)\"
+endif
+ifdef WIFI_DRIVER_FW_PATH_AP
+wifi_hal_cflags += -DWIFI_DRIVER_FW_PATH_AP=\"$(WIFI_DRIVER_FW_PATH_AP)\"
+endif
+ifdef WIFI_DRIVER_FW_PATH_P2P
+wifi_hal_cflags += -DWIFI_DRIVER_FW_PATH_P2P=\"$(WIFI_DRIVER_FW_PATH_P2P)\"
+endif
+ifdef WIFI_DRIVER_FW_PATH_PARAM
+wifi_hal_cflags += -DWIFI_DRIVER_FW_PATH_PARAM=\"$(WIFI_DRIVER_FW_PATH_PARAM)\"
+endif
+
+ifdef WIFI_DRIVER_STATE_CTRL_PARAM
+wifi_hal_cflags += -DWIFI_DRIVER_STATE_CTRL_PARAM=\"$(WIFI_DRIVER_STATE_CTRL_PARAM)\"
+endif
+ifdef WIFI_DRIVER_STATE_ON
+wifi_hal_cflags += -DWIFI_DRIVER_STATE_ON=\"$(WIFI_DRIVER_STATE_ON)\"
+endif
+ifdef WIFI_DRIVER_STATE_OFF
+wifi_hal_cflags += -DWIFI_DRIVER_STATE_OFF=\"$(WIFI_DRIVER_STATE_OFF)\"
+endif
+
+# Common code shared between the HALs.
+# ============================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := libwifi-hal-common
+LOCAL_CFLAGS := $(wifi_hal_cflags)
+LOCAL_SRC_FILES := wifi_hal_common.cpp
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
+include $(BUILD_STATIC_LIBRARY)
+
+# A fallback "vendor" HAL library.
+# Don't link this, link libwifi-hal.
+# ============================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := libwifi-hal-fallback
+LOCAL_CFLAGS := $(wifi_hal_cflags)
+LOCAL_SRC_FILES := wifi_hal_fallback.cpp
+LOCAL_C_INCLUDES := $(call include-path-for, libhardware_legacy)
+include $(BUILD_STATIC_LIBRARY)
+
+# Pick a vendor provided HAL implementation library.
+# ============================================================
+LIB_WIFI_HAL := libwifi-hal-fallback
+ifeq ($(BOARD_WLAN_DEVICE), bcmdhd)
+  LIB_WIFI_HAL := libwifi-hal-bcm
+else ifeq ($(BOARD_WLAN_DEVICE), qcwcn)
+  LIB_WIFI_HAL := libwifi-hal-qcom
+else ifeq ($(BOARD_WLAN_DEVICE), mrvl)
+  # this is commented because none of the nexus devices
+  # that sport Marvell's wifi have support for HAL
+  # LIB_WIFI_HAL := libwifi-hal-mrvl
+else ifeq ($(BOARD_WLAN_DEVICE), MediaTek)
+  # support MTK WIFI HAL
+  LIB_WIFI_HAL := libwifi-hal-mt66xx
+endif
+
+# The WiFi HAL that you should be linking.
+# ============================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := libwifi-hal
+LOCAL_CFLAGS := $(wifi_hal_cflags)
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
+LOCAL_EXPORT_C_INCLUDE_DIRS := \
+    $(LOCAL_PATH)/include \
+    $(call include-path-for, libhardware_legacy)
+LOCAL_SHARED_LIBRARIES := \
+    libcutils \
+    liblog \
+    libnl \
+    libutils
+LOCAL_SRC_FILES := \
+    driver_tool.cpp
+LOCAL_WHOLE_STATIC_LIBRARIES := $(LIB_WIFI_HAL) libwifi-hal-common
+include $(BUILD_SHARED_LIBRARY)
+
+# Test utilities (e.g. mock classes) for libwifi-hal
+# ============================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := libwifi-hal-test
+LOCAL_CFLAGS := $(wifi_hal_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)
+
+endif
diff --git a/libwifi_hal/driver_tool.cpp b/libwifi_hal/driver_tool.cpp
new file mode 100644
index 0000000..688017b
--- /dev/null
+++ b/libwifi_hal/driver_tool.cpp
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+
+#include "wifi_hal/driver_tool.h"
+
+#include "hardware_legacy/wifi.h"
+
+namespace android {
+namespace wifi_hal {
+
+const int DriverTool::kFirmwareModeSta = WIFI_GET_FW_PATH_STA;
+const int DriverTool::kFirmwareModeAp = WIFI_GET_FW_PATH_AP;
+const int DriverTool::kFirmwareModeP2p = WIFI_GET_FW_PATH_P2P;
+
+bool DriverTool::LoadDriver() {
+  return ::wifi_load_driver() == 0;
+}
+
+bool DriverTool::UnloadDriver() {
+  return ::wifi_unload_driver() == 0;
+}
+
+bool DriverTool::IsDriverLoaded() {
+  return ::wifi_unload_driver() != 0;
+}
+
+bool DriverTool::ChangeFirmwareMode(int mode) {
+  const char* fwpath = wifi_get_fw_path(mode);
+  if (!fwpath) {
+    return true;  // HAL doesn't think we need to load firmware for this mode.
+  }
+  if (wifi_change_fw_path(fwpath) != 0) {
+    // Not all devices actually require firmware reloads, but
+    // failure to change the firmware path when it is defined is an error.
+    return false;
+  }
+  return true;
+}
+
+}  // namespace wifi_hal
+}  // namespace android
diff --git a/libwifi_hal/include/hardware_legacy/wifi.h b/libwifi_hal/include/hardware_legacy/wifi.h
new file mode 100644
index 0000000..defff0a
--- /dev/null
+++ b/libwifi_hal/include/hardware_legacy/wifi.h
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+
+#ifndef HARDWARE_LEGACY_WIFI_H
+#define HARDWARE_LEGACY_WIFI_H
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+/**
+ * Load the Wi-Fi driver.
+ *
+ * @return 0 on success, < 0 on failure.
+ */
+int wifi_load_driver();
+
+/**
+ * Unload the Wi-Fi driver.
+ *
+ * @return 0 on success, < 0 on failure.
+ */
+int wifi_unload_driver();
+
+/**
+ * Check if the Wi-Fi driver is loaded.
+ * Check if the Wi-Fi driver is loaded.
+
+ * @return 0 on success, < 0 on failure.
+ */
+int is_wifi_driver_loaded();
+
+/**
+ * Return the path to requested firmware
+ */
+#define WIFI_GET_FW_PATH_STA  0
+#define WIFI_GET_FW_PATH_AP 1
+#define WIFI_GET_FW_PATH_P2P  2
+const char *wifi_get_fw_path(int fw_type);
+
+/**
+ * Change the path to firmware for the wlan driver
+ */
+int wifi_change_fw_path(const char *fwpath);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif  /* HARDWARE_LEGACY_WIFI_H */
diff --git a/libwifi_hal/include/wifi_hal/driver_tool.h b/libwifi_hal/include/wifi_hal/driver_tool.h
new file mode 100644
index 0000000..f1a43cc
--- /dev/null
+++ b/libwifi_hal/include/wifi_hal/driver_tool.h
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_WIFI_SYSTEM_DRIVER_TOOL_H
+#define ANDROID_WIFI_SYSTEM_DRIVER_TOOL_H
+
+namespace android {
+namespace wifi_hal {
+
+// Utilities for interacting with the driver.
+class DriverTool {
+ public:
+  static const int kFirmwareModeSta;
+  static const int kFirmwareModeAp;
+  static const int kFirmwareModeP2p;
+
+  DriverTool() = default;
+  virtual ~DriverTool() = default;
+
+  // These methods allow manipulation of the WiFi driver.
+  // They all return true on success, and false otherwise.
+  virtual bool LoadDriver();
+  virtual bool UnloadDriver();
+  virtual bool IsDriverLoaded();
+
+  // Change the firmware mode.
+  // |mode| is one of the kFirmwareMode* constants defined above.
+  // Returns true on success, and false otherwise.
+  virtual bool ChangeFirmwareMode(int mode);
+
+};  // class DriverTool
+
+}  // namespace wifi_hal
+}  // namespace android
+
+#endif  // ANDROID_WIFI_SYSTEM_DRIVER_TOOL_H
+
diff --git a/libwifi_hal/testlib/include/wifi_hal_test/mock_driver_tool.h b/libwifi_hal/testlib/include/wifi_hal_test/mock_driver_tool.h
new file mode 100644
index 0000000..fef0dab
--- /dev/null
+++ b/libwifi_hal/testlib/include/wifi_hal_test/mock_driver_tool.h
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_WIFI_HAL_MOCK_DRIVER_TOOL_H
+#define ANDROID_WIFI_HAL_MOCK_DRIVER_TOOL_H
+
+#include <wifi_hal/driver_tool.h>
+
+namespace android {
+namespace wifi_hal {
+
+class MockDriverTool : public DriverTool {
+ public:
+  ~MockDriverTool() override = default;
+  MOCK_METHOD0(LoadDriver, bool());
+  MOCK_METHOD0(UnloadDriver, bool());
+  MOCK_METHOD0(IsDriverLoaded, bool());
+  MOCK_METHOD1(ChangeFirmwareMode, bool(int mode));
+
+};  // class MockDriverTool
+
+}  // namespace wifi_hal
+}  // namespace android
+
+#endif  // ANDROID_WIFI_HAL_MOCK_DRIVER_TOOL_H
+
diff --git a/libwifi_hal/wifi_hal_common.cpp b/libwifi_hal/wifi_hal_common.cpp
new file mode 100644
index 0000000..54c38e1
--- /dev/null
+++ b/libwifi_hal/wifi_hal_common.cpp
@@ -0,0 +1,240 @@
+/*
+ * Copyright 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.
+ */
+
+#include "hardware_legacy/wifi.h"
+
+#define LOG_TAG "WifiHalCommon"
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <cutils/log.h>
+#include <cutils/misc.h>
+#include <cutils/properties.h>
+
+extern int init_module(void *, unsigned long, const char *);
+extern int delete_module(const char *, unsigned int);
+
+#ifndef WIFI_DRIVER_FW_PATH_STA
+#define WIFI_DRIVER_FW_PATH_STA		NULL
+#endif
+#ifndef WIFI_DRIVER_FW_PATH_AP
+#define WIFI_DRIVER_FW_PATH_AP		NULL
+#endif
+#ifndef WIFI_DRIVER_FW_PATH_P2P
+#define WIFI_DRIVER_FW_PATH_P2P		NULL
+#endif
+#ifndef WIFI_DRIVER_FW_PATH_PARAM
+#define WIFI_DRIVER_FW_PATH_PARAM	"/sys/module/wlan/parameters/fwpath"
+#endif
+
+#ifndef WIFI_DRIVER_MODULE_ARG
+#define WIFI_DRIVER_MODULE_ARG          ""
+#endif
+
+static const char DRIVER_PROP_NAME[]    = "wlan.driver.status";
+#ifdef WIFI_DRIVER_MODULE_PATH
+static const char DRIVER_MODULE_NAME[]  = WIFI_DRIVER_MODULE_NAME;
+static const char DRIVER_MODULE_TAG[]   = WIFI_DRIVER_MODULE_NAME " ";
+static const char DRIVER_MODULE_PATH[]  = WIFI_DRIVER_MODULE_PATH;
+static const char DRIVER_MODULE_ARG[]   = WIFI_DRIVER_MODULE_ARG;
+static const char MODULE_FILE[]         = "/proc/modules";
+#endif
+
+static int insmod(const char *filename, const char *args)
+{
+    void *module;
+    unsigned int size;
+    int ret;
+
+    module = load_file(filename, &size);
+    if (!module)
+        return -1;
+
+    ret = init_module(module, size, args);
+
+    free(module);
+
+    return ret;
+}
+
+static int rmmod(const char *modname)
+{
+    int ret = -1;
+    int maxtry = 10;
+
+    while (maxtry-- > 0) {
+        ret = delete_module(modname, O_NONBLOCK | O_EXCL);
+        if (ret < 0 && errno == EAGAIN)
+            usleep(500000);
+        else
+            break;
+    }
+
+    if (ret != 0)
+        ALOGD("Unable to unload driver module \"%s\": %s\n",
+             modname, strerror(errno));
+    return ret;
+}
+
+#ifdef WIFI_DRIVER_STATE_CTRL_PARAM
+int wifi_change_driver_state(const char *state)
+{
+    int len;
+    int fd;
+    int ret = 0;
+
+    if (!state)
+        return -1;
+    fd = TEMP_FAILURE_RETRY(open(WIFI_DRIVER_STATE_CTRL_PARAM, O_WRONLY));
+    if (fd < 0) {
+        ALOGE("Failed to open driver state control param (%s)", strerror(errno));
+        return -1;
+    }
+    len = strlen(state) + 1;
+    if (TEMP_FAILURE_RETRY(write(fd, state, len)) != len) {
+        ALOGE("Failed to write driver state control param (%s)", strerror(errno));
+        ret = -1;
+    }
+    close(fd);
+    return ret;
+}
+#endif
+
+int is_wifi_driver_loaded() {
+    char driver_status[PROPERTY_VALUE_MAX];
+#ifdef WIFI_DRIVER_MODULE_PATH
+    FILE *proc;
+    char line[sizeof(DRIVER_MODULE_TAG)+10];
+#endif
+
+    if (!property_get(DRIVER_PROP_NAME, driver_status, NULL)
+            || strcmp(driver_status, "ok") != 0) {
+        return 0;  /* driver not loaded */
+    }
+#ifdef WIFI_DRIVER_MODULE_PATH
+    /*
+     * If the property says the driver is loaded, check to
+     * make sure that the property setting isn't just left
+     * over from a previous manual shutdown or a runtime
+     * crash.
+     */
+    if ((proc = fopen(MODULE_FILE, "r")) == NULL) {
+        ALOGW("Could not open %s: %s", MODULE_FILE, strerror(errno));
+        property_set(DRIVER_PROP_NAME, "unloaded");
+        return 0;
+    }
+    while ((fgets(line, sizeof(line), proc)) != NULL) {
+        if (strncmp(line, DRIVER_MODULE_TAG, strlen(DRIVER_MODULE_TAG)) == 0) {
+            fclose(proc);
+            return 1;
+        }
+    }
+    fclose(proc);
+    property_set(DRIVER_PROP_NAME, "unloaded");
+    return 0;
+#else
+    return 1;
+#endif
+}
+
+int wifi_load_driver()
+{
+#ifdef WIFI_DRIVER_MODULE_PATH
+    if (is_wifi_driver_loaded()) {
+        return 0;
+    }
+
+    if (insmod(DRIVER_MODULE_PATH, DRIVER_MODULE_ARG) < 0)
+        return -1;
+
+#elif defined WIFI_DRIVER_STATE_CTRL_PARAM
+    if (is_wifi_driver_loaded()) {
+        return 0;
+    }
+
+    if (wifi_change_driver_state(WIFI_DRIVER_STATE_ON) < 0)
+        return -1;
+#endif
+    property_set(DRIVER_PROP_NAME, "ok");
+    return 0;
+}
+
+int wifi_unload_driver()
+{
+    usleep(200000); /* allow to finish interface down */
+#ifdef WIFI_DRIVER_MODULE_PATH
+    if (rmmod(DRIVER_MODULE_NAME) == 0) {
+        int count = 20; /* wait at most 10 seconds for completion */
+        while (count-- > 0) {
+            if (!is_wifi_driver_loaded())
+                break;
+            usleep(500000);
+        }
+        usleep(500000); /* allow card removal */
+        if (count) {
+            return 0;
+        }
+        return -1;
+    } else
+        return -1;
+#else
+#ifdef WIFI_DRIVER_STATE_CTRL_PARAM
+    if (is_wifi_driver_loaded()) {
+        if (wifi_change_driver_state(WIFI_DRIVER_STATE_OFF) < 0)
+            return -1;
+    }
+#endif
+    property_set(DRIVER_PROP_NAME, "unloaded");
+    return 0;
+#endif
+}
+
+const char *wifi_get_fw_path(int fw_type)
+{
+    switch (fw_type) {
+    case WIFI_GET_FW_PATH_STA:
+        return WIFI_DRIVER_FW_PATH_STA;
+    case WIFI_GET_FW_PATH_AP:
+        return WIFI_DRIVER_FW_PATH_AP;
+    case WIFI_GET_FW_PATH_P2P:
+        return WIFI_DRIVER_FW_PATH_P2P;
+    }
+    return NULL;
+}
+
+int wifi_change_fw_path(const char *fwpath)
+{
+    int len;
+    int fd;
+    int ret = 0;
+
+    if (!fwpath)
+        return ret;
+    fd = TEMP_FAILURE_RETRY(open(WIFI_DRIVER_FW_PATH_PARAM, O_WRONLY));
+    if (fd < 0) {
+        ALOGE("Failed to open wlan fw path param (%s)", strerror(errno));
+        return -1;
+    }
+    len = strlen(fwpath) + 1;
+    if (TEMP_FAILURE_RETRY(write(fd, fwpath, len)) != len) {
+        ALOGE("Failed to write wlan fw path param (%s)", strerror(errno));
+        ret = -1;
+    }
+    close(fd);
+    return ret;
+}
diff --git a/libwifi_hal/wifi_hal_fallback.cpp b/libwifi_hal/wifi_hal_fallback.cpp
new file mode 100644
index 0000000..64259a1
--- /dev/null
+++ b/libwifi_hal/wifi_hal_fallback.cpp
@@ -0,0 +1,21 @@
+/*
+ * Copyright 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.
+ */
+
+#include "hardware_legacy/wifi_hal.h"
+
+wifi_error init_wifi_vendor_hal_func_table(wifi_hal_fn *fn) {
+    return WIFI_ERROR_NOT_SUPPORTED;
+}
diff --git a/libwifi_system/Android.mk b/libwifi_system/Android.mk
new file mode 100644
index 0000000..61d340a
--- /dev/null
+++ b/libwifi_system/Android.mk
@@ -0,0 +1,91 @@
+# 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)
+
+ifneq ($(TARGET_BUILD_PDK), true)
+
+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 \
+    liblog \
+    libnetutils \
+    libnl \
+    libwifi-hal
+
+# Tolerate certain emulators which apparently don't have supplicant installed.
+ifdef WPA_SUPPLICANT_VERSION
+LOCAL_CFLAGS += -DLIBWPA_CLIENT_EXISTS
+LOCAL_SHARED_LIBRARIES += libwpa_client
+endif
+
+LOCAL_SRC_FILES := \
+    hostapd_manager.cpp \
+    interface_tool.cpp \
+    hal_tool.cpp \
+    wifi.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)
+
+endif
diff --git a/libwifi_system/hal_tool.cpp b/libwifi_system/hal_tool.cpp
new file mode 100644
index 0000000..4c7401d
--- /dev/null
+++ b/libwifi_system/hal_tool.cpp
@@ -0,0 +1,585 @@
+/*
+ * 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.
+ */
+
+#include "wifi_system/hal_tool.h"
+
+#include <log/log.h>
+
+namespace android {
+namespace wifi_system {
+namespace {
+
+wifi_error wifi_initialize_stub(wifi_handle* handle) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+void wifi_cleanup_stub(wifi_handle handle, wifi_cleaned_up_handler handler) {}
+
+void wifi_event_loop_stub(wifi_handle handle) {}
+
+void wifi_get_error_info_stub(wifi_error err, const char** msg) { *msg = NULL; }
+
+wifi_error wifi_get_supported_feature_set_stub(wifi_interface_handle handle,
+                                               feature_set* set) {
+  return WIFI_ERROR_UNINITIALIZED;
+}
+
+wifi_error wifi_get_concurrency_matrix_stub(wifi_interface_handle handle,
+                                            int max_size, feature_set* matrix,
+                                            int* size) {
+  return WIFI_ERROR_UNINITIALIZED;
+}
+
+wifi_error wifi_set_scanning_mac_oui_stub(wifi_interface_handle handle,
+                                          unsigned char* oui_data) {
+  return WIFI_ERROR_UNINITIALIZED;
+}
+
+/* List of all supported channels, including 5GHz channels */
+wifi_error wifi_get_supported_channels_stub(wifi_handle handle, int* size,
+                                            wifi_channel* list) {
+  return WIFI_ERROR_UNINITIALIZED;
+}
+
+/* Enhanced power reporting */
+wifi_error wifi_is_epr_supported_stub(wifi_handle handle) {
+  return WIFI_ERROR_UNINITIALIZED;
+}
+
+/* multiple interface support */
+wifi_error wifi_get_ifaces_stub(wifi_handle handle, int* num_ifaces,
+                                wifi_interface_handle** ifaces) {
+  return WIFI_ERROR_UNINITIALIZED;
+}
+
+wifi_error wifi_get_iface_name_stub(wifi_interface_handle iface, char* name,
+                                    size_t size) {
+  return WIFI_ERROR_UNINITIALIZED;
+}
+
+wifi_error wifi_set_iface_event_handler_stub(wifi_request_id id,
+                                             wifi_interface_handle iface,
+                                             wifi_event_handler eh) {
+  return WIFI_ERROR_UNINITIALIZED;
+}
+
+wifi_error wifi_reset_iface_event_handler_stub(wifi_request_id id,
+                                               wifi_interface_handle iface) {
+  return WIFI_ERROR_UNINITIALIZED;
+}
+
+wifi_error wifi_start_gscan_stub(wifi_request_id id,
+                                 wifi_interface_handle iface,
+                                 wifi_scan_cmd_params params,
+                                 wifi_scan_result_handler handler) {
+  return WIFI_ERROR_UNINITIALIZED;
+}
+
+wifi_error wifi_stop_gscan_stub(wifi_request_id id,
+                                wifi_interface_handle iface) {
+  return WIFI_ERROR_UNINITIALIZED;
+}
+
+wifi_error wifi_get_cached_gscan_results_stub(wifi_interface_handle iface,
+                                              byte flush, int max,
+                                              wifi_cached_scan_results* results,
+                                              int* num) {
+  return WIFI_ERROR_UNINITIALIZED;
+}
+
+wifi_error wifi_set_bssid_hotlist_stub(wifi_request_id id,
+                                       wifi_interface_handle iface,
+                                       wifi_bssid_hotlist_params params,
+                                       wifi_hotlist_ap_found_handler handler) {
+  return WIFI_ERROR_UNINITIALIZED;
+}
+
+wifi_error wifi_reset_bssid_hotlist_stub(wifi_request_id id,
+                                         wifi_interface_handle iface) {
+  return WIFI_ERROR_UNINITIALIZED;
+}
+
+wifi_error wifi_set_significant_change_handler_stub(
+    wifi_request_id id, wifi_interface_handle iface,
+    wifi_significant_change_params params,
+    wifi_significant_change_handler handler) {
+  return WIFI_ERROR_UNINITIALIZED;
+}
+
+wifi_error wifi_reset_significant_change_handler_stub(
+    wifi_request_id id, wifi_interface_handle iface) {
+  return WIFI_ERROR_UNINITIALIZED;
+}
+
+wifi_error wifi_get_gscan_capabilities_stub(
+    wifi_interface_handle handle, wifi_gscan_capabilities* capabilities) {
+  return WIFI_ERROR_UNINITIALIZED;
+}
+
+wifi_error wifi_set_link_stats_stub(wifi_interface_handle iface,
+                                    wifi_link_layer_params params) {
+  return WIFI_ERROR_UNINITIALIZED;
+}
+
+wifi_error wifi_get_link_stats_stub(wifi_request_id id,
+                                    wifi_interface_handle iface,
+                                    wifi_stats_result_handler handler) {
+  return WIFI_ERROR_UNINITIALIZED;
+}
+
+wifi_error wifi_clear_link_stats_stub(wifi_interface_handle iface,
+                                      u32 stats_clear_req_mask,
+                                      u32* stats_clear_rsp_mask, u8 stop_req,
+                                      u8* stop_rsp) {
+  return WIFI_ERROR_UNINITIALIZED;
+}
+
+wifi_error wifi_get_valid_channels_stub(wifi_interface_handle handle, int band,
+                                        int max_channels,
+                                        wifi_channel* channels,
+                                        int* num_channels) {
+  return WIFI_ERROR_UNINITIALIZED;
+}
+
+/* API to request RTT measurement */
+wifi_error wifi_rtt_range_request_stub(wifi_request_id id,
+                                       wifi_interface_handle iface,
+                                       unsigned num_rtt_config,
+                                       wifi_rtt_config rtt_config[],
+                                       wifi_rtt_event_handler handler) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+/* API to cancel RTT measurements */
+wifi_error wifi_rtt_range_cancel_stub(wifi_request_id id,
+                                      wifi_interface_handle iface,
+                                      unsigned num_devices, mac_addr addr[]) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+/* API to get RTT capability */
+wifi_error wifi_get_rtt_capabilities_stub(wifi_interface_handle iface,
+                                          wifi_rtt_capabilities* capabilities) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+/* API to enable RTT responder role */
+wifi_error wifi_enable_responder_stub(wifi_request_id id,
+                                      wifi_interface_handle iface,
+                                      wifi_channel_info channel_hint,
+                                      unsigned max_duration_seconds,
+                                      wifi_channel_info* channel_used) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+/* API to disable RTT responder role */
+wifi_error wifi_disable_responder_stub(wifi_request_id id,
+                                       wifi_interface_handle iface) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+/* API to get available channel for RTT responder role */
+wifi_error wifi_rtt_get_available_channel_stub(wifi_interface_handle iface,
+                                               wifi_channel_info* channel) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_set_nodfs_flag_stub(wifi_interface_handle iface, u32 nodfs) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_start_logging_stub(wifi_interface_handle iface,
+                                   u32 verbose_level, u32 flags,
+                                   u32 max_interval_sec, u32 min_data_size,
+                                   char* buffer_name) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_set_epno_list_stub(int id, wifi_interface_info* iface,
+                                   const wifi_epno_params* params,
+                                   wifi_epno_handler handler) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_reset_epno_list_stub(int id, wifi_interface_info* iface) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_set_country_code_stub(wifi_interface_handle iface,
+                                      const char* code) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_get_firmware_memory_dump_stub(
+    wifi_interface_handle iface, wifi_firmware_memory_dump_handler handler) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_set_log_handler_stub(wifi_request_id id,
+                                     wifi_interface_handle iface,
+                                     wifi_ring_buffer_data_handler handler) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_reset_log_handler_stub(wifi_request_id id,
+                                       wifi_interface_handle iface) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_set_alert_handler_stub(wifi_request_id id,
+                                       wifi_interface_handle iface,
+                                       wifi_alert_handler handler) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_reset_alert_handler_stub(wifi_request_id id,
+                                         wifi_interface_handle iface) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_get_firmware_version_stub(wifi_interface_handle iface,
+                                          char* buffer, int buffer_size) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_get_ring_buffers_status_stub(wifi_interface_handle iface,
+                                             u32* num_rings,
+                                             wifi_ring_buffer_status* status) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_get_logger_supported_feature_set_stub(
+    wifi_interface_handle iface, unsigned int* support) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_get_ring_data_stub(wifi_interface_handle iface,
+                                   char* ring_name) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_get_driver_version_stub(wifi_interface_handle iface,
+                                        char* buffer, int buffer_size) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_enable_tdls_stub(wifi_interface_handle iface, mac_addr addr,
+                                 wifi_tdls_params* params,
+                                 wifi_tdls_handler handler) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_disable_tdls_stub(wifi_interface_handle iface, mac_addr addr) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_get_tdls_status_stub(wifi_interface_handle iface, mac_addr addr,
+                                     wifi_tdls_status* status) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_get_tdls_capabilities_stub(
+    wifi_interface_handle iface, wifi_tdls_capabilities* capabilities) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_set_bssid_blacklist_stub(wifi_request_id id,
+                                         wifi_interface_handle iface,
+                                         wifi_bssid_params params) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_start_sending_offloaded_packet_stub(
+    wifi_request_id id, wifi_interface_handle iface, u8* ip_packet,
+    u16 ip_packet_len, u8* src_mac_addr, u8* dst_mac_addr, u32 period_msec) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_stop_sending_offloaded_packet_stub(
+    wifi_request_id id, wifi_interface_handle iface) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_get_wake_reason_stats_stub(
+    wifi_interface_handle iface,
+    WLAN_DRIVER_WAKE_REASON_CNT* wifi_wake_reason_cnt) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_configure_nd_offload_stub(wifi_interface_handle iface,
+                                          u8 enable) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_get_driver_memory_dump_stub(
+    wifi_interface_handle iface, wifi_driver_memory_dump_callbacks callbacks) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_start_pkt_fate_monitoring_stub(wifi_interface_handle iface) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_get_tx_pkt_fates_stub(wifi_interface_handle handle,
+                                      wifi_tx_report* tx_report_bufs,
+                                      size_t n_requested_fates,
+                                      size_t* n_provided_fates) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_get_rx_pkt_fates_stub(wifi_interface_handle handle,
+                                      wifi_rx_report* rx_report_bufs,
+                                      size_t n_requested_fates,
+                                      size_t* n_provided_fates) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+wifi_error wifi_nan_enable_request_stub(transaction_id id,
+                                        wifi_interface_handle iface,
+                                        NanEnableRequest* msg) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_nan_disable_request_stub(transaction_id id,
+                                         wifi_interface_handle iface) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_nan_publish_request_stub(transaction_id id,
+                                         wifi_interface_handle iface,
+                                         NanPublishRequest* msg) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_nan_publish_cancel_request_stub(transaction_id id,
+                                                wifi_interface_handle iface,
+                                                NanPublishCancelRequest* msg) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_nan_subscribe_request_stub(transaction_id id,
+                                           wifi_interface_handle iface,
+                                           NanSubscribeRequest* msg) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_nan_subscribe_cancel_request_stub(
+    transaction_id id, wifi_interface_handle iface,
+    NanSubscribeCancelRequest* msg) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_nan_transmit_followup_request_stub(
+    transaction_id id, wifi_interface_handle iface,
+    NanTransmitFollowupRequest* msg) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_nan_stats_request_stub(transaction_id id,
+                                       wifi_interface_handle iface,
+                                       NanStatsRequest* msg) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_nan_config_request_stub(transaction_id id,
+                                        wifi_interface_handle iface,
+                                        NanConfigRequest* msg) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_nan_tca_request_stub(transaction_id id,
+                                     wifi_interface_handle iface,
+                                     NanTCARequest* msg) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_nan_beacon_sdf_payload_request_stub(
+    transaction_id id, wifi_interface_handle iface,
+    NanBeaconSdfPayloadRequest* msg) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_nan_register_handler_stub(wifi_interface_handle iface,
+                                          NanCallbackHandler handlers) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_nan_get_version_stub(wifi_handle handle, NanVersion* version) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_nan_get_capabilities_stub(transaction_id id,
+                                          wifi_interface_handle iface) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_nan_data_interface_create_stub(transaction_id id,
+                                               wifi_interface_handle iface,
+                                               char* iface_name) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_nan_data_interface_delete_stub(transaction_id id,
+                                               wifi_interface_handle iface,
+                                               char* iface_name) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_nan_data_request_initiator_stub(
+    transaction_id id, wifi_interface_handle iface,
+    NanDataPathInitiatorRequest* msg) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_nan_data_indication_response_stub(
+    transaction_id id, wifi_interface_handle iface,
+    NanDataPathIndicationResponse* msg) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_nan_data_end_stub(transaction_id id,
+                                  wifi_interface_handle iface,
+                                  NanDataPathEndRequest* msg) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_get_packet_filter_capabilities_stub(
+    wifi_interface_handle handle, u32* version, u32* max_len) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+wifi_error wifi_set_packet_filter_stub(wifi_interface_handle handle,
+                                       const u8* program, u32 len) {
+  return WIFI_ERROR_NOT_SUPPORTED;
+}
+
+bool init_wifi_stub_hal_func_table(wifi_hal_fn* hal_fn) {
+  if (hal_fn == NULL) {
+    return false;
+  }
+  hal_fn->wifi_initialize = wifi_initialize_stub;
+  hal_fn->wifi_cleanup = wifi_cleanup_stub;
+  hal_fn->wifi_event_loop = wifi_event_loop_stub;
+  hal_fn->wifi_get_error_info = wifi_get_error_info_stub;
+  hal_fn->wifi_get_supported_feature_set = wifi_get_supported_feature_set_stub;
+  hal_fn->wifi_get_concurrency_matrix = wifi_get_concurrency_matrix_stub;
+  hal_fn->wifi_set_scanning_mac_oui = wifi_set_scanning_mac_oui_stub;
+  hal_fn->wifi_get_supported_channels = wifi_get_supported_channels_stub;
+  hal_fn->wifi_is_epr_supported = wifi_is_epr_supported_stub;
+  hal_fn->wifi_get_ifaces = wifi_get_ifaces_stub;
+  hal_fn->wifi_get_iface_name = wifi_get_iface_name_stub;
+  hal_fn->wifi_reset_iface_event_handler = wifi_reset_iface_event_handler_stub;
+  hal_fn->wifi_start_gscan = wifi_start_gscan_stub;
+  hal_fn->wifi_stop_gscan = wifi_stop_gscan_stub;
+  hal_fn->wifi_get_cached_gscan_results = wifi_get_cached_gscan_results_stub;
+  hal_fn->wifi_set_bssid_hotlist = wifi_set_bssid_hotlist_stub;
+  hal_fn->wifi_reset_bssid_hotlist = wifi_reset_bssid_hotlist_stub;
+  hal_fn->wifi_set_significant_change_handler =
+      wifi_set_significant_change_handler_stub;
+  hal_fn->wifi_reset_significant_change_handler =
+      wifi_reset_significant_change_handler_stub;
+  hal_fn->wifi_get_gscan_capabilities = wifi_get_gscan_capabilities_stub;
+  hal_fn->wifi_set_link_stats = wifi_set_link_stats_stub;
+  hal_fn->wifi_get_link_stats = wifi_get_link_stats_stub;
+  hal_fn->wifi_clear_link_stats = wifi_clear_link_stats_stub;
+  hal_fn->wifi_get_valid_channels = wifi_get_valid_channels_stub;
+  hal_fn->wifi_rtt_range_request = wifi_rtt_range_request_stub;
+  hal_fn->wifi_rtt_range_cancel = wifi_rtt_range_cancel_stub;
+  hal_fn->wifi_get_rtt_capabilities = wifi_get_rtt_capabilities_stub;
+  hal_fn->wifi_start_logging = wifi_start_logging_stub;
+  hal_fn->wifi_set_epno_list = wifi_set_epno_list_stub;
+  hal_fn->wifi_set_country_code = wifi_set_country_code_stub;
+  hal_fn->wifi_enable_tdls = wifi_enable_tdls_stub;
+  hal_fn->wifi_disable_tdls = wifi_disable_tdls_stub;
+  hal_fn->wifi_get_tdls_status = wifi_get_tdls_status_stub;
+  hal_fn->wifi_get_tdls_capabilities = wifi_get_tdls_capabilities_stub;
+  hal_fn->wifi_set_nodfs_flag = wifi_set_nodfs_flag_stub;
+  hal_fn->wifi_get_firmware_memory_dump = wifi_get_firmware_memory_dump_stub;
+  hal_fn->wifi_set_log_handler = wifi_set_log_handler_stub;
+  hal_fn->wifi_reset_log_handler = wifi_reset_log_handler_stub;
+  hal_fn->wifi_set_alert_handler = wifi_set_alert_handler_stub;
+  hal_fn->wifi_reset_alert_handler = wifi_reset_alert_handler_stub;
+  hal_fn->wifi_get_firmware_version = wifi_get_firmware_version_stub;
+  hal_fn->wifi_get_ring_buffers_status = wifi_get_ring_buffers_status_stub;
+  hal_fn->wifi_get_logger_supported_feature_set =
+      wifi_get_logger_supported_feature_set_stub;
+  hal_fn->wifi_get_ring_data = wifi_get_ring_data_stub;
+  hal_fn->wifi_get_driver_version = wifi_get_driver_version_stub;
+  hal_fn->wifi_set_bssid_blacklist = wifi_set_bssid_blacklist_stub;
+  hal_fn->wifi_start_sending_offloaded_packet =
+      wifi_start_sending_offloaded_packet_stub;
+  hal_fn->wifi_stop_sending_offloaded_packet =
+      wifi_stop_sending_offloaded_packet_stub;
+  hal_fn->wifi_get_wake_reason_stats = wifi_get_wake_reason_stats_stub;
+  hal_fn->wifi_configure_nd_offload = wifi_configure_nd_offload_stub;
+  hal_fn->wifi_get_driver_memory_dump = wifi_get_driver_memory_dump_stub;
+  hal_fn->wifi_start_pkt_fate_monitoring = wifi_start_pkt_fate_monitoring_stub;
+  hal_fn->wifi_get_tx_pkt_fates = wifi_get_tx_pkt_fates_stub;
+  hal_fn->wifi_get_rx_pkt_fates = wifi_get_rx_pkt_fates_stub;
+  hal_fn->wifi_nan_enable_request = wifi_nan_enable_request_stub;
+  hal_fn->wifi_nan_disable_request = wifi_nan_disable_request_stub;
+  hal_fn->wifi_nan_publish_request = wifi_nan_publish_request_stub;
+  hal_fn->wifi_nan_publish_cancel_request =
+      wifi_nan_publish_cancel_request_stub;
+  hal_fn->wifi_nan_subscribe_request = wifi_nan_subscribe_request_stub;
+  hal_fn->wifi_nan_subscribe_cancel_request =
+      wifi_nan_subscribe_cancel_request_stub;
+  hal_fn->wifi_nan_transmit_followup_request =
+      wifi_nan_transmit_followup_request_stub;
+  hal_fn->wifi_nan_stats_request = wifi_nan_stats_request_stub;
+  hal_fn->wifi_nan_config_request = wifi_nan_config_request_stub;
+  hal_fn->wifi_nan_tca_request = wifi_nan_tca_request_stub;
+  hal_fn->wifi_nan_beacon_sdf_payload_request =
+      wifi_nan_beacon_sdf_payload_request_stub;
+  hal_fn->wifi_nan_register_handler = wifi_nan_register_handler_stub;
+  hal_fn->wifi_nan_get_version = wifi_nan_get_version_stub;
+  hal_fn->wifi_nan_get_capabilities = wifi_nan_get_capabilities_stub;
+  hal_fn->wifi_nan_data_interface_create = wifi_nan_data_interface_create_stub;
+  hal_fn->wifi_nan_data_interface_delete = wifi_nan_data_interface_delete_stub;
+  hal_fn->wifi_nan_data_request_initiator =
+      wifi_nan_data_request_initiator_stub;
+  hal_fn->wifi_nan_data_indication_response =
+      wifi_nan_data_indication_response_stub;
+  hal_fn->wifi_nan_data_end = wifi_nan_data_end_stub;
+  hal_fn->wifi_get_packet_filter_capabilities =
+      wifi_get_packet_filter_capabilities_stub;
+  hal_fn->wifi_set_packet_filter = wifi_set_packet_filter_stub;
+
+  return true;
+}
+
+}  // namespace
+
+bool HalTool::InitFunctionTable(wifi_hal_fn* hal_fn) {
+  if (!init_wifi_stub_hal_func_table(hal_fn)) {
+    ALOGE("Can not initialize the basic function pointer table");
+    return false;
+  }
+
+  if (init_wifi_vendor_hal_func_table(hal_fn) != WIFI_SUCCESS) {
+    ALOGE("Can not initialize the vendor function pointer table");
+    return false;
+  }
+
+  return true;
+}
+
+bool HalTool::CanGetValidChannels(wifi_hal_fn* hal_fn) {
+  return hal_fn &&
+         (hal_fn->wifi_get_valid_channels != wifi_get_valid_channels_stub);
+}
+
+}  // namespace wifi_system
+}  // namespace android
diff --git a/libwifi_system/hostapd_manager.cpp b/libwifi_system/hostapd_manager.cpp
new file mode 100644
index 0000000..889fd69
--- /dev/null
+++ b/libwifi_system/hostapd_manager.cpp
@@ -0,0 +1,202 @@
+/*
+ * 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.
+ */
+
+#include "wifi_system/hostapd_manager.h"
+
+#include <iomanip>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <cutils/properties.h>
+#include <openssl/evp.h>
+#include <openssl/sha.h>
+#include <private/android_filesystem_config.h>
+
+#include "wifi_system/wifi.h"
+
+using android::base::StringPrintf;
+using android::base::WriteStringToFile;
+using std::string;
+using std::vector;
+using std::stringstream;
+
+namespace android {
+namespace wifi_system {
+namespace {
+
+const int kDefaultApChannel = 6;
+const char kHostapdServiceName[] = "hostapd";
+const char kHostapdConfigFilePath[] = "/data/misc/wifi/hostapd.conf";
+
+string GeneratePsk(const vector<uint8_t>& ssid,
+                   const vector<uint8_t>& passphrase) {
+  string result;
+  unsigned char psk[SHA256_DIGEST_LENGTH];
+
+  // Use the PKCS#5 PBKDF2 with 4096 iterations
+  if (PKCS5_PBKDF2_HMAC_SHA1(reinterpret_cast<const char*>(passphrase.data()),
+                             passphrase.size(),
+                             ssid.data(), ssid.size(),
+                             4096, sizeof(psk), psk) != 1) {
+    LOG(ERROR) << "Cannot generate PSK using PKCS#5 PBKDF2";
+    return result;
+  }
+
+  stringstream ss;
+  ss << std::hex;
+  ss << std::setfill('0');
+  for (int j = 0; j < SHA256_DIGEST_LENGTH; j++) {
+    ss << std::setw(2) << static_cast<unsigned int>(psk[j]);
+  }
+  result = ss.str();
+
+  return result;
+}
+
+}  // namespace
+
+bool HostapdManager::StartHostapd() {
+  if (hostapd_is_running_) {
+    LOG(ERROR) << "SoftAP is already running";
+    return false;
+  }
+
+  if (ensure_entropy_file_exists() < 0) {
+    LOG(WARNING) << "Wi-Fi entropy file was not created";
+  }
+
+  if (property_set("ctl.start", kHostapdServiceName) != 0) {
+    LOG(ERROR) << "Failed to start SoftAP";
+    return false;
+  }
+
+  LOG(DEBUG) << "SoftAP started successfully";
+  hostapd_is_running_ = true;
+  return true;
+}
+
+bool HostapdManager::IsHostapdRunning() {
+  return hostapd_is_running_;
+}
+
+bool HostapdManager::StopHostapd() {
+  if (!hostapd_is_running_) {
+    LOG(DEBUG) << "SoftAP is not running";
+    return true;  // Not really an error, hostapd is already stopped.
+  }
+
+  LOG(DEBUG) << "Stopping the SoftAP service...";
+
+  if (property_set("ctl.stop", kHostapdServiceName) < 0) {
+    LOG(ERROR) << "Failed to stop hostapd service!";
+    return false;
+  }
+
+  LOG(DEBUG) << "SoftAP stopped successfully";
+  hostapd_is_running_ = false;
+  return true;
+}
+
+bool HostapdManager::WriteHostapdConfig(const string& config) {
+  if (!WriteStringToFile(config, kHostapdConfigFilePath,
+                         S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP,
+                         AID_SYSTEM, AID_WIFI)) {
+    int error = errno;
+    LOG(ERROR) << "Cannot write hostapd config to \""
+               << kHostapdConfigFilePath << "\": " << strerror(error);
+    return false;
+  }
+  return true;
+}
+
+string HostapdManager::CreateHostapdConfig(
+    const string& interface_name,
+    const vector<uint8_t> ssid,
+    bool is_hidden,
+    int channel,
+    EncryptionType encryption_type,
+    const vector<uint8_t> passphrase) {
+  string result;
+
+  if (channel < 0) {
+    channel = kDefaultApChannel;
+  }
+
+  if (ssid.size() > 32) {
+    LOG(ERROR) << "SSIDs must be <= 32 bytes long";
+    return result;
+  }
+
+  stringstream ss;
+  ss << std::hex;
+  ss << std::setfill('0');
+  for (uint8_t b : ssid) {
+    ss << std::setw(2) << static_cast<unsigned int>(b);
+  }
+  const string ssid_as_string  = ss.str();
+
+  string encryption_config;
+  if (encryption_type != EncryptionType::kOpen) {
+    string psk = GeneratePsk(ssid, passphrase);
+    if (psk.empty()) {
+      return result;
+    }
+    if (encryption_type == EncryptionType::kWpa) {
+      encryption_config = StringPrintf("wpa=3\n"
+                                       "wpa_pairwise=TKIP CCMP\n"
+                                       "wpa_psk=%s\n", psk.c_str());
+    } else if (encryption_type == EncryptionType::kWpa2) {
+      encryption_config = StringPrintf("wpa=2\n"
+                                       "rsn_pairwise=CCMP\n"
+                                       "wpa_psk=%s\n", psk.c_str());
+    } else {
+      using encryption_t = std::underlying_type<EncryptionType>::type;
+      LOG(ERROR) << "Unknown encryption type ("
+                 << static_cast<encryption_t>(encryption_type)
+                 << ")";
+      return result;
+    }
+  }
+
+  result = StringPrintf(
+      "interface=%s\n"
+      "driver=nl80211\n"
+      "ctrl_interface=/data/misc/wifi/hostapd\n"
+      // ssid2 signals to hostapd that the value is not a literal value
+      // for use as a SSID.  In this case, we're giving it a hex string
+      // and hostapd needs to expect that.
+      "ssid2=%s\n"
+      "channel=%d\n"
+      "ieee80211n=1\n"
+      "hw_mode=%c\n"
+      "ignore_broadcast_ssid=%d\n"
+      "wowlan_triggers=any\n"
+      "%s",
+      interface_name.c_str(),
+      ssid_as_string.c_str(),
+      channel,
+      (channel <= 14) ? 'g' : 'a',
+      (is_hidden) ? 1 : 0,
+      encryption_config.c_str());
+  return result;
+}
+
+}  // namespace wifi_system
+}  // namespace android
diff --git a/libwifi_system/include/wifi_system/hal_tool.h b/libwifi_system/include/wifi_system/hal_tool.h
new file mode 100644
index 0000000..b25893e
--- /dev/null
+++ b/libwifi_system/include/wifi_system/hal_tool.h
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_WIFI_SYSTEM_HAL_TOOL_H
+#define ANDROID_WIFI_SYSTEM_HAL_TOOL_H
+
+#include <hardware_legacy/wifi_hal.h>
+
+namespace android {
+namespace wifi_system {
+
+// Utilities for interacting with the HAL.
+class HalTool {
+ public:
+  HalTool() = default;
+  virtual ~HalTool() = default;
+
+  virtual bool InitFunctionTable(wifi_hal_fn* hal_fn);
+
+  virtual bool CanGetValidChannels(wifi_hal_fn* hal_fn);
+};  // class HalTool
+
+}  // namespace wifi_system
+}  // namespace android
+
+#endif  // ANDROID_WIFI_SYSTEM_HAL_TOOL_H
diff --git a/libwifi_system/include/wifi_system/hostapd_manager.h b/libwifi_system/include/wifi_system/hostapd_manager.h
new file mode 100644
index 0000000..2bffde4
--- /dev/null
+++ b/libwifi_system/include/wifi_system/hostapd_manager.h
@@ -0,0 +1,81 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_WIFI_SYSTEM_HOSTAPD_MANAGER_H
+#define ANDROID_WIFI_SYSTEM_HOSTAPD_MANAGER_H
+
+#include <string>
+#include <vector>
+
+#include <android-base/macros.h>
+
+namespace android {
+namespace wifi_system {
+
+class HostapdManager {
+ public:
+  enum class EncryptionType {
+    kOpen,
+    kWpa,
+    kWpa2,  // Strongly prefer this if at all possible.
+  };
+
+  HostapdManager() = default;
+  virtual ~HostapdManager() = default;
+
+  // Request that hostapd be started.
+  // Returns true on success.
+  virtual bool StartHostapd();
+
+  // Returns true if hostapd is currently running.
+  virtual bool IsHostapdRunning();
+
+  // Request that a running instance of hostapd be stopped.
+  // Returns true on success.
+  virtual bool StopHostapd();
+
+  // Create a string suitable for writing to the hostapd configuration file.
+  // |interface_name| is a network interface name (e.g. "wlan0").
+  // |ssid| is the SSID used by the configurated network.
+  // |is_hidden| is true iff hostapd should not broadcast the SSID.
+  // |channel| is the WiFi channel (e.g. 6) or <0 for a default value.
+  // |encryption_type| defines the encryption of the configured network.
+  // |passphrase| is ignored for kOpen networks.
+  //
+  // Returns an empty string on failure.
+  virtual std::string CreateHostapdConfig(
+      const std::string& interface_name,
+      const std::vector<uint8_t> ssid,
+      bool is_hidden,
+      int channel,
+      EncryptionType encryption,
+      const std::vector<uint8_t> passphrase);
+
+  // Write out a hostapd configuration file created via CreateHostapdConfig().
+  // Future instances of hostapd will use this new configuration.
+  // Returns true if the configuration file is successfully written.
+  virtual bool WriteHostapdConfig(const std::string& config_file);
+
+ private:
+  bool hostapd_is_running_ = false;
+
+  DISALLOW_COPY_AND_ASSIGN(HostapdManager);
+};  // class HostapdManager
+
+}  // namespace wifi_system
+}  // namespace android
+
+#endif  // ANDROID_WIFI_SYSTEM_HOSTAPD_MANAGER_H
diff --git a/libwifi_system/include/wifi_system/interface_tool.h b/libwifi_system/include/wifi_system/interface_tool.h
new file mode 100644
index 0000000..2e58379
--- /dev/null
+++ b/libwifi_system/include/wifi_system/interface_tool.h
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_WIFI_SYSTEM_INTERFACE_TOOL_H
+#define ANDROID_WIFI_SYSTEM_INTERFACE_TOOL_H
+
+namespace android {
+namespace wifi_system {
+
+class InterfaceTool {
+ public:
+  InterfaceTool() = default;
+  virtual ~InterfaceTool() = default;
+
+  // Set the interface named by |if_name| up or down.
+  // Returns true on success, false otherwise.
+  virtual bool SetUpState(const char* if_name, bool request_up);
+
+  // A helpful alias for SetUpState() that assumes there is a single system
+  // WiFi interface.  Prefer this form if you're hardcoding "wlan0" to help us
+  // identify all the places we are hardcoding the name of the wifi interface.
+  virtual bool SetWifiUpState(bool request_up);
+
+};  // class InterfaceTool
+
+}  // namespace wifi_system
+}  // namespace android
+
+#endif  // ANDROID_WIFI_SYSTEM_INTERFACE_TOOL_H
diff --git a/libwifi_system/include/wifi_system/wifi.h b/libwifi_system/include/wifi_system/wifi.h
new file mode 100644
index 0000000..3e662a1
--- /dev/null
+++ b/libwifi_system/include/wifi_system/wifi.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_WIFI_SYSTEM_WIFI_H
+#define ANDROID_WIFI_SYSTEM_WIFI_H
+
+#include <cstddef>
+
+namespace android {
+namespace wifi_system {
+
+extern const char kWiFiEntropyFile[];
+
+/**
+ * Start supplicant.
+ *
+ * @return 0 on success, < 0 on failure.
+ */
+int wifi_start_supplicant(int p2pSupported);
+
+/**
+ * Stop supplicant.
+ *
+ * @return 0 on success, < 0 on failure.
+ */
+int wifi_stop_supplicant(int p2pSupported);
+
+/**
+ * Open a connection to supplicant
+ *
+ * @return 0 on success, < 0 on failure.
+ */
+int wifi_connect_to_supplicant();
+
+/**
+ * Close connection to supplicant
+ *
+ * @return 0 on success, < 0 on failure.
+ */
+void wifi_close_supplicant_connection();
+
+/**
+ * wifi_wait_for_event() performs a blocking call to
+ * get a Wi-Fi event and returns a string representing
+ * a Wi-Fi event when it occurs.
+ *
+ * @param buf is the buffer that receives the event
+ * @param len is the maximum length of the buffer
+ *
+ * @returns number of bytes in buffer, 0 if no
+ * event (for instance, no connection), and less than 0
+ * if there is an error.
+ */
+int wifi_wait_for_event(char* buf, size_t len);
+
+/**
+ * wifi_command() issues a command to the Wi-Fi driver.
+ *
+ * Android extends the standard commands listed at
+ * /link http://hostap.epitest.fi/wpa_supplicant/devel/ctrl_iface_page.html
+ * to include support for sending commands to the driver:
+ *
+ * See wifi/java/android/net/wifi/WifiNative.java for the details of
+ * driver commands that are supported
+ *
+ * @param command is the string command (preallocated with 32 bytes)
+ * @param commandlen is command buffer length
+ * @param reply is a buffer to receive a reply string
+ * @param reply_len on entry, this is the maximum length of
+ *        the reply buffer. On exit, the number of
+ *        bytes in the reply buffer.
+ *
+ * @return 0 if successful, < 0 if an error.
+ */
+int wifi_command(const char* command, char* reply, size_t* reply_len);
+
+/**
+ * Check and create if necessary initial entropy file
+ */
+int ensure_entropy_file_exists();
+
+}  // namespace wifi_system
+}  // namespace android
+
+#endif  // ANDROID_WIFI_SYSTEM_WIFI_H
diff --git a/libwifi_system/interface_tool.cpp b/libwifi_system/interface_tool.cpp
new file mode 100644
index 0000000..ed0b9f7
--- /dev/null
+++ b/libwifi_system/interface_tool.cpp
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+#include "wifi_system/interface_tool.h"
+
+#include <netinet/in.h>
+#include <sys/socket.h>
+/* We need linux/if.h for flags like IFF_UP.  Sadly, it forward declares
+   struct sockaddr and must be included after sys/socket.h. */
+#include <linux/if.h>
+
+#include <android-base/unique_fd.h>
+#include <log/log.h>
+
+namespace android {
+namespace wifi_system {
+namespace {
+
+const char kWlan0InterfaceName[] = "wlan0";
+
+}  // namespace
+
+bool InterfaceTool::SetUpState(const char* if_name, bool request_up) {
+  base::unique_fd sock(socket(PF_INET, SOCK_DGRAM, 0));
+  if (sock.get() < 0) {
+    ALOGE("Bad socket: %d, errno: %d\n", sock.get(), errno);
+    return false;
+  }
+
+  struct ifreq ifr;
+  memset(&ifr, 0, sizeof(ifr));
+  if (strlcpy(ifr.ifr_name, if_name, sizeof(ifr.ifr_name)) >=
+      sizeof(ifr.ifr_name)) {
+    ALOGE("Interface name is too long: %s\n", if_name);
+    return false;
+  }
+
+  if (TEMP_FAILURE_RETRY(ioctl(sock.get(), SIOCGIFFLAGS, &ifr)) != 0) {
+    ALOGE("Could not read interface %s errno: %d\n", if_name, errno);
+    return false;
+  }
+
+  const bool currently_up = ifr.ifr_flags & IFF_UP;
+  if (currently_up == request_up) {
+    return true;
+  }
+
+  if (request_up) {
+    ifr.ifr_flags |= IFF_UP;
+  } else {
+    ifr.ifr_flags &= ~IFF_UP;
+  }
+
+  if (TEMP_FAILURE_RETRY(ioctl(sock.get(), SIOCSIFFLAGS, &ifr)) != 0) {
+    ALOGE("Could not set interface %s flags: %d\n", if_name, errno);
+    return false;
+  }
+
+  return true;
+}
+
+bool InterfaceTool::SetWifiUpState(bool request_up) {
+  return SetUpState(kWlan0InterfaceName, request_up);
+}
+
+}  // namespace wifi_system
+}  // namespace android
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
new file mode 100644
index 0000000..312428f
--- /dev/null
+++ b/libwifi_system/testlib/include/wifi_system_test/mock_hal_tool.h
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_WIFI_SYSTEM_TEST_MOCK_HAL_TOOL_H
+#define ANDROID_WIFI_SYSTEM_TEST_MOCK_HAL_TOOL_H
+
+#include <wifi_system/hal_tool.h>
+
+namespace android {
+namespace wifi_system {
+
+class MockHalTool : public HalTool {
+ public:
+  ~MockHalTool() override = default;
+
+  MOCK_METHOD1(InitFunctionTable, bool(wifi_hal_fn*));
+  MOCK_METHOD1(CanGetValidChannels, bool(wifi_hal_fn*));
+
+};  // class MockHalTool
+
+}  // namespace wifi_system
+}  // namespace android
+
+#endif  // ANDROID_WIFI_SYSTEM_TEST_MOCK_HAL_TOOL_H
diff --git a/libwifi_system/testlib/include/wifi_system_test/mock_interface_tool.h b/libwifi_system/testlib/include/wifi_system_test/mock_interface_tool.h
new file mode 100644
index 0000000..13e6780
--- /dev/null
+++ b/libwifi_system/testlib/include/wifi_system_test/mock_interface_tool.h
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_WIFI_SYSTEM_TEST_MOCK_INTERFACE_TOOL_H
+#define ANDROID_WIFI_SYSTEM_TEST_MOCK_INTERFACE_TOOL_H
+
+#include <wifi_system/interface_tool.h>
+
+namespace android {
+namespace wifi_system {
+
+class MockInterfaceTool : public InterfaceTool {
+ public:
+  ~MockInterfaceTool() override = default;
+
+  MOCK_METHOD2(SetUpState, bool(const char* if_name, bool request_up));
+  MOCK_METHOD1(SetWifiUpState, bool(bool request_up));
+
+};  // class MockInterfaceTool
+
+}  // namespace wifi_system
+}  // namespace android
+
+#endif  // ANDROID_WIFI_SYSTEM_TEST_MOCK_INTERFACE_TOOL_H
diff --git a/libwifi_system/tests/hostapd_manager_unittest.cpp b/libwifi_system/tests/hostapd_manager_unittest.cpp
new file mode 100644
index 0000000..f8ebd99
--- /dev/null
+++ b/libwifi_system/tests/hostapd_manager_unittest.cpp
@@ -0,0 +1,145 @@
+/*
+ * 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.
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <string>
+#include <vector>
+
+#include "wifi_system/hostapd_manager.h"
+
+using std::string;
+using std::vector;
+
+namespace android {
+namespace wifi_system {
+namespace {
+
+const char kTestInterfaceName[] = "foobar0";
+const char kTestSsidStr[] = "helloisitme";
+const char kTestPassphraseStr[] = "yourelookingfor";
+const int kTestChannel = 2;
+
+#define CONFIG_COMMON_PREFIX \
+    "interface=foobar0\n" \
+    "driver=nl80211\n" \
+    "ctrl_interface=/data/misc/wifi/hostapd\n" \
+    "ssid2=68656c6c6f" "6973" "6974" "6d65\n" \
+    "channel=2\n" \
+    "ieee80211n=1\n" \
+    "hw_mode=g\n"
+
+// If you generate your config file with both the test ssid
+// and the test passphrase, you'll get this line in the config.
+#define CONFIG_PSK_LINE \
+    "wpa_psk=dffa36815281e5a6eca1910f254717fa2528681335e3bbec5966d2aa9221a66e\n"
+
+#define CONFIG_WPA_SUFFIX \
+    "wpa=3\n" \
+    "wpa_pairwise=TKIP CCMP\n" \
+    CONFIG_PSK_LINE
+
+#define CONFIG_WPA2_SUFFIX \
+    "wpa=2\n" \
+    "rsn_pairwise=CCMP\n" \
+    CONFIG_PSK_LINE
+
+const char kExpectedOpenConfig[] =
+  CONFIG_COMMON_PREFIX
+  "ignore_broadcast_ssid=0\n"
+  "wowlan_triggers=any\n";
+
+const char kExpectedWpaConfig[] =
+    CONFIG_COMMON_PREFIX
+    "ignore_broadcast_ssid=0\n"
+    "wowlan_triggers=any\n"
+    CONFIG_WPA_SUFFIX;
+
+const char kExpectedWpa2Config[] =
+    CONFIG_COMMON_PREFIX
+    "ignore_broadcast_ssid=0\n"
+    "wowlan_triggers=any\n"
+    CONFIG_WPA2_SUFFIX;
+
+class HostapdManagerTest : public ::testing::Test {
+ protected:
+  string GetConfigForEncryptionType(
+      HostapdManager::EncryptionType encryption_type) {
+    return HostapdManager().CreateHostapdConfig(
+        kTestInterfaceName,
+        cstr2vector(kTestSsidStr),
+        false,  // not hidden
+        kTestChannel,
+        encryption_type,
+        cstr2vector(kTestPassphraseStr));
+  }
+
+  vector<uint8_t> cstr2vector(const char* data) {
+    return vector<uint8_t>(data, data + strlen(data));
+  }
+};  // class HostapdManagerTest
+
+}  // namespace
+
+TEST_F(HostapdManagerTest, GeneratesCorrectOpenConfig) {
+  string config = GetConfigForEncryptionType(
+      HostapdManager::EncryptionType::kOpen);
+  EXPECT_FALSE(config.empty());
+  EXPECT_EQ(kExpectedOpenConfig, config);
+}
+
+TEST_F(HostapdManagerTest, GeneratesCorrectWpaConfig) {
+  string config = GetConfigForEncryptionType(
+      HostapdManager::EncryptionType::kWpa);
+  EXPECT_FALSE(config.empty());
+  EXPECT_EQ(kExpectedWpaConfig, config);
+}
+
+TEST_F(HostapdManagerTest, GeneratesCorrectWpa2Config) {
+  string config = GetConfigForEncryptionType(
+      HostapdManager::EncryptionType::kWpa2);
+  EXPECT_FALSE(config.empty());
+  EXPECT_EQ(kExpectedWpa2Config, config);
+}
+
+TEST_F(HostapdManagerTest, RespectsHiddenSetting) {
+  string config = HostapdManager().CreateHostapdConfig(
+        kTestInterfaceName,
+        cstr2vector(kTestSsidStr),
+        true,
+        kTestChannel,
+        HostapdManager::EncryptionType::kOpen,
+        vector<uint8_t>());
+  EXPECT_FALSE(config.find("ignore_broadcast_ssid=1\n") == string::npos);
+  EXPECT_TRUE(config.find("ignore_broadcast_ssid=0\n") == string::npos);
+}
+
+TEST_F(HostapdManagerTest, CorrectlyInfersHwMode) {
+  string config = HostapdManager().CreateHostapdConfig(
+        kTestInterfaceName,
+        cstr2vector(kTestSsidStr),
+        true,
+        44,
+        HostapdManager::EncryptionType::kOpen,
+        vector<uint8_t>());
+  EXPECT_FALSE(config.find("hw_mode=a\n") == string::npos);
+  EXPECT_TRUE(config.find("hw_mode=g\n") == string::npos);
+}
+
+
+}  // namespace wifi_system
+}  // namespace android
diff --git a/libwifi_system/tests/main.cpp b/libwifi_system/tests/main.cpp
new file mode 100644
index 0000000..51e8dad
--- /dev/null
+++ b/libwifi_system/tests/main.cpp
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include <android-base/logging.h>
+
+int main(int argc, char** argv) {
+  ::testing::InitGoogleTest(&argc, argv);
+  // Force ourselves to always log to stderr
+  android::base::InitLogging(argv, android::base::StderrLogger);
+  return RUN_ALL_TESTS();
+}
+
diff --git a/libwifi_system/wifi.cpp b/libwifi_system/wifi.cpp
new file mode 100644
index 0000000..3b08cf1
--- /dev/null
+++ b/libwifi_system/wifi.cpp
@@ -0,0 +1,566 @@
+/*
+ * Copyright 2008, 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.
+ */
+
+#include "wifi_system/wifi.h"
+#define LOG_TAG "WifiHW"
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <cutils/log.h>
+#include <cutils/memory.h>
+#include <cutils/misc.h>
+#include <cutils/properties.h>
+#include <private/android_filesystem_config.h>
+
+#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
+#include <sys/_system_properties.h>
+
+#ifdef LIBWPA_CLIENT_EXISTS
+#include <libwpa_client/wpa_ctrl.h>
+#else
+#define WPA_EVENT_TERMINATING "CTRL-EVENT-TERMINATING "
+struct wpa_ctrl {};
+void wpa_ctrl_cleanup(void) {}
+struct wpa_ctrl* wpa_ctrl_open(const char* ctrl_path) {
+  return NULL;
+}
+void wpa_ctrl_close(struct wpa_ctrl* ctrl) {}
+int wpa_ctrl_request(struct wpa_ctrl* ctrl, const char* cmd, size_t cmd_len,
+                     char* reply, size_t* reply_len,
+                     void (*msg_cb)(char* msg, size_t len)) {
+  return 0;
+}
+int wpa_ctrl_attach(struct wpa_ctrl* ctrl) { return 0; }
+int wpa_ctrl_detach(struct wpa_ctrl* ctrl) { return 0; }
+int wpa_ctrl_recv(struct wpa_ctrl* ctrl, char* reply, size_t* reply_len) {
+  return 0;
+}
+int wpa_ctrl_get_fd(struct wpa_ctrl* ctrl) { return 0; }
+#endif  // defined LIBWPA_CLIENT_EXISTS
+
+namespace android {
+namespace wifi_system {
+namespace {
+
+/* socket pair used to exit from a blocking read */
+int exit_sockets[2];
+struct wpa_ctrl* ctrl_conn;
+struct wpa_ctrl* monitor_conn;
+
+static char primary_iface[PROPERTY_VALUE_MAX];
+// TODO: use new ANDROID_SOCKET mechanism, once support for multiple
+// sockets is in
+
+#define WIFI_TEST_INTERFACE "sta"
+
+#define WIFI_DRIVER_LOADER_DELAY 1000000
+
+const char IFACE_DIR[] = "/data/system/wpa_supplicant";
+const char SUPPLICANT_NAME[] = "wpa_supplicant";
+const char SUPP_PROP_NAME[] = "init.svc.wpa_supplicant";
+const char P2P_SUPPLICANT_NAME[] = "p2p_supplicant";
+const char P2P_PROP_NAME[] = "init.svc.p2p_supplicant";
+const char SUPP_CONFIG_TEMPLATE[] = "/system/etc/wifi/wpa_supplicant.conf";
+const char SUPP_CONFIG_FILE[] = "/data/misc/wifi/wpa_supplicant.conf";
+const char P2P_CONFIG_FILE[] = "/data/misc/wifi/p2p_supplicant.conf";
+
+const char IFNAME[] = "IFNAME=";
+#define IFNAMELEN (sizeof(IFNAME) - 1)
+const char WPA_EVENT_IGNORE[] = "CTRL-EVENT-IGNORE ";
+
+unsigned char dummy_key[21] = {0x02, 0x11, 0xbe, 0x33, 0x43, 0x35, 0x68,
+                               0x47, 0x84, 0x99, 0xa9, 0x2b, 0x1c, 0xd3,
+                               0xee, 0xff, 0xf1, 0xe2, 0xf3, 0xf4, 0xf5};
+
+/* Is either SUPPLICANT_NAME or P2P_SUPPLICANT_NAME */
+char supplicant_name[PROPERTY_VALUE_MAX];
+/* Is either SUPP_PROP_NAME or P2P_PROP_NAME */
+char supplicant_prop_name[PROPERTY_KEY_MAX];
+
+void wifi_close_sockets() {
+  if (ctrl_conn != NULL) {
+    wpa_ctrl_close(ctrl_conn);
+    ctrl_conn = NULL;
+  }
+
+  if (monitor_conn != NULL) {
+    wpa_ctrl_close(monitor_conn);
+    monitor_conn = NULL;
+  }
+
+  if (exit_sockets[0] >= 0) {
+    close(exit_sockets[0]);
+    exit_sockets[0] = -1;
+  }
+
+  if (exit_sockets[1] >= 0) {
+    close(exit_sockets[1]);
+    exit_sockets[1] = -1;
+  }
+}
+
+int ensure_config_file_exists(const char* config_file) {
+  char buf[2048];
+  int srcfd, destfd;
+  int nread;
+  int ret;
+
+  ret = access(config_file, R_OK | W_OK);
+  if ((ret == 0) || (errno == EACCES)) {
+    if ((ret != 0) &&
+        (chmod(config_file, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) != 0)) {
+      ALOGE("Cannot set RW to \"%s\": %s", config_file, strerror(errno));
+      return -1;
+    }
+    return 0;
+  } else if (errno != ENOENT) {
+    ALOGE("Cannot access \"%s\": %s", config_file, strerror(errno));
+    return -1;
+  }
+
+  srcfd = TEMP_FAILURE_RETRY(open(SUPP_CONFIG_TEMPLATE, O_RDONLY));
+  if (srcfd < 0) {
+    ALOGE("Cannot open \"%s\": %s", SUPP_CONFIG_TEMPLATE, strerror(errno));
+    return -1;
+  }
+
+  destfd = TEMP_FAILURE_RETRY(open(config_file, O_CREAT | O_RDWR, 0660));
+  if (destfd < 0) {
+    close(srcfd);
+    ALOGE("Cannot create \"%s\": %s", config_file, strerror(errno));
+    return -1;
+  }
+
+  while ((nread = TEMP_FAILURE_RETRY(read(srcfd, buf, sizeof(buf)))) != 0) {
+    if (nread < 0) {
+      ALOGE("Error reading \"%s\": %s", SUPP_CONFIG_TEMPLATE, strerror(errno));
+      close(srcfd);
+      close(destfd);
+      unlink(config_file);
+      return -1;
+    }
+    TEMP_FAILURE_RETRY(write(destfd, buf, nread));
+  }
+
+  close(destfd);
+  close(srcfd);
+
+  /* chmod is needed because open() didn't set permisions properly */
+  if (chmod(config_file, 0660) < 0) {
+    ALOGE("Error changing permissions of %s to 0660: %s", config_file,
+          strerror(errno));
+    unlink(config_file);
+    return -1;
+  }
+
+  if (chown(config_file, AID_SYSTEM, AID_WIFI) < 0) {
+    ALOGE("Error changing group ownership of %s to %d: %s", config_file,
+          AID_WIFI, strerror(errno));
+    unlink(config_file);
+    return -1;
+  }
+  return 0;
+}
+
+}  // namespace
+
+const char kWiFiEntropyFile[] = "/data/misc/wifi/entropy.bin";
+
+int wifi_start_supplicant(int p2p_supported) {
+  char supp_status[PROPERTY_VALUE_MAX] = {'\0'};
+  int count = 200; /* wait at most 20 seconds for completion */
+  const prop_info* pi;
+  unsigned serial = 0;
+
+  if (p2p_supported) {
+    strcpy(supplicant_name, P2P_SUPPLICANT_NAME);
+    strcpy(supplicant_prop_name, P2P_PROP_NAME);
+
+    /* Ensure p2p config file is created */
+    if (ensure_config_file_exists(P2P_CONFIG_FILE) < 0) {
+      ALOGE("Failed to create a p2p config file");
+      return -1;
+    }
+
+  } else {
+    strcpy(supplicant_name, SUPPLICANT_NAME);
+    strcpy(supplicant_prop_name, SUPP_PROP_NAME);
+  }
+
+  /* Check whether already running */
+  if (property_get(supplicant_prop_name, supp_status, NULL) &&
+      strcmp(supp_status, "running") == 0) {
+    return 0;
+  }
+
+  /* Before starting the daemon, make sure its config file exists */
+  if (ensure_config_file_exists(SUPP_CONFIG_FILE) < 0) {
+    ALOGE("Wi-Fi will not be enabled");
+    return -1;
+  }
+
+  if (ensure_entropy_file_exists() < 0) {
+    ALOGE("Wi-Fi entropy file was not created");
+  }
+
+  /* Clear out any stale socket files that might be left over. */
+  wpa_ctrl_cleanup();
+
+  /* Reset sockets used for exiting from hung state */
+  exit_sockets[0] = exit_sockets[1] = -1;
+
+  /*
+   * Get a reference to the status property, so we can distinguish
+   * the case where it goes stopped => running => stopped (i.e.,
+   * it start up, but fails right away) from the case in which
+   * it starts in the stopped state and never manages to start
+   * running at all.
+   */
+  pi = __system_property_find(supplicant_prop_name);
+  if (pi != NULL) {
+    serial = __system_property_serial(pi);
+  }
+  property_get("wifi.interface", primary_iface, WIFI_TEST_INTERFACE);
+
+  property_set("ctl.start", supplicant_name);
+  sched_yield();
+
+  while (count-- > 0) {
+    if (pi == NULL) {
+      pi = __system_property_find(supplicant_prop_name);
+    }
+    if (pi != NULL) {
+      /*
+       * property serial updated means that init process is scheduled
+       * after we sched_yield, further property status checking is based on this
+       */
+      if (__system_property_serial(pi) != serial) {
+        __system_property_read(pi, NULL, supp_status);
+        if (strcmp(supp_status, "running") == 0) {
+          return 0;
+        } else if (strcmp(supp_status, "stopped") == 0) {
+          return -1;
+        }
+      }
+    }
+    usleep(100000);
+  }
+  return -1;
+}
+
+int wifi_stop_supplicant(int p2p_supported) {
+  char supp_status[PROPERTY_VALUE_MAX] = {'\0'};
+  int count = 50; /* wait at most 5 seconds for completion */
+
+  if (p2p_supported) {
+    strcpy(supplicant_name, P2P_SUPPLICANT_NAME);
+    strcpy(supplicant_prop_name, P2P_PROP_NAME);
+  } else {
+    strcpy(supplicant_name, SUPPLICANT_NAME);
+    strcpy(supplicant_prop_name, SUPP_PROP_NAME);
+  }
+
+  /* Check whether supplicant already stopped */
+  if (property_get(supplicant_prop_name, supp_status, NULL) &&
+      strcmp(supp_status, "stopped") == 0) {
+    return 0;
+  }
+
+  property_set("ctl.stop", supplicant_name);
+  sched_yield();
+
+  while (count-- > 0) {
+    if (property_get(supplicant_prop_name, supp_status, NULL)) {
+      if (strcmp(supp_status, "stopped") == 0) return 0;
+    }
+    usleep(100000);
+  }
+  ALOGE("Failed to stop supplicant");
+  return -1;
+}
+
+namespace {
+
+int wifi_connect_on_socket_path(const char* path) {
+  char supp_status[PROPERTY_VALUE_MAX] = {'\0'};
+
+  /* Make sure supplicant is running */
+  if (!property_get(supplicant_prop_name, supp_status, NULL) ||
+      strcmp(supp_status, "running") != 0) {
+    ALOGE("Supplicant not running, cannot connect");
+    return -1;
+  }
+
+  ctrl_conn = wpa_ctrl_open(path);
+  if (ctrl_conn == NULL) {
+    ALOGE("Unable to open connection to supplicant on \"%s\": %s", path,
+          strerror(errno));
+    return -1;
+  }
+  monitor_conn = wpa_ctrl_open(path);
+  if (monitor_conn == NULL) {
+    wpa_ctrl_close(ctrl_conn);
+    ctrl_conn = NULL;
+    return -1;
+  }
+  if (wpa_ctrl_attach(monitor_conn) != 0) {
+    wpa_ctrl_close(monitor_conn);
+    wpa_ctrl_close(ctrl_conn);
+    ctrl_conn = monitor_conn = NULL;
+    return -1;
+  }
+
+  if (socketpair(AF_UNIX, SOCK_STREAM, 0, exit_sockets) == -1) {
+    wpa_ctrl_close(monitor_conn);
+    wpa_ctrl_close(ctrl_conn);
+    ctrl_conn = monitor_conn = NULL;
+    return -1;
+  }
+
+  return 0;
+}
+
+int wifi_send_command(const char* cmd, char* reply, size_t* reply_len) {
+  int ret;
+  if (ctrl_conn == NULL) {
+    ALOGV("Not connected to wpa_supplicant - \"%s\" command dropped.\n", cmd);
+    return -1;
+  }
+  ret = wpa_ctrl_request(ctrl_conn, cmd, strlen(cmd), reply, reply_len, NULL);
+  if (ret == -2) {
+    ALOGD("'%s' command timed out.\n", cmd);
+    /* unblocks the monitor receive socket for termination */
+    TEMP_FAILURE_RETRY(write(exit_sockets[0], "T", 1));
+    return -2;
+  } else if (ret < 0 || strncmp(reply, "FAIL", 4) == 0) {
+    return -1;
+  }
+  if (strncmp(cmd, "PING", 4) == 0) {
+    reply[*reply_len] = '\0';
+  }
+  return 0;
+}
+
+int wifi_supplicant_connection_active() {
+  char supp_status[PROPERTY_VALUE_MAX] = {'\0'};
+
+  if (property_get(supplicant_prop_name, supp_status, NULL)) {
+    if (strcmp(supp_status, "stopped") == 0) return -1;
+  }
+
+  return 0;
+}
+
+int wifi_ctrl_recv(char* reply, size_t* reply_len) {
+  int res;
+  int ctrlfd = wpa_ctrl_get_fd(monitor_conn);
+  struct pollfd rfds[2];
+
+  memset(rfds, 0, 2 * sizeof(struct pollfd));
+  rfds[0].fd = ctrlfd;
+  rfds[0].events |= POLLIN;
+  rfds[1].fd = exit_sockets[1];
+  rfds[1].events |= POLLIN;
+  do {
+    res = TEMP_FAILURE_RETRY(poll(rfds, 2, 30000));
+    if (res < 0) {
+      ALOGE("Error poll = %d", res);
+      return res;
+    } else if (res == 0) {
+      /* timed out, check if supplicant is active
+       * or not ..
+       */
+      res = wifi_supplicant_connection_active();
+      if (res < 0) return -2;
+    }
+  } while (res == 0);
+
+  if (rfds[0].revents & POLLIN) {
+    return wpa_ctrl_recv(monitor_conn, reply, reply_len);
+  }
+
+  /* it is not rfds[0], then it must be rfts[1] (i.e. the exit socket)
+   * or we timed out. In either case, this call has failed ..
+   */
+  return -2;
+}
+
+int wifi_wait_on_socket(char* buf, size_t buflen) {
+  size_t nread = buflen - 1;
+  int result;
+  char* match;
+  char* match2;
+
+  if (monitor_conn == NULL) {
+    return snprintf(buf, buflen, "IFNAME=%s %s - connection closed",
+                    primary_iface, WPA_EVENT_TERMINATING);
+  }
+
+  result = wifi_ctrl_recv(buf, &nread);
+
+  /* Terminate reception on exit socket */
+  if (result == -2) {
+    return snprintf(buf, buflen, "IFNAME=%s %s - connection closed",
+                    primary_iface, WPA_EVENT_TERMINATING);
+  }
+
+  if (result < 0) {
+    ALOGD("wifi_ctrl_recv failed: %s\n", strerror(errno));
+    return snprintf(buf, buflen, "IFNAME=%s %s - recv error", primary_iface,
+                    WPA_EVENT_TERMINATING);
+  }
+  buf[nread] = '\0';
+  /* Check for EOF on the socket */
+  if (result == 0 && nread == 0) {
+    /* Fabricate an event to pass up */
+    ALOGD("Received EOF on supplicant socket\n");
+    return snprintf(buf, buflen, "IFNAME=%s %s - signal 0 received",
+                    primary_iface, WPA_EVENT_TERMINATING);
+  }
+  /*
+   * Events strings are in the format
+   *
+   *     IFNAME=iface <N>CTRL-EVENT-XXX
+   *        or
+   *     <N>CTRL-EVENT-XXX
+   *
+   * where N is the message level in numerical form (0=VERBOSE, 1=DEBUG,
+   * etc.) and XXX is the event name. The level information is not useful
+   * to us, so strip it off.
+   */
+
+  if (strncmp(buf, IFNAME, IFNAMELEN) == 0) {
+    match = strchr(buf, ' ');
+    if (match != NULL) {
+      if (match[1] == '<') {
+        match2 = strchr(match + 2, '>');
+        if (match2 != NULL) {
+          nread -= (match2 - match);
+          memmove(match + 1, match2 + 1, nread - (match - buf) + 1);
+        }
+      }
+    } else {
+      return snprintf(buf, buflen, "%s", WPA_EVENT_IGNORE);
+    }
+  } else if (buf[0] == '<') {
+    match = strchr(buf, '>');
+    if (match != NULL) {
+      nread -= (match + 1 - buf);
+      memmove(buf, match + 1, nread + 1);
+      ALOGV("supplicant generated event without interface - %s\n", buf);
+    }
+  } else {
+    /* let the event go as is! */
+    ALOGW(
+        "supplicant generated event without interface and without message "
+        "level - %s\n",
+        buf);
+  }
+
+  return nread;
+}
+
+}  // namespace
+
+/* Establishes the control and monitor socket connections on the interface */
+int wifi_connect_to_supplicant() {
+  static char path[PATH_MAX];
+
+  if (access(IFACE_DIR, F_OK) == 0) {
+    snprintf(path, sizeof(path), "%s/%s", IFACE_DIR, primary_iface);
+  } else {
+    snprintf(path, sizeof(path), "@android:wpa_%s", primary_iface);
+  }
+  return wifi_connect_on_socket_path(path);
+}
+
+void wifi_close_supplicant_connection() {
+  char supp_status[PROPERTY_VALUE_MAX] = {'\0'};
+  int count =
+      50; /* wait at most 5 seconds to ensure init has stopped stupplicant */
+
+  wifi_close_sockets();
+
+  while (count-- > 0) {
+    if (property_get(supplicant_prop_name, supp_status, NULL)) {
+      if (strcmp(supp_status, "stopped") == 0) return;
+    }
+    usleep(100000);
+  }
+}
+
+int wifi_wait_for_event(char* buf, size_t buflen) {
+  return wifi_wait_on_socket(buf, buflen);
+}
+
+int wifi_command(const char* command, char* reply, size_t* reply_len) {
+  return wifi_send_command(command, reply, reply_len);
+}
+
+int ensure_entropy_file_exists() {
+  int ret;
+  int destfd;
+
+  ret = access(kWiFiEntropyFile, R_OK | W_OK);
+  if ((ret == 0) || (errno == EACCES)) {
+    if ((ret != 0) &&
+        (chmod(kWiFiEntropyFile, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) != 0)) {
+      ALOGE("Cannot set RW to \"%s\": %s", kWiFiEntropyFile, strerror(errno));
+      return -1;
+    }
+    return 0;
+  }
+  destfd = TEMP_FAILURE_RETRY(open(kWiFiEntropyFile, O_CREAT | O_RDWR, 0660));
+  if (destfd < 0) {
+    ALOGE("Cannot create \"%s\": %s", kWiFiEntropyFile, strerror(errno));
+    return -1;
+  }
+
+  if (TEMP_FAILURE_RETRY(write(destfd, dummy_key, sizeof(dummy_key))) !=
+      sizeof(dummy_key)) {
+    ALOGE("Error writing \"%s\": %s", kWiFiEntropyFile, strerror(errno));
+    close(destfd);
+    return -1;
+  }
+  close(destfd);
+
+  /* chmod is needed because open() didn't set permisions properly */
+  if (chmod(kWiFiEntropyFile, 0660) < 0) {
+    ALOGE("Error changing permissions of %s to 0660: %s", kWiFiEntropyFile,
+          strerror(errno));
+    unlink(kWiFiEntropyFile);
+    return -1;
+  }
+
+  if (chown(kWiFiEntropyFile, AID_SYSTEM, AID_WIFI) < 0) {
+    ALOGE("Error changing group ownership of %s to %d: %s", kWiFiEntropyFile,
+          AID_WIFI, strerror(errno));
+    unlink(kWiFiEntropyFile);
+    return -1;
+  }
+  return 0;
+}
+
+}  // namespace wifi_system
+}  // namespace android
diff --git a/service/Android.mk b/service/Android.mk
index 5c6c839..d949ea4 100644
--- a/service/Android.mk
+++ b/service/Android.mk
@@ -16,79 +16,15 @@
 
 ifneq ($(TARGET_BUILD_PDK), true)
 
-# Make HAL stub library 1
-# ============================================================
-
-include $(CLEAR_VARS)
-
-LOCAL_REQUIRED_MODULES :=
-
-LOCAL_CFLAGS += -Wall -Werror -Wextra -Wno-unused-parameter -Wno-unused-function \
-                -Wunused-variable -Winit-self -Wwrite-strings -Wshadow
-
-LOCAL_C_INCLUDES += \
-	external/libnl-headers \
-	$(call include-path-for, libhardware_legacy)/hardware_legacy
-
-LOCAL_SRC_FILES := \
-	lib/wifi_hal.cpp
-
-LOCAL_MODULE := libwifi-hal
-
-include $(BUILD_STATIC_LIBRARY)
-
-# Make HAL stub library 2
-# ============================================================
-
-include $(CLEAR_VARS)
-
-LOCAL_REQUIRED_MODULES :=
-
-LOCAL_CFLAGS += -Wall -Werror -Wextra -Wno-unused-parameter -Wno-unused-function \
-                -Wunused-variable -Winit-self -Wwrite-strings -Wshadow
-
-LOCAL_C_INCLUDES += \
-	$(LOCAL_PATH)/jni \
-	external/libnl-headers \
-	$(call include-path-for, libhardware_legacy)/hardware_legacy
-
-LOCAL_SRC_FILES := \
-	lib/wifi_hal_stub.cpp
-
-LOCAL_MODULE := libwifi-hal-stub
-
-include $(BUILD_STATIC_LIBRARY)
-
-# set correct hal library path
-# ============================================================
-LIB_WIFI_HAL := libwifi-hal
-
-ifeq ($(BOARD_WLAN_DEVICE), bcmdhd)
-  LIB_WIFI_HAL := libwifi-hal-bcm
-else ifeq ($(BOARD_WLAN_DEVICE), qcwcn)
-  LIB_WIFI_HAL := libwifi-hal-qcom
-else ifeq ($(BOARD_WLAN_DEVICE), mrvl)
-  # this is commented because none of the nexus devices
-  # that sport Marvell's wifi have support for HAL
-  # LIB_WIFI_HAL := libwifi-hal-mrvl
-else ifeq ($(BOARD_WLAN_DEVICE), MediaTek)
-  # support MTK WIFI HAL
-  LIB_WIFI_HAL := libwifi-hal-mt66xx
-endif
-
 # Make the JNI part
 # ============================================================
 include $(CLEAR_VARS)
 
-LOCAL_REQUIRED_MODULES := libhardware_legacy
-
 LOCAL_CFLAGS += -Wall -Werror -Wextra -Wno-unused-parameter -Wno-unused-function \
                 -Wunused-variable -Winit-self -Wwrite-strings -Wshadow
 
 LOCAL_C_INCLUDES += \
 	$(JNI_H_INCLUDE) \
-	$(call include-path-for, libhardware)/hardware \
-	$(call include-path-for, libhardware_legacy)/hardware_legacy \
 	libcore/include
 
 LOCAL_SHARED_LIBRARIES += \
@@ -96,19 +32,15 @@
 	libnativehelper \
 	libcutils \
 	libutils \
-	libhardware \
-	libhardware_legacy \
-	libnl \
-	libdl
-
-LOCAL_STATIC_LIBRARIES += libwifi-hal-stub
-LOCAL_STATIC_LIBRARIES += $(LIB_WIFI_HAL)
+	libdl \
+	libwifi-hal \
+	libwifi-system
 
 LOCAL_SRC_FILES := \
 	jni/com_android_server_wifi_WifiNative.cpp \
 	jni/jni_helper.cpp
 
-ifdef INCLUDE_NAN_FEATURE
+ifeq ($(BOARD_HAS_NAN), true)
 LOCAL_SRC_FILES += \
 	jni/com_android_server_wifi_nan_WifiNanNative.cpp
 endif
@@ -120,15 +52,19 @@
 # Build the java code
 # ============================================================
 
+wificond_aidl_path := system/connectivity/wificond/aidl
+wificond_aidl_rel_path := ../../../../../$(wificond_aidl_path)
+
 include $(CLEAR_VARS)
 
-LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/java
+LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/java $(wificond_aidl_path)
 LOCAL_SRC_FILES := $(call all-java-files-under, java) \
 	$(call all-Iaidl-files-under, java) \
+	$(call all-Iaidl-files-under, $(wificond_aidl_rel_path)) \
 	$(call all-logtags-files-under, java) \
 	$(call all-proto-files-under, proto)
 
-ifndef INCLUDE_NAN_FEATURE
+ifneq ($(BOARD_HAS_NAN), true)
 LOCAL_SRC_FILES := $(filter-out $(call all-java-files-under, \
           java/com/android/server/wifi/nan),$(LOCAL_SRC_FILES))
 endif
diff --git a/service/java/com/android/server/wifi/Clock.java b/service/java/com/android/server/wifi/Clock.java
index df0df6d..bb2f21e 100644
--- a/service/java/com/android/server/wifi/Clock.java
+++ b/service/java/com/android/server/wifi/Clock.java
@@ -27,7 +27,7 @@
      *
      * @return Current time in milliseconds.
      */
-    public long currentTimeMillis() {
+    public long getWallClockMillis() {
         return System.currentTimeMillis();
     }
 
@@ -36,26 +36,26 @@
      *
      * @return Current time since boot in milliseconds.
      */
-    public long elapsedRealtime() {
+    public long getElapsedSinceBootMillis() {
         return SystemClock.elapsedRealtime();
     }
 
-    /**
-     * Returns the current timestamp of the most precise timer available on the local system, in
-     * nanoseconds.
-     *
-     * @return Current time in nanoseconds.
-     */
-    public long nanoTime() {
-        return System.nanoTime();
-    }
-
-    /**
+   /**
      * Returns nanoseconds since boot, including time spent in sleep.
      *
      * @return Current time since boot in nanoseconds.
      */
-    public long elapsedRealtimeNanos() {
+    public long getElapsedSinceBootNanos() {
         return SystemClock.elapsedRealtimeNanos();
     }
+
+    /**
+     * Returns milliseconds since boot, not counting time spent in deep sleep.
+     *
+     * @return Milliseconds of non-sleep uptime since boot.
+     */
+    public long getUptimeSinceBootMillis() {
+        return SystemClock.uptimeMillis();
+    }
+
 }
diff --git a/service/java/com/android/server/wifi/FrameworkFacade.java b/service/java/com/android/server/wifi/FrameworkFacade.java
index cb03f7a..b273731 100644
--- a/service/java/com/android/server/wifi/FrameworkFacade.java
+++ b/service/java/com/android/server/wifi/FrameworkFacade.java
@@ -20,14 +20,13 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
-import android.net.ConnectivityManager;
+import android.content.pm.PackageManager;
 import android.net.TrafficStats;
 import android.net.ip.IpManager;
 import android.net.wifi.IWifiScanner;
 import android.net.wifi.WifiScanner;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.INetworkManagementService;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -36,8 +35,6 @@
 import android.security.KeyStore;
 import android.telephony.CarrierConfigManager;
 
-import java.util.ArrayList;
-
 /**
  * This class allows overriding objects with mocks to write unit tests
  */
@@ -128,28 +125,6 @@
     }
 
     /**
-     * Create a SoftApManager.
-     * @param context current context
-     * @param looper current thread looper
-     * @param wifiNative reference to WifiNative
-     * @param nmService reference to NetworkManagementService
-     * @param cm reference to ConnectivityManager
-     * @param countryCode Country code
-     * @param allowed2GChannels list of allowed 2G channels
-     * @param listener listener for SoftApManager
-     * @return an instance of SoftApManager
-     */
-    public SoftApManager makeSoftApManager(
-            Context context, Looper looper, WifiNative wifiNative,
-            INetworkManagementService nmService, ConnectivityManager cm,
-            String countryCode, ArrayList<Integer> allowed2GChannels,
-            SoftApManager.Listener listener) {
-        return new SoftApManager(
-                looper, wifiNative, nmService, countryCode,
-                allowed2GChannels, listener);
-    }
-
-    /**
      * Checks whether the given uid has been granted the given permission.
      * @param permName the permission to check
      * @param uid The uid to check
diff --git a/service/java/com/android/server/wifi/NetworkUpdateResult.java b/service/java/com/android/server/wifi/NetworkUpdateResult.java
index 63cc33f..22c8e22 100644
--- a/service/java/com/android/server/wifi/NetworkUpdateResult.java
+++ b/service/java/com/android/server/wifi/NetworkUpdateResult.java
@@ -67,4 +67,9 @@
     public void setIsNewNetwork(boolean isNew) {
         isNewNetwork = isNew;
     }
+
+    public boolean isSuccess() {
+        return netId != INVALID_NETWORK_ID;
+    }
+
 }
diff --git a/service/java/com/android/server/wifi/README.txt b/service/java/com/android/server/wifi/README.txt
index 0d74da1..2ab4d8b 100644
--- a/service/java/com/android/server/wifi/README.txt
+++ b/service/java/com/android/server/wifi/README.txt
@@ -1,10 +1,26 @@
-This code has moved from 
+Path history for this code:
 
-frameworks/base/services/java/com/android/server/wifi: gitk <SHA1 to be filled in later>
+commit date: 2013-12-18 to 2014-01-07
+commit hash: a07c419913bfae2a896fbc29e8f269ee08c4d910 (add)
+commit hash: 4a3f9cf099bbbe52dc0edb2a7e1d1c976bc335a3 (delete)
+dst:         frameworks/opt/net/wifi/service
+src:         frameworks/base/services/core/java/com/android/server/wifi
 
-Prior to that it was at
+commit date: 2013-12-19
+commit hash: 9158825f9c41869689d6b1786d7c7aa8bdd524ce (many more files)
+commit hash: 19c662b3df3b35756a92282bb6cc767e6407cb8a (a few files)
+dst:         frameworks/base/services/core/java/com/android/server/wifi
+src:         frameworks/base/services/java/com/android/server/wifi
 
-frameworks/base/wifi/java/android/net/wifi: gitk ffadfb9ffdced62db215319d3edc7717802088fb
+commit date: 2013-12-11
+commit hash: ffadfb9ffdced62db215319d3edc7717802088fb
+dst:         frameworks/base/services/java/com/android/server/wifi
+src:         frameworks/base/wifi/java/android/net/wifi
+
+commit date: 2008-10-21
+commit hash: 54b6cfa9a9e5b861a9930af873580d6dc20f773c
+dst:         frameworks/base/wifi/java/android/net/wifi
+src:         initial aosp import?
 
 ////////////////////////////////////////////////////////////////
 
diff --git a/service/java/com/android/server/wifi/ScanDetail.java b/service/java/com/android/server/wifi/ScanDetail.java
index dc87a5b..23e52db 100644
--- a/service/java/com/android/server/wifi/ScanDetail.java
+++ b/service/java/com/android/server/wifi/ScanDetail.java
@@ -83,40 +83,9 @@
         mScanResult = scanResult;
         mNetworkDetail = networkDetail;
         mMatches = matches;
-        mSeen = mScanResult.seen;
-    }
-
-    /**
-     * Update the data stored in the scan result with the provided information.
-     *
-     * @param networkDetail NetworkDetail
-     * @param level int
-     * @param wssid WifiSsid
-     * @param ssid String
-     * @param flags String
-     * @param freq int
-     * @param tsf long
-     */
-    public void updateResults(NetworkDetail networkDetail, int level, WifiSsid wssid, String ssid,
-                              String flags, int freq, long tsf) {
-        mScanResult.level = level;
-        mScanResult.wifiSsid = wssid;
-        // Keep existing API
-        mScanResult.SSID = ssid;
-        mScanResult.capabilities = flags;
-        mScanResult.frequency = freq;
-        mScanResult.timestamp = tsf;
-        mSeen = System.currentTimeMillis();
-        //mScanResult.seen = mSeen;
-        mScanResult.channelWidth = networkDetail.getChannelWidth();
-        mScanResult.centerFreq0 = networkDetail.getCenterfreq0();
-        mScanResult.centerFreq1 = networkDetail.getCenterfreq1();
-        if (networkDetail.is80211McResponderSupport()) {
-            mScanResult.setFlag(ScanResult.FLAG_80211mc_RESPONDER);
-        }
-        if (networkDetail.isInterworking()) {
-            mScanResult.setFlag(ScanResult.FLAG_PASSPOINT_NETWORK);
-        }
+        // Only inherit |mScanResult.seen| if it was previously set. This ensures that |mSeen|
+        // will always contain a valid timestamp.
+        mSeen = (mScanResult.seen == 0) ? System.currentTimeMillis() : mScanResult.seen;
     }
 
     /**
diff --git a/service/java/com/android/server/wifi/ScanDetailCache.java b/service/java/com/android/server/wifi/ScanDetailCache.java
index cb44e2a..ede9a6e 100644
--- a/service/java/com/android/server/wifi/ScanDetailCache.java
+++ b/service/java/com/android/server/wifi/ScanDetailCache.java
@@ -29,8 +29,8 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashMap;
 import java.util.Iterator;
-import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * Maps BSSIDs to their individual ScanDetails for a given WifiConfiguration.
@@ -40,14 +40,29 @@
     private static final String TAG = "ScanDetailCache";
     private static final boolean DBG = false;
 
-    private WifiConfiguration mConfig;
-    private ConcurrentHashMap<String, ScanDetail> mMap;
-    private ConcurrentHashMap<String, PasspointMatchInfo> mPasspointMatches;
+    private final WifiConfiguration mConfig;
+    private final int mMaxSize;
+    private final int mTrimSize;
+    private final HashMap<String, ScanDetail> mMap;
+    private final HashMap<String, PasspointMatchInfo> mPasspointMatches;
 
-    ScanDetailCache(WifiConfiguration config) {
+    /**
+     * Scan Detail cache associated with each configured network.
+     *
+     * The cache size is trimmed down to |trimSize| once it crosses the provided |maxSize|.
+     * Since this operation is relatively expensive, ensure that |maxSize| and |trimSize| are not
+     * too close to each other. |trimSize| should always be <= |maxSize|.
+     *
+     * @param config   WifiConfiguration object corresponding to the network.
+     * @param maxSize  Max size desired for the cache.
+     * @param trimSize Size to trim the cache down to once it reaches |maxSize|.
+     */
+    ScanDetailCache(WifiConfiguration config, int maxSize, int trimSize) {
         mConfig = config;
-        mMap = new ConcurrentHashMap(16, 0.75f, 2);
-        mPasspointMatches = new ConcurrentHashMap(16, 0.75f, 2);
+        mMaxSize = maxSize;
+        mTrimSize = trimSize;
+        mMap = new HashMap(16, 0.75f);
+        mPasspointMatches = new HashMap(16, 0.75f);
     }
 
     void put(ScanDetail scanDetail) {
@@ -55,6 +70,10 @@
     }
 
     void put(ScanDetail scanDetail, PasspointMatch match, HomeSP homeSp) {
+        // First check if we have reached |maxSize|. if yes, trim it down to |trimSize|.
+        if (mMap.size() >= mMaxSize) {
+            trim();
+        }
 
         mMap.put(scanDetail.getBSSIDString(), scanDetail);
 
@@ -75,6 +94,7 @@
 
     void remove(String bssid) {
         mMap.remove(bssid);
+        mPasspointMatches.remove(bssid);
     }
 
     int size() {
@@ -94,13 +114,12 @@
     }
 
     /**
-     * Method to reduce the cache to the given size by removing the oldest entries.
-     *
-     * @param num int target cache size
+     * Method to reduce the cache to |mTrimSize| size by removing the oldest entries.
+     * TODO: Investigate if this method can be further optimized.
      */
-    public void trim(int num) {
+    private void trim() {
         int currentSize = mMap.size();
-        if (currentSize <= num) {
+        if (currentSize < mTrimSize) {
             return; // Nothing to trim
         }
         ArrayList<ScanDetail> list = new ArrayList<ScanDetail>(mMap.values());
@@ -120,7 +139,7 @@
                 }
             });
         }
-        for (int i = 0; i < currentSize - num; i++) {
+        for (int i = 0; i < currentSize - mTrimSize; i++) {
             // Remove oldest results from scan cache
             ScanDetail result = list.get(i);
             mMap.remove(result.getBSSIDString());
@@ -288,7 +307,6 @@
     }
 
 
-
     @Override
     public String toString() {
         StringBuilder sbuf = new StringBuilder();
diff --git a/service/java/com/android/server/wifi/SoftApManager.java b/service/java/com/android/server/wifi/SoftApManager.java
index 2dfb754..266234b 100644
--- a/service/java/com/android/server/wifi/SoftApManager.java
+++ b/service/java/com/android/server/wifi/SoftApManager.java
@@ -20,13 +20,14 @@
 import static com.android.server.wifi.util.ApConfigUtil.ERROR_NO_CHANNEL;
 import static com.android.server.wifi.util.ApConfigUtil.SUCCESS;
 
-import android.content.Context;
-import android.net.ConnectivityManager;
+import android.net.wifi.IApInterface;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
+import android.os.IBinder.DeathRecipient;
 import android.os.INetworkManagementService;
 import android.os.Looper;
 import android.os.Message;
+import android.os.RemoteException;
 import android.util.Log;
 
 import com.android.internal.util.State;
@@ -72,8 +73,9 @@
                          INetworkManagementService nmService,
                          String countryCode,
                          ArrayList<Integer> allowed2GChannels,
-                         Listener listener) {
-        mStateMachine = new SoftApStateMachine(looper);
+                         Listener listener,
+                         IApInterface apInterface) {
+        mStateMachine = new SoftApStateMachine(looper, apInterface);
 
         mNmService = nmService;
         mWifiNative = wifiNative;
@@ -174,15 +176,29 @@
         /* Commands for the state machine. */
         public static final int CMD_START = 0;
         public static final int CMD_STOP = 1;
+        public static final int CMD_AP_INTERFACE_BINDER_DEATH = 2;
+
+        private final IApInterface mApInterface;
 
         private final State mIdleState = new IdleState();
         private final State mStartedState = new StartedState();
 
-        SoftApStateMachine(Looper looper) {
+        private ApInterfaceDeathRecipient mDeathRecipient;
+
+        private class ApInterfaceDeathRecipient implements DeathRecipient {
+            @Override
+            public void binderDied() {
+                SoftApStateMachine.this.sendMessage(CMD_AP_INTERFACE_BINDER_DEATH);
+            }
+        }
+
+
+        SoftApStateMachine(Looper looper, IApInterface apInterface) {
             super(TAG, looper);
+            mApInterface = apInterface;
 
             addState(mIdleState);
-            addState(mStartedState, mIdleState);
+            addState(mStartedState);
 
             setInitialState(mIdleState);
             start();
@@ -190,11 +206,31 @@
 
         private class IdleState extends State {
             @Override
+            public void enter() {
+                if (mDeathRecipient != null) {
+                    mApInterface.asBinder().unlinkToDeath(mDeathRecipient, 0);
+                    mDeathRecipient = null;
+                }
+            }
+
+            @Override
             public boolean processMessage(Message message) {
                 switch (message.what) {
                     case CMD_START:
+                        int result = SUCCESS;
                         updateApState(WifiManager.WIFI_AP_STATE_ENABLING, 0);
-                        int result = startSoftAp((WifiConfiguration) message.obj);
+                        mDeathRecipient = new ApInterfaceDeathRecipient();
+                        try {
+                            mApInterface.asBinder().linkToDeath(mDeathRecipient, 0);
+                        } catch (RemoteException e) {
+                            // The remote has already died.
+                            result = ERROR_GENERIC;
+                            break;
+                        }
+
+                        if (result == SUCCESS) {
+                            result = startSoftAp((WifiConfiguration) message.obj);
+                        }
                         if (result == SUCCESS) {
                             updateApState(WifiManager.WIFI_AP_STATE_ENABLED, 0);
                             transitionTo(mStartedState);
@@ -204,12 +240,17 @@
                                 reason = WifiManager.SAP_START_FAILURE_NO_CHANNEL;
                             }
                             updateApState(WifiManager.WIFI_AP_STATE_FAILED, reason);
+                            if (mDeathRecipient != null) {
+                                mApInterface.asBinder().unlinkToDeath(mDeathRecipient, 0);
+                                mDeathRecipient = null;
+                            }
                         }
                         break;
                     default:
                         /* Ignore all other commands. */
                         break;
                 }
+
                 return HANDLED;
             }
         }
@@ -221,10 +262,16 @@
                     case CMD_START:
                         /* Already started, ignore this command. */
                         break;
+                    case CMD_AP_INTERFACE_BINDER_DEATH:
                     case CMD_STOP:
                         updateApState(WifiManager.WIFI_AP_STATE_DISABLING, 0);
                         stopSoftAp();
-                        updateApState(WifiManager.WIFI_AP_STATE_DISABLED, 0);
+                        if (message.what == CMD_AP_INTERFACE_BINDER_DEATH) {
+                            updateApState(WifiManager.WIFI_AP_STATE_FAILED,
+                                    WifiManager.SAP_START_FAILURE_GENERAL);
+                        } else {
+                            updateApState(WifiManager.WIFI_AP_STATE_DISABLED, 0);
+                        }
                         transitionTo(mIdleState);
                         break;
                     default:
diff --git a/service/java/com/android/server/wifi/WifiBackupRestore.java b/service/java/com/android/server/wifi/WifiBackupRestore.java
new file mode 100644
index 0000000..c83e063
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiBackupRestore.java
@@ -0,0 +1,749 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.net.IpConfiguration;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiEnterpriseConfig;
+import android.util.Log;
+import android.util.Pair;
+import android.util.SparseArray;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+import com.android.server.net.IpConfigStore;
+import com.android.server.wifi.util.XmlUtil;
+import com.android.server.wifi.util.XmlUtil.IpConfigurationXmlUtil;
+import com.android.server.wifi.util.XmlUtil.WifiConfigurationXmlUtil;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.CharArrayReader;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Class used to backup/restore data using the SettingsBackupAgent.
+ * There are 2 symmetric API's exposed here:
+ * 1. retrieveBackupDataFromConfigurations: Retrieve the configuration data to be backed up.
+ * 2. retrieveConfigurationsFromBackupData: Restore the configuration using the provided data.
+ * The byte stream to be backed up is XML encoded and versioned to migrate the data easily across
+ * revisions.
+ */
+public class WifiBackupRestore {
+    private static final String TAG = "WifiBackupRestore";
+
+    /**
+     * Current backup data version. This will be incremented for any additions.
+     */
+    private static final int CURRENT_BACKUP_DATA_VERSION = 1;
+
+    /** This list of older versions will be used to restore data from older backups. */
+    /**
+     * First version of the backup data format.
+     */
+    private static final int INITIAL_BACKUP_DATA_VERSION = 1;
+
+    /**
+     * List of XML section header tags in the backed up data
+     */
+    private static final String XML_TAG_DOCUMENT_HEADER = "WifiBackupData";
+    private static final String XML_TAG_VERSION = "Version";
+    private static final String XML_TAG_SECTION_HEADER_NETWORK_LIST = "NetworkList";
+    private static final String XML_TAG_SECTION_HEADER_NETWORK = "Network";
+    private static final String XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION = "WifiConfiguration";
+    private static final String XML_TAG_SECTION_HEADER_IP_CONFIGURATION = "IpConfiguration";
+
+    /**
+     * Regex to mask out passwords in backup data dump.
+     */
+    private static final String PSK_MASK_LINE_MATCH_PATTERN =
+            "<.*" + WifiConfigurationXmlUtil.XML_TAG_PRE_SHARED_KEY + ".*>.*<.*>";
+    private static final String PSK_MASK_SEARCH_PATTERN =
+            "(<.*" + WifiConfigurationXmlUtil.XML_TAG_PRE_SHARED_KEY + ".*>)(.*)(<.*>)";
+    private static final String PSK_MASK_REPLACE_PATTERN = "$1*$3";
+
+    private static final String WEP_KEYS_MASK_LINE_START_MATCH_PATTERN =
+            "<string-array.*" + WifiConfigurationXmlUtil.XML_TAG_WEP_KEYS + ".*num=\"[0-9]\">";
+    private static final String WEP_KEYS_MASK_LINE_END_MATCH_PATTERN = "</string-array>";
+    private static final String WEP_KEYS_MASK_SEARCH_PATTERN = "(<.*=)(.*)(/>)";
+    private static final String WEP_KEYS_MASK_REPLACE_PATTERN = "$1*$3";
+
+    /**
+     * Verbose logging flag.
+     */
+    private boolean mVerboseLoggingEnabled = false;
+
+    /**
+     * Store the dump of the backup/restore data for debugging. This is only stored when verbose
+     * logging is enabled in developer options.
+     */
+    private byte[] mDebugLastBackupDataRetrieved;
+    private byte[] mDebugLastBackupDataRestored;
+    private byte[] mDebugLastSupplicantBackupDataRestored;
+
+    /**
+     * Retrieve an XML byte stream representing the data that needs to be backed up from the
+     * provided configurations.
+     *
+     * @param configurations list of currently saved networks that needs to be backed up.
+     * @return Raw byte stream of XML that needs to be backed up.
+     */
+    public byte[] retrieveBackupDataFromConfigurations(List<WifiConfiguration> configurations) {
+        if (configurations == null) {
+            Log.e(TAG, "Invalid configuration list received");
+            return null;
+        }
+
+        try {
+            final XmlSerializer out = new FastXmlSerializer();
+            final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+            out.setOutput(outputStream, StandardCharsets.UTF_8.name());
+
+            // Start writing the XML stream.
+            XmlUtil.writeDocumentStart(out, XML_TAG_DOCUMENT_HEADER);
+
+            XmlUtil.writeNextValue(out, XML_TAG_VERSION, CURRENT_BACKUP_DATA_VERSION);
+
+            writeNetworkConfigurationsToXml(out, configurations);
+
+            XmlUtil.writeDocumentEnd(out, XML_TAG_DOCUMENT_HEADER);
+
+            byte[] data = outputStream.toByteArray();
+
+            if (mVerboseLoggingEnabled) {
+                mDebugLastBackupDataRetrieved = data;
+            }
+
+            return data;
+        } catch (XmlPullParserException e) {
+            Log.e(TAG, "Error retrieving the backup data: " + e);
+        } catch (IOException e) {
+            Log.e(TAG, "Error retrieving the backup data: " + e);
+        }
+        return null;
+    }
+
+    /**
+     * Write the list of configurations to the XML stream.
+     */
+    private void writeNetworkConfigurationsToXml(
+            XmlSerializer out, List<WifiConfiguration> configurations)
+            throws XmlPullParserException, IOException {
+        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_NETWORK_LIST);
+        for (WifiConfiguration configuration : configurations) {
+            // We don't want to backup/restore enterprise/passpoint configurations.
+            if (configuration.isEnterprise() || configuration.isPasspoint()) {
+                Log.d(TAG, "Skipping enterprise network for backup: " + configuration.configKey());
+                continue;
+            }
+            // Write this configuration data now.
+            XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_NETWORK);
+            writeNetworkConfigurationToXml(out, configuration);
+            XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_NETWORK);
+        }
+        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_NETWORK_LIST);
+    }
+
+    /**
+     * Write the configuration data elements from the provided Configuration to the XML stream.
+     * Uses XmlUtils to write the values of each element.
+     */
+    private void writeNetworkConfigurationToXml(XmlSerializer out, WifiConfiguration configuration)
+            throws XmlPullParserException, IOException {
+        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION);
+        WifiConfigurationXmlUtil.writeToXmlForBackup(out, configuration);
+        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION);
+        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_IP_CONFIGURATION);
+        IpConfigurationXmlUtil.writeToXml(out, configuration.getIpConfiguration());
+        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_IP_CONFIGURATION);
+    }
+
+    /**
+     * Parse out the configurations from the back up data.
+     *
+     * @param data raw byte stream representing the XML data.
+     * @return list of networks retrieved from the backed up data.
+     */
+    public List<WifiConfiguration> retrieveConfigurationsFromBackupData(byte[] data) {
+        if (data == null || data.length == 0) {
+            Log.e(TAG, "Invalid backup data received");
+            return null;
+        }
+
+        try {
+            if (mVerboseLoggingEnabled) {
+                mDebugLastBackupDataRestored = data;
+            }
+
+            final XmlPullParser in = Xml.newPullParser();
+            ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
+            in.setInput(inputStream, StandardCharsets.UTF_8.name());
+
+            // Start parsing the XML stream.
+            XmlUtil.gotoDocumentStart(in, XML_TAG_DOCUMENT_HEADER);
+            int rootTagDepth = in.getDepth();
+
+            int version = (int) XmlUtil.readNextValueWithName(in, XML_TAG_VERSION);
+            if (version < INITIAL_BACKUP_DATA_VERSION || version > CURRENT_BACKUP_DATA_VERSION) {
+                Log.e(TAG, "Invalid version of data: " + version);
+                return null;
+            }
+
+            return parseNetworkConfigurationsFromXml(in, rootTagDepth, version);
+        } catch (XmlPullParserException e) {
+            Log.e(TAG, "Error parsing the backup data: " + e);
+        } catch (IOException e) {
+            Log.e(TAG, "Error parsing the backup data: " + e);
+        }
+        return null;
+    }
+
+    /**
+     * Parses the list of configurations from the provided XML stream.
+     *
+     * @param in            XmlPullParser instance pointing to the XML stream.
+     * @param outerTagDepth depth of the outer tag in the XML document.
+     * @param dataVersion   version number parsed from incoming data.
+     * @return List<WifiConfiguration> object if parsing is successful, null otherwise.
+     */
+    private List<WifiConfiguration> parseNetworkConfigurationsFromXml(
+            XmlPullParser in, int outerTagDepth, int dataVersion)
+            throws XmlPullParserException, IOException {
+        // Find the configuration list section.
+        XmlUtil.gotoNextSectionWithName(in, XML_TAG_SECTION_HEADER_NETWORK_LIST, outerTagDepth);
+        // Find all the configurations within the configuration list section.
+        int networkListTagDepth = outerTagDepth + 1;
+        List<WifiConfiguration> configurations = new ArrayList<>();
+        while (XmlUtil.gotoNextSectionWithNameOrEnd(
+                in, XML_TAG_SECTION_HEADER_NETWORK, networkListTagDepth)) {
+            WifiConfiguration configuration =
+                    parseNetworkConfigurationFromXml(in, dataVersion, networkListTagDepth);
+            if (configuration != null) {
+                Log.v(TAG, "Parsed Configuration: " + configuration.configKey());
+                configurations.add(configuration);
+            }
+        }
+        return configurations;
+    }
+
+    /**
+     * Helper method to parse the WifiConfiguration object and validate the configKey parsed.
+     */
+    private WifiConfiguration parseWifiConfigurationFromXmlAndValidateConfigKey(
+            XmlPullParser in, int outerTagDepth)
+            throws XmlPullParserException, IOException {
+        Pair<String, WifiConfiguration> parsedConfig =
+                WifiConfigurationXmlUtil.parseFromXml(in, outerTagDepth);
+        if (parsedConfig == null || parsedConfig.first == null || parsedConfig.second == null) {
+            return null;
+        }
+        String configKeyParsed = parsedConfig.first;
+        WifiConfiguration configuration = parsedConfig.second;
+        String configKeyCalculated = configuration.configKey();
+        if (!configKeyParsed.equals(configKeyCalculated)) {
+            String configKeyMismatchLog =
+                    "Configuration key does not match. Retrieved: " + configKeyParsed
+                            + ", Calculated: " + configKeyCalculated;
+            if (configuration.shared) {
+                Log.e(TAG, configKeyMismatchLog);
+                return null;
+            } else {
+                // ConfigKey mismatches are expected for private networks because the
+                // UID is not preserved across backup/restore.
+                Log.w(TAG, configKeyMismatchLog);
+            }
+        }
+       return configuration;
+    }
+
+    /**
+     * Parses the configuration data elements from the provided XML stream to a Configuration.
+     *
+     * @param in            XmlPullParser instance pointing to the XML stream.
+     * @param outerTagDepth depth of the outer tag in the XML document.
+     * @param dataVersion   version number parsed from incoming data.
+     * @return WifiConfiguration object if parsing is successful, null otherwise.
+     */
+    private WifiConfiguration parseNetworkConfigurationFromXml(XmlPullParser in, int dataVersion,
+            int outerTagDepth)
+            throws XmlPullParserException, IOException {
+        // Any version migration needs to be handled here in future.
+        if (dataVersion == INITIAL_BACKUP_DATA_VERSION) {
+            WifiConfiguration configuration = null;
+            int networkTagDepth = outerTagDepth + 1;
+            // Retrieve WifiConfiguration object first.
+            XmlUtil.gotoNextSectionWithName(
+                    in, XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION, networkTagDepth);
+            int configTagDepth = networkTagDepth + 1;
+            configuration = parseWifiConfigurationFromXmlAndValidateConfigKey(in, configTagDepth);
+            if (configuration == null) {
+                return null;
+            }
+            // Now retrieve any IP configuration info.
+            XmlUtil.gotoNextSectionWithName(
+                    in, XML_TAG_SECTION_HEADER_IP_CONFIGURATION, networkTagDepth);
+            IpConfiguration ipConfiguration =
+                    IpConfigurationXmlUtil.parseFromXml(in, configTagDepth);
+            configuration.setIpConfiguration(ipConfiguration);
+            return configuration;
+        }
+        return null;
+    }
+
+    /**
+     * Create log dump of the backup data in XML format with the preShared & WEP key masked.
+     *
+     * PSK keys are written in the following format in XML:
+     * <string name="PreSharedKey">WifiBackupRestorePsk</string>
+     *
+     * WEP Keys are written in following format in XML:
+     * <string-array name="WEPKeys" num="4">
+     *  <item value="WifiBackupRestoreWep1" />
+     *  <item value="WifiBackupRestoreWep2" />
+     *  <item value="WifiBackupRestoreWep3" />
+     *  <item value="WifiBackupRestoreWep3" />
+     * </string-array>
+     */
+    private String createLogFromBackupData(byte[] data) {
+        StringBuilder sb = new StringBuilder();
+        try {
+            String xmlString = new String(data, StandardCharsets.UTF_8.name());
+            boolean wepKeysLine = false;
+            for (String line : xmlString.split("\n")) {
+                if (line.matches(PSK_MASK_LINE_MATCH_PATTERN)) {
+                    line = line.replaceAll(PSK_MASK_SEARCH_PATTERN, PSK_MASK_REPLACE_PATTERN);
+                }
+                if (line.matches(WEP_KEYS_MASK_LINE_START_MATCH_PATTERN)) {
+                    wepKeysLine = true;
+                } else if (line.matches(WEP_KEYS_MASK_LINE_END_MATCH_PATTERN)) {
+                    wepKeysLine = false;
+                } else if (wepKeysLine) {
+                    line = line.replaceAll(
+                            WEP_KEYS_MASK_SEARCH_PATTERN, WEP_KEYS_MASK_REPLACE_PATTERN);
+                }
+                sb.append(line).append("\n");
+            }
+        } catch (UnsupportedEncodingException e) {
+            return "";
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Restore state from the older supplicant back up data.
+     * The old backup data was essentially a backup of wpa_supplicant.conf & ipconfig.txt file.
+     *
+     * @param supplicantData Raw byte stream of wpa_supplicant.conf
+     * @param ipConfigData   Raw byte stream of ipconfig.txt
+     * @return list of networks retrieved from the backed up data.
+     */
+    public List<WifiConfiguration> retrieveConfigurationsFromSupplicantBackupData(
+            byte[] supplicantData, byte[] ipConfigData) {
+        if (supplicantData == null || supplicantData.length == 0) {
+            Log.e(TAG, "Invalid supplicant backup data received");
+            return null;
+        }
+
+        if (mVerboseLoggingEnabled) {
+            mDebugLastSupplicantBackupDataRestored = supplicantData;
+        }
+
+        SupplicantBackupMigration.SupplicantNetworks supplicantNetworks =
+                new SupplicantBackupMigration.SupplicantNetworks();
+        // Incorporate the networks present in the backup data.
+        char[] restoredAsChars = new char[supplicantData.length];
+        for (int i = 0; i < supplicantData.length; i++) {
+            restoredAsChars[i] = (char) supplicantData[i];
+        }
+
+        BufferedReader in = new BufferedReader(new CharArrayReader(restoredAsChars));
+        supplicantNetworks.readNetworksFromStream(in);
+
+        // Retrieve corresponding WifiConfiguration objects.
+        List<WifiConfiguration> configurations = supplicantNetworks.retrieveWifiConfigurations();
+
+        // Now retrieve all the IpConfiguration objects and set in the corresponding
+        // WifiConfiguration objects if ipconfig data is present.
+        if (ipConfigData != null && ipConfigData.length != 0) {
+            SparseArray<IpConfiguration> networks =
+                    IpConfigStore.readIpAndProxyConfigurations(
+                            new ByteArrayInputStream(ipConfigData));
+            if (networks != null) {
+                for (int i = 0; i < networks.size(); i++) {
+                    int id = networks.keyAt(i);
+                    for (WifiConfiguration configuration : configurations) {
+                        // This is a dangerous lookup, but that's how it is currently written.
+                        if (configuration.configKey().hashCode() == id) {
+                            configuration.setIpConfiguration(networks.valueAt(i));
+                        }
+                    }
+                }
+            } else {
+                Log.e(TAG, "Failed to parse ipconfig data");
+            }
+        } else {
+            Log.e(TAG, "Invalid ipconfig backup data received");
+        }
+        return configurations;
+    }
+
+    /**
+     * Enable verbose logging.
+     *
+     * @param verbose verbosity level.
+     */
+    public void enableVerboseLogging(int verbose) {
+        mVerboseLoggingEnabled = (verbose > 0);
+        if (!mVerboseLoggingEnabled) {
+            mDebugLastBackupDataRetrieved = null;
+            mDebugLastBackupDataRestored = null;
+            mDebugLastSupplicantBackupDataRestored = null;
+        }
+    }
+
+    /**
+     * Dump out the last backup/restore data if verbose logging is enabled.
+     *
+     * @param fd   unused
+     * @param pw   PrintWriter for writing dump to
+     * @param args unused
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("Dump of WifiBackupRestore");
+        if (mDebugLastBackupDataRetrieved != null) {
+            pw.println("Last backup data retrieved: "
+                    + createLogFromBackupData(mDebugLastBackupDataRetrieved));
+        }
+        if (mDebugLastBackupDataRestored != null) {
+            pw.println("Last backup data restored: "
+                    + createLogFromBackupData(mDebugLastBackupDataRestored));
+        }
+        if (mDebugLastSupplicantBackupDataRestored != null) {
+            pw.println("Last old backup data restored: "
+                    + SupplicantBackupMigration.createLogFromBackupData(
+                            mDebugLastSupplicantBackupDataRestored));
+        }
+    }
+
+    /**
+     * These sub classes contain the logic to parse older backups and restore wifi state from it.
+     * Most of the code here has been migrated over from BackupSettingsAgent.
+     * This is kind of ugly text parsing, but it is needed to support the migration of this data.
+     */
+    public static class SupplicantBackupMigration {
+        /**
+         * List of keys to look out for in wpa_supplicant.conf parsing.
+         * These key values are declared in different parts of the wifi codebase today.
+         */
+        public static final String SUPPLICANT_KEY_SSID = WifiConfiguration.ssidVarName;
+        public static final String SUPPLICANT_KEY_HIDDEN = WifiConfiguration.hiddenSSIDVarName;
+        public static final String SUPPLICANT_KEY_KEY_MGMT = WifiConfiguration.KeyMgmt.varName;
+        public static final String SUPPLICANT_KEY_CLIENT_CERT =
+                WifiEnterpriseConfig.CLIENT_CERT_KEY;
+        public static final String SUPPLICANT_KEY_CA_CERT = WifiEnterpriseConfig.CA_CERT_KEY;
+        public static final String SUPPLICANT_KEY_CA_PATH = WifiEnterpriseConfig.CA_PATH_KEY;
+        public static final String SUPPLICANT_KEY_EAP = WifiEnterpriseConfig.EAP_KEY;
+        public static final String SUPPLICANT_KEY_PSK = WifiConfiguration.pskVarName;
+        public static final String SUPPLICANT_KEY_WEP_KEY0 = WifiConfiguration.wepKeyVarNames[0];
+        public static final String SUPPLICANT_KEY_WEP_KEY1 = WifiConfiguration.wepKeyVarNames[1];
+        public static final String SUPPLICANT_KEY_WEP_KEY2 = WifiConfiguration.wepKeyVarNames[2];
+        public static final String SUPPLICANT_KEY_WEP_KEY3 = WifiConfiguration.wepKeyVarNames[3];
+        public static final String SUPPLICANT_KEY_WEP_KEY_IDX =
+                WifiConfiguration.wepTxKeyIdxVarName;
+        public static final String SUPPLICANT_KEY_ID_STR = WifiSupplicantControl.ID_STRING_VAR_NAME;
+
+        /**
+         * Regex to mask out passwords in backup data dump.
+         */
+        private static final String PSK_MASK_LINE_MATCH_PATTERN =
+                ".*" + SUPPLICANT_KEY_PSK + ".*=.*";
+        private static final String PSK_MASK_SEARCH_PATTERN =
+                "(.*" + SUPPLICANT_KEY_PSK + ".*=)(.*)";
+        private static final String PSK_MASK_REPLACE_PATTERN = "$1*";
+
+        private static final String WEP_KEYS_MASK_LINE_MATCH_PATTERN =
+                ".*" + SUPPLICANT_KEY_WEP_KEY0.replace("0", "") + ".*=.*";
+        private static final String WEP_KEYS_MASK_SEARCH_PATTERN =
+                "(.*" + SUPPLICANT_KEY_WEP_KEY0.replace("0", "") + ".*=)(.*)";
+        private static final String WEP_KEYS_MASK_REPLACE_PATTERN = "$1*";
+
+        /**
+         * Create log dump of the backup data in wpa_supplicant.conf format with the preShared &
+         * WEP key masked.
+         *
+         * PSK keys are written in the following format in wpa_supplicant.conf:
+         *  psk=WifiBackupRestorePsk
+         *
+         * WEP Keys are written in following format in wpa_supplicant.conf:
+         *  wep_keys0=WifiBackupRestoreWep0
+         *  wep_keys1=WifiBackupRestoreWep1
+         *  wep_keys2=WifiBackupRestoreWep2
+         *  wep_keys3=WifiBackupRestoreWep3
+         */
+        public static String createLogFromBackupData(byte[] data) {
+            StringBuilder sb = new StringBuilder();
+            try {
+                String supplicantConfString = new String(data, StandardCharsets.UTF_8.name());
+                for (String line : supplicantConfString.split("\n")) {
+                    if (line.matches(PSK_MASK_LINE_MATCH_PATTERN)) {
+                        line = line.replaceAll(PSK_MASK_SEARCH_PATTERN, PSK_MASK_REPLACE_PATTERN);
+                    }
+                    if (line.matches(WEP_KEYS_MASK_LINE_MATCH_PATTERN)) {
+                        line = line.replaceAll(
+                                WEP_KEYS_MASK_SEARCH_PATTERN, WEP_KEYS_MASK_REPLACE_PATTERN);
+                    }
+                    sb.append(line).append("\n");
+                }
+            } catch (UnsupportedEncodingException e) {
+                return "";
+            }
+            return sb.toString();
+        }
+
+        /**
+         * Class for capturing a network definition from the wifi supplicant config file.
+         */
+        static class SupplicantNetwork {
+            private String mParsedSSIDLine;
+            private String mParsedHiddenLine;
+            private String mParsedKeyMgmtLine;
+            private String mParsedPskLine;
+            private String[] mParsedWepKeyLines = new String[4];
+            private String mParsedWepTxKeyIdxLine;
+            private String mParsedIdStrLine;
+            public boolean certUsed = false;
+            public boolean isEap = false;
+
+            /**
+             * Read lines from wpa_supplicant.conf stream for this network.
+             */
+            public static SupplicantNetwork readNetworkFromStream(BufferedReader in) {
+                final SupplicantNetwork n = new SupplicantNetwork();
+                String line;
+                try {
+                    while (in.ready()) {
+                        line = in.readLine();
+                        if (line == null || line.startsWith("}")) {
+                            break;
+                        }
+                        n.parseLine(line);
+                    }
+                } catch (IOException e) {
+                    return null;
+                }
+                return n;
+            }
+
+            /**
+             * Parse a line from wpa_supplicant.conf stream for this network.
+             */
+            void parseLine(String line) {
+                // Can't rely on particular whitespace patterns so strip leading/trailing.
+                line = line.trim();
+                if (line.isEmpty()) return; // only whitespace; drop the line.
+
+                // Now parse the network block within wpa_supplicant.conf and store the important
+                // lines for procesing later.
+                if (line.startsWith(SUPPLICANT_KEY_SSID)) {
+                    mParsedSSIDLine = line;
+                } else if (line.startsWith(SUPPLICANT_KEY_HIDDEN)) {
+                    mParsedHiddenLine = line;
+                } else if (line.startsWith(SUPPLICANT_KEY_KEY_MGMT)) {
+                    mParsedKeyMgmtLine = line;
+                    if (line.contains("EAP")) {
+                        isEap = true;
+                    }
+                } else if (line.startsWith(SUPPLICANT_KEY_CLIENT_CERT)) {
+                    certUsed = true;
+                } else if (line.startsWith(SUPPLICANT_KEY_CA_CERT)) {
+                    certUsed = true;
+                } else if (line.startsWith(SUPPLICANT_KEY_CA_PATH)) {
+                    certUsed = true;
+                } else if (line.startsWith(SUPPLICANT_KEY_EAP)) {
+                    isEap = true;
+                } else if (line.startsWith(SUPPLICANT_KEY_PSK)) {
+                    mParsedPskLine = line;
+                } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY0)) {
+                    mParsedWepKeyLines[0] = line;
+                } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY1)) {
+                    mParsedWepKeyLines[1] = line;
+                } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY2)) {
+                    mParsedWepKeyLines[2] = line;
+                } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY3)) {
+                    mParsedWepKeyLines[3] = line;
+                } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY_IDX)) {
+                    mParsedWepTxKeyIdxLine = line;
+                } else if (line.startsWith(SUPPLICANT_KEY_ID_STR)) {
+                    mParsedIdStrLine = line;
+                }
+            }
+
+            /**
+             * Create WifiConfiguration object from the parsed data for this network.
+             */
+            public WifiConfiguration createWifiConfiguration() {
+                if (mParsedSSIDLine == null) {
+                    // No SSID => malformed network definition
+                    return null;
+                }
+                WifiConfiguration configuration = new WifiConfiguration();
+                configuration.SSID = mParsedSSIDLine.substring(mParsedSSIDLine.indexOf('=') + 1);
+
+                if (mParsedHiddenLine != null) {
+                    // Can't use Boolean.valueOf() because it works only for true/false.
+                    configuration.hiddenSSID =
+                            Integer.parseInt(mParsedHiddenLine.substring(
+                                    mParsedHiddenLine.indexOf('=') + 1)) != 0;
+                }
+                if (mParsedKeyMgmtLine == null) {
+                    // no key_mgmt line specified; this is defined as equivalent to
+                    // "WPA-PSK WPA-EAP".
+                    configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+                    configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
+                } else {
+                    // Need to parse the mParsedKeyMgmtLine line
+                    final String bareKeyMgmt =
+                            mParsedKeyMgmtLine.substring(mParsedKeyMgmtLine.indexOf('=') + 1);
+                    String[] typeStrings = bareKeyMgmt.split("\\s+");
+
+                    // Parse out all the key management regimes permitted for this network.
+                    // The literal strings here are the standard values permitted in
+                    // wpa_supplicant.conf.
+                    for (int i = 0; i < typeStrings.length; i++) {
+                        final String ktype = typeStrings[i];
+                        if (ktype.equals("NONE")) {
+                            configuration.allowedKeyManagement.set(
+                                    WifiConfiguration.KeyMgmt.NONE);
+                        } else if (ktype.equals("WPA-PSK")) {
+                            configuration.allowedKeyManagement.set(
+                                    WifiConfiguration.KeyMgmt.WPA_PSK);
+                        } else if (ktype.equals("WPA-EAP")) {
+                            configuration.allowedKeyManagement.set(
+                                    WifiConfiguration.KeyMgmt.WPA_EAP);
+                        } else if (ktype.equals("IEEE8021X")) {
+                            configuration.allowedKeyManagement.set(
+                                    WifiConfiguration.KeyMgmt.IEEE8021X);
+                        }
+                    }
+                }
+                if (mParsedPskLine != null) {
+                    configuration.preSharedKey =
+                            mParsedPskLine.substring(mParsedPskLine.indexOf('=') + 1);
+                }
+                if (mParsedWepKeyLines[0] != null) {
+                    configuration.wepKeys[0] =
+                            mParsedWepKeyLines[0].substring(mParsedWepKeyLines[0].indexOf('=') + 1);
+                }
+                if (mParsedWepKeyLines[1] != null) {
+                    configuration.wepKeys[1] =
+                            mParsedWepKeyLines[1].substring(mParsedWepKeyLines[1].indexOf('=') + 1);
+                }
+                if (mParsedWepKeyLines[2] != null) {
+                    configuration.wepKeys[2] =
+                            mParsedWepKeyLines[2].substring(mParsedWepKeyLines[2].indexOf('=') + 1);
+                }
+                if (mParsedWepKeyLines[3] != null) {
+                    configuration.wepKeys[3] =
+                            mParsedWepKeyLines[3].substring(mParsedWepKeyLines[3].indexOf('=') + 1);
+                }
+                if (mParsedWepTxKeyIdxLine != null) {
+                    configuration.wepTxKeyIndex =
+                            Integer.valueOf(mParsedWepTxKeyIdxLine.substring(
+                                    mParsedWepTxKeyIdxLine.indexOf('=') + 1));
+                }
+                if (mParsedIdStrLine != null) {
+                    String idString =
+                            mParsedIdStrLine.substring(mParsedIdStrLine.indexOf('=') + 1);
+                    Map<String, String> extras = WifiNative.parseNetworkExtra(idString);
+                    String configKey = extras.get(WifiSupplicantControl.ID_STRING_KEY_CONFIG_KEY);
+                    if (!configKey.equals(configuration.configKey())) {
+                        // ConfigKey mismatches are expected for private networks because the
+                        // UID is not preserved across backup/restore.
+                        Log.w(TAG, "Configuration key does not match. Retrieved: " + configKey
+                                + ", Calculated: " + configuration.configKey());
+                    }
+                }
+                return configuration;
+            }
+        }
+
+        /**
+         * Ingest multiple wifi config fragments from wpa_supplicant.conf, looking for network={}
+         * blocks and eliminating duplicates
+         */
+        static class SupplicantNetworks {
+            final ArrayList<SupplicantNetwork> mNetworks = new ArrayList<>(8);
+
+            /**
+             * Parse the wpa_supplicant.conf file stream and add networks.
+             */
+            public void readNetworksFromStream(BufferedReader in) {
+                try {
+                    String line;
+                    while (in.ready()) {
+                        line = in.readLine();
+                        if (line != null) {
+                            if (line.startsWith("network")) {
+                                SupplicantNetwork net = SupplicantNetwork.readNetworkFromStream(in);
+                                // Networks that use certificates for authentication can't be
+                                // restored because the certificates they need don't get restored
+                                // (because they are stored in keystore, and can't be restored).
+                                // Similarly, omit EAP network definitions to avoid propagating
+                                // controlled enterprise network definitions.
+                                if (net.isEap || net.certUsed) {
+                                    Log.d(TAG, "Skipping enterprise network for restore: "
+                                            + net.mParsedSSIDLine + " / " + net.mParsedKeyMgmtLine);
+                                    continue;
+                                }
+                                mNetworks.add(net);
+                            }
+                        }
+                    }
+                } catch (IOException e) {
+                    // whatever happened, we're done now
+                }
+            }
+
+            /**
+             * Retrieve a list of WifiConfiguration objects parsed from wpa_supplicant.conf
+             */
+            public List<WifiConfiguration> retrieveWifiConfigurations() {
+                ArrayList<WifiConfiguration> wifiConfigurations = new ArrayList<>();
+                for (SupplicantNetwork net : mNetworks) {
+                    WifiConfiguration wifiConfiguration = net.createWifiConfiguration();
+                    if (wifiConfiguration != null) {
+                        Log.v(TAG, "Parsed Configuration: " + wifiConfiguration.configKey());
+                        wifiConfigurations.add(wifiConfiguration);
+                    }
+                }
+                return wifiConfigurations;
+            }
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/WifiConfigManager.java b/service/java/com/android/server/wifi/WifiConfigManager.java
index c1a334a..1e288ef 100644
--- a/service/java/com/android/server/wifi/WifiConfigManager.java
+++ b/service/java/com/android/server/wifi/WifiConfigManager.java
@@ -45,7 +45,6 @@
 import android.net.wifi.WpsResult;
 import android.os.Environment;
 import android.os.RemoteException;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -151,8 +150,6 @@
  *
  */
 public class WifiConfigManager {
-    private static boolean sVDBG = false;
-    private static boolean sVVDBG = false;
     public static final String TAG = "WifiConfigManager";
     public static final int MAX_TX_PACKET_FOR_FULL_SCANS = 8;
     public static final int MAX_RX_PACKET_FOR_FULL_SCANS = 16;
@@ -185,6 +182,11 @@
     private static final int DEFAULT_MAX_DHCP_RETRIES = 9;
 
     /**
+     * Maximum age of scan results that can be used for averaging out RSSI value.
+     */
+    private static final int SCAN_RESULT_MAXIMUM_AGE_MS = 40000;
+
+    /**
      * The threshold for each kind of error. If a network continuously encounter the same error more
      * than the threshold times, this network will be disabled. -1 means unavailable.
      */
@@ -195,10 +197,12 @@
             5,  //  threshold for DISABLED_AUTHENTICATION_FAILURE
             5,  //  threshold for DISABLED_DHCP_FAILURE
             5,  //  threshold for DISABLED_DNS_FAILURE
+            1,  //  threshold for DISABLED_WPS_START
             6,  //  threshold for DISABLED_TLS_VERSION_MISMATCH
             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_WIFI_MANAGER
+            1   //  threshold for DISABLED_BY_USER_SWITCH
     };
 
     /**
@@ -211,10 +215,12 @@
             5,                  // threshold for DISABLED_AUTHENTICATION_FAILURE
             5,                  // threshold for DISABLED_DHCP_FAILURE
             5,                  // threshold for DISABLED_DNS_FAILURE
+            0,                  // threshold for DISABLED_WPS_START
             Integer.MAX_VALUE,  // threshold for DISABLED_TLS_VERSION
             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_WIFI_MANAGER
+            Integer.MAX_VALUE   // threshold for DISABLED_BY_USER_SWITCH
     };
 
     public final AtomicBoolean mEnableAutoJoinWhenAssociated = new AtomicBoolean();
@@ -222,7 +228,6 @@
     public final AtomicBoolean mEnableRssiPollWhenAssociated = new AtomicBoolean(true);
     public final AtomicInteger mThresholdSaturatedRssi5 = new AtomicInteger();
     public final AtomicInteger mThresholdQualifiedRssi24 = new AtomicInteger();
-    public final AtomicInteger mEnableVerboseLogging = new AtomicInteger(0);
     public final AtomicInteger mAlwaysEnableScansWhileAssociated = new AtomicInteger(0);
     public final AtomicInteger mMaxNumActiveChannelsForPartialScans = new AtomicInteger();
 
@@ -255,6 +260,10 @@
      * The SSIDs are encoded in a String as per definition of WifiConfiguration.SSID field.
      */
     public Set<String> mDeletedEphemeralSSIDs = new HashSet<String>();
+    /**
+     * List of blacklisted BSSIDs.
+     */
+    private final HashSet<String> mBssidBlacklist = new HashSet<String>();
 
     /* configured networks with network id as the key */
     private final ConfigurationMap mConfiguredNetworks;
@@ -262,7 +271,8 @@
     private final LocalLog mLocalLog;
     private final KeyStore mKeyStore;
     private final WifiNetworkHistory mWifiNetworkHistory;
-    private final WifiConfigStore mWifiConfigStore;
+    private final WifiSupplicantControl mWifiSupplicantControl;
+    private final WifiKeyStore mWifiKeyStore;
     private final AnqpCache mAnqpCache;
     private final SupplicantBridge mSupplicantBridge;
     private final SupplicantBridgeCallbacks mSupplicantBridgeCallbacks;
@@ -270,15 +280,15 @@
     private final boolean mEnableOsuQueries;
     private final SIMAccessor mSIMAccessor;
     private final UserManager mUserManager;
-    private final Object mActiveScanDetailLock = new Object();
+    private final BackupManagerProxy mBackupManagerProxy;
 
+    private boolean mVerboseLoggingEnabled = false;
     private Context mContext;
     private FrameworkFacade mFacade;
     private Clock mClock;
     private IpConfigStore mIpconfigStore;
     private DelayedDiskWrite mWriter;
     private boolean mOnlyLinkSameCredentialConfigurations;
-    private boolean mShowNetworks = false;
     private int mCurrentUserId = UserHandle.USER_SYSTEM;
 
     /* Stores a map of NetworkId to ScanCache */
@@ -304,7 +314,8 @@
      */
     private HashSet<String> mLostConfigsDbg = new HashSet<String>();
 
-    private ScanDetail mActiveScanDetail;   // ScanDetail associated with active network
+    // NetworkDetail associated with the last selected Passpoint network.
+    private NetworkDetail mSelectedPasspointNetwork;
 
     private class SupplicantBridgeCallbacks implements SupplicantBridge.SupplicantBridgeCallbacks {
         @Override
@@ -339,12 +350,7 @@
         mClock = clock;
         mKeyStore = keyStore;
         mUserManager = userManager;
-
-        if (mShowNetworks) {
-            mLocalLog = wifiNative.getLocalLog();
-        } else {
-            mLocalLog = null;
-        }
+        mLocalLog = wifiNative.getLocalLog();
 
         mOnlyLinkSameCredentialConfigurations = mContext.getResources().getBoolean(
                 R.bool.config_wifi_only_link_same_credential_configurations);
@@ -396,28 +402,27 @@
         mSIMAccessor = new SIMAccessor(mContext);
         mWriter = new DelayedDiskWrite();
         mIpconfigStore = new IpConfigStore(mWriter);
-        mWifiNetworkHistory = new WifiNetworkHistory(context, mLocalLog, mWriter);
-        mWifiConfigStore =
-                new WifiConfigStore(context, wifiNative, mKeyStore, mLocalLog, mShowNetworks, true);
+        mWifiNetworkHistory = new WifiNetworkHistory(mContext, mLocalLog, mWriter);
+        mWifiSupplicantControl = new WifiSupplicantControl(mContext, wifiNative, mLocalLog);
+        mWifiKeyStore = new WifiKeyStore(keyStore);
+        mBackupManagerProxy = new BackupManagerProxy();
     }
 
     public void trimANQPCache(boolean all) {
         mAnqpCache.clear(all, DBG);
     }
 
+    /**
+     * Enable verbose logging.
+     */
     void enableVerboseLogging(int verbose) {
-        mEnableVerboseLogging.set(verbose);
         if (verbose > 0) {
-            sVDBG = true;
-            mShowNetworks = true;
+            mVerboseLoggingEnabled = true;
         } else {
-            sVDBG = false;
+            mVerboseLoggingEnabled = false;
         }
-        if (verbose > 1) {
-            sVVDBG = true;
-        } else {
-            sVVDBG = false;
-        }
+        mWifiSupplicantControl.enableVerboseLogging(mVerboseLoggingEnabled);
+        mWifiKeyStore.enableVerboseLogging(mVerboseLoggingEnabled);
     }
 
     /**
@@ -543,50 +548,6 @@
     }
 
     /**
-     * Fetch the list of currently saved networks (i.e. all configured networks, excluding
-     * ephemeral networks) that were recently seen.
-     *
-     * @param scanResultAgeMs The maximum age (in ms) of scan results for which we calculate the
-     * RSSI values
-     * @param copy If true, the returned list will contain copies of the configurations for the
-     * saved networks. Otherwise, the returned list will contain references to these
-     * configurations.
-     * @return List of networks
-     */
-    List<WifiConfiguration> getRecentSavedNetworks(int scanResultAgeMs, boolean copy) {
-        List<WifiConfiguration> networks = new ArrayList<WifiConfiguration>();
-
-        for (WifiConfiguration config : mConfiguredNetworks.valuesForCurrentUser()) {
-            if (config.ephemeral) {
-                // Do not enumerate and return this configuration to anyone (e.g. WiFi Picker);
-                // treat it as unknown instead. This configuration can still be retrieved
-                // directly by its key or networkId.
-                continue;
-            }
-
-            // Calculate the RSSI for scan results that are more recent than scanResultAgeMs.
-            ScanDetailCache cache = getScanDetailCache(config);
-            if (cache == null) {
-                continue;
-            }
-            config.setVisibility(cache.getVisibility(scanResultAgeMs));
-            if (config.visibility == null) {
-                continue;
-            }
-            if (config.visibility.rssi5 == WifiConfiguration.INVALID_RSSI
-                    && config.visibility.rssi24 == WifiConfiguration.INVALID_RSSI) {
-                continue;
-            }
-            if (copy) {
-                networks.add(new WifiConfiguration(config));
-            } else {
-                networks.add(config);
-            }
-        }
-        return networks;
-    }
-
-    /**
      *  Update the configuration and BSSID with latest RSSI value.
      */
     void updateConfiguration(WifiInfo info) {
@@ -603,9 +564,8 @@
                 result.level = info.getRssi();
 
                 // Average the RSSI value
-                result.averageRssi(previousRssi, previousSeen,
-                        WifiQualifiedNetworkSelector.SCAN_RESULT_MAXIMUNM_AGE);
-                if (sVDBG) {
+                result.averageRssi(previousRssi, previousSeen, SCAN_RESULT_MAXIMUM_AGE_MS);
+                if (mVerboseLoggingEnabled) {
                     logd("updateConfiguration freq=" + result.frequency
                             + " BSSID=" + result.BSSID
                             + " RSSI=" + result.level
@@ -655,11 +615,11 @@
     }
 
     private boolean setNetworkPriorityNative(WifiConfiguration config, int priority) {
-        return mWifiConfigStore.setNetworkPriority(config, priority);
+        return mWifiSupplicantControl.setNetworkPriority(config, priority);
     }
 
     private boolean setSSIDNative(WifiConfiguration config, String ssid) {
-        return mWifiConfigStore.setNetworkSSID(config, ssid);
+        return mWifiSupplicantControl.setNetworkSSID(config, ssid);
     }
 
     public boolean updateLastConnectUid(WifiConfiguration config, int uid) {
@@ -686,7 +646,7 @@
      * @return false if the network id is invalid
      */
     boolean selectNetwork(WifiConfiguration config, boolean updatePriorities, int uid) {
-        if (sVDBG) localLogNetwork("selectNetwork", config.networkId);
+        if (mVerboseLoggingEnabled) localLogNetwork("selectNetwork", config.networkId);
         if (config.networkId == INVALID_NETWORK_ID) return false;
         if (!WifiConfigurationUtil.isVisibleToAnyProfile(config,
                 mUserManager.getProfiles(mCurrentUserId))) {
@@ -712,6 +672,7 @@
             setNetworkPriorityNative(config, ++mLastPriority);
         }
 
+        NetworkDetail selectedPasspointNetwork = null;
         if (config.isPasspoint()) {
             // Set the SSID for the underlying WPA supplicant network entry corresponding to this
             // Passpoint profile to the SSID of the BSS selected by QNS. |config.SSID| is set by
@@ -720,9 +681,18 @@
             logd("Setting SSID for WPA supplicant network " + config.networkId + " to "
                     + config.SSID);
             setSSIDNative(config, config.SSID);
+            String selectedPasspointNetworkBssid = mWifiSupplicantControl.getNetworkBSSID(config);
+            if (selectedPasspointNetworkBssid != null) {
+                ScanDetail result =
+                        getScanDetailCache(config).getScanDetail(selectedPasspointNetworkBssid);
+                if (result != null) {
+                    selectedPasspointNetwork = result.getNetworkDetail();
+                }
+            }
         }
+        mSelectedPasspointNetwork = selectedPasspointNetwork;
 
-        mWifiConfigStore.enableHS20(config.isPasspoint());
+        mWifiSupplicantControl.enableHS20(config.isPasspoint());
 
         if (updatePriorities) {
             saveConfig();
@@ -759,8 +729,10 @@
             return new NetworkUpdateResult(INVALID_NETWORK_ID);
         }
 
-        if (sVDBG) localLogNetwork("WifiConfigManager: saveNetwork netId", config.networkId);
-        if (sVDBG) {
+        if (mVerboseLoggingEnabled) {
+            localLogNetwork("WifiConfigManager: saveNetwork netId", config.networkId);
+        }
+        if (mVerboseLoggingEnabled) {
             logd("WifiConfigManager saveNetwork,"
                     + " size=" + Integer.toString(mConfiguredNetworks.sizeForAllUsers())
                     + " (for all users)"
@@ -770,7 +742,7 @@
         }
 
         if (mDeletedEphemeralSSIDs.remove(config.SSID)) {
-            if (sVDBG) {
+            if (mVerboseLoggingEnabled) {
                 logd("WifiConfigManager: removed from ephemeral blacklist: " + config.SSID);
             }
             // NOTE: This will be flushed to disk as part of the addOrUpdateNetworkNative call
@@ -781,18 +753,22 @@
         NetworkUpdateResult result = addOrUpdateNetworkNative(config, uid);
         int netId = result.getNetworkId();
 
-        if (sVDBG) localLogNetwork("WifiConfigManager: saveNetwork got it back netId=", netId);
+        if (mVerboseLoggingEnabled) {
+            localLogNetwork("WifiConfigManager: saveNetwork got it back netId=", netId);
+        }
 
         conf = mConfiguredNetworks.getForCurrentUser(netId);
         if (conf != null) {
             if (!conf.getNetworkSelectionStatus().isNetworkEnabled()) {
-                if (sVDBG) localLog("WifiConfigManager: re-enabling: " + conf.SSID);
+                if (mVerboseLoggingEnabled) {
+                    localLog("WifiConfigManager: re-enabling: " + conf.SSID);
+                }
 
                 // reenable autojoin, since new information has been provided
                 updateNetworkSelectionStatus(netId,
                         WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
             }
-            if (sVDBG) {
+            if (mVerboseLoggingEnabled) {
                 logd("WifiConfigManager: saveNetwork got config back netId="
                         + Integer.toString(netId)
                         + " uid=" + Integer.toString(config.creatorUid));
@@ -810,7 +786,7 @@
 
     void noteRoamingFailure(WifiConfiguration config, int reason) {
         if (config == null) return;
-        config.lastRoamingFailure = mClock.currentTimeMillis();
+        config.lastRoamingFailure = mClock.getWallClockMillis();
         config.roamingFailureBlackListTimeMilli =
                 2 * (config.roamingFailureBlackListTimeMilli + 1000);
         if (config.roamingFailureBlackListTimeMilli > mNetworkSwitchingBlackListPeriodMs) {
@@ -820,7 +796,7 @@
     }
 
     void saveWifiConfigBSSID(WifiConfiguration config, String bssid) {
-        mWifiConfigStore.setNetworkBSSID(config, bssid);
+        mWifiSupplicantControl.setNetworkBSSID(config, bssid);
     }
 
 
@@ -836,6 +812,7 @@
                             WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
                     break;
                 case DISCONNECTED:
+                    mSelectedPasspointNetwork = null;
                     //If network is already disabled, keep the status
                     if (config.status == Status.CURRENT) {
                         config.status = Status.ENABLED;
@@ -885,7 +862,7 @@
      * @return {@code true} if it succeeds, {@code false} otherwise
      */
     boolean forgetNetwork(int netId) {
-        if (mShowNetworks) localLogNetwork("forgetNetwork", netId);
+        if (mVerboseLoggingEnabled) localLogNetwork("forgetNetwork", netId);
         if (!removeNetwork(netId)) {
             loge("Failed to forget network " + netId);
             return false;
@@ -910,7 +887,7 @@
             return WifiConfiguration.INVALID_NETWORK_ID;
         }
 
-        if (mShowNetworks) localLogNetwork("addOrUpdateNetwork id=", config.networkId);
+        if (mVerboseLoggingEnabled) localLogNetwork("addOrUpdateNetwork id=", config.networkId);
         if (config.isPasspoint()) {
             /* create a temporary SSID with providerFriendlyName */
             Long csum = getChecksum(config.FQDN);
@@ -955,11 +932,8 @@
     }
 
     public int matchProviderWithCurrentNetwork(String fqdn) {
-        ScanDetail scanDetail = null;
-        synchronized (mActiveScanDetailLock) {
-            scanDetail = mActiveScanDetail;
-        }
-        if (scanDetail == null) {
+        NetworkDetail selectedPasspointNetwork = mSelectedPasspointNetwork;
+        if (selectedPasspointNetwork == null) {
             return PasspointMatch.None.ordinal();
         }
         HomeSP homeSP = mMOManager.getHomeSP(fqdn);
@@ -967,12 +941,15 @@
             return PasspointMatch.None.ordinal();
         }
 
-        ANQPData anqpData = mAnqpCache.getEntry(scanDetail.getNetworkDetail());
+        Map<Constants.ANQPElementType, ANQPElement> anqpElements
+                = selectedPasspointNetwork.getANQPElements();
 
-        Map<Constants.ANQPElementType, ANQPElement> anqpElements =
-                anqpData != null ? anqpData.getANQPElements() : null;
+        if (anqpElements == null || anqpElements.isEmpty()) {
+            ANQPData anqpData = mAnqpCache.getEntry(selectedPasspointNetwork);
+            anqpElements = anqpData != null ? anqpData.getANQPElements() : null;
+        }
 
-        return homeSP.match(scanDetail.getNetworkDetail(), anqpElements, mSIMAccessor).ordinal();
+        return homeSP.match(selectedPasspointNetwork, anqpElements, mSIMAccessor).ordinal();
     }
 
     /**
@@ -1149,7 +1126,7 @@
      * @return {@code true} if it succeeds, {@code false} otherwise
      */
     boolean removeNetwork(int netId) {
-        if (mShowNetworks) localLogNetwork("removeNetwork", netId);
+        if (mVerboseLoggingEnabled) localLogNetwork("removeNetwork", netId);
         WifiConfiguration config = mConfiguredNetworks.getForCurrentUser(netId);
         if (!removeConfigAndSendBroadcastIfNeeded(config)) {
             return false;
@@ -1170,7 +1147,7 @@
         if (config == null) {
             return false;
         }
-        if (!mWifiConfigStore.removeNetwork(config)) {
+        if (!removeNetworkNative(config)) {
             loge("Failed to remove network " + config.networkId);
             return false;
         }
@@ -1187,7 +1164,7 @@
             return false;
         }
         String key = config.configKey();
-        if (sVDBG) {
+        if (mVerboseLoggingEnabled) {
             logd("removeNetwork " + " key=" + key + " config.id=" + config.networkId);
         }
         writeIpAndProxyConfigurations();
@@ -1238,7 +1215,7 @@
             if (app.uid != config.creatorUid || !app.packageName.equals(config.creatorName)) {
                 continue;
             }
-            if (mShowNetworks) {
+            if (mVerboseLoggingEnabled) {
                 localLog("Removing network " + config.SSID
                          + ", application \"" + app.packageName + "\" uninstalled"
                          + " from user " + UserHandle.getUserId(app.uid));
@@ -1261,7 +1238,7 @@
                 continue;
             }
             success &= removeNetwork(config.networkId);
-            if (mShowNetworks) {
+            if (mVerboseLoggingEnabled) {
                 localLog("Removing network " + config.SSID
                         + ", user " + userId + " removed");
             }
@@ -1283,54 +1260,61 @@
         if (config == null) {
             return false;
         }
-
         updateNetworkSelectionStatus(
                 config, WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
         setLatestUserSelectedConfiguration(config);
         boolean ret = true;
         if (disableOthers) {
             ret = selectNetworkWithoutBroadcast(config.networkId);
-            if (sVDBG) {
-                localLogNetwork("enableNetwork(disableOthers=true, uid=" + uid + ") ",
-                        config.networkId);
+            if (ret) {
+                if (mVerboseLoggingEnabled) {
+                    localLogNetwork("enableNetwork(disableOthers=true, uid=" + uid + ") ",
+                            config.networkId);
+                }
+                markAllNetworksDisabledExcept(config.networkId,
+                        WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WIFI_MANAGER);
+                updateLastConnectUid(config, uid);
+                writeKnownNetworkHistory();
+                sendConfiguredNetworksChangedBroadcast();
             }
-            updateLastConnectUid(config, uid);
-            writeKnownNetworkHistory();
-            sendConfiguredNetworksChangedBroadcast();
         } else {
-            if (sVDBG) localLogNetwork("enableNetwork(disableOthers=false) ", config.networkId);
+            if (mVerboseLoggingEnabled) {
+                localLogNetwork("enableNetwork(disableOthers=false) ", config.networkId);
+            }
             sendConfiguredNetworksChangedBroadcast(config, WifiManager.CHANGE_REASON_CONFIG_CHANGE);
         }
         return ret;
     }
 
     boolean selectNetworkWithoutBroadcast(int netId) {
-        return mWifiConfigStore.selectNetwork(
-                mConfiguredNetworks.getForCurrentUser(netId),
-                mConfiguredNetworks.valuesForCurrentUser());
+        return mWifiSupplicantControl.selectNetwork(mConfiguredNetworks.getForCurrentUser(netId));
     }
 
     /**
      * Disable a network in wpa_supplicant.
      */
     boolean disableNetworkNative(WifiConfiguration config) {
-        return mWifiConfigStore.disableNetwork(config);
+        return mWifiSupplicantControl.disableNetwork(config);
     }
 
     /**
      * Disable all networks in wpa_supplicant.
      */
     void disableAllNetworksNative() {
-        mWifiConfigStore.disableAllNetworks(mConfiguredNetworks.valuesForCurrentUser());
+        mWifiSupplicantControl.disableAllNetworks(mConfiguredNetworks.valuesForCurrentUser());
     }
 
-    /**
-     * Disable a network. Note that there is no saveConfig operation.
-     * @param netId network to be disabled
-     * @return {@code true} if it succeeds, {@code false} otherwise
-     */
-    boolean disableNetwork(int netId) {
-        return mWifiConfigStore.disableNetwork(mConfiguredNetworks.getForCurrentUser(netId));
+    /* Mark all networks except specified netId as disabled */
+    private void markAllNetworksDisabledExcept(int netId, int disableReason) {
+        for (WifiConfiguration config : mConfiguredNetworks.valuesForCurrentUser()) {
+            if (config != null && config.networkId != netId) {
+                updateNetworkSelectionStatus(config, disableReason);
+            }
+        }
+    }
+
+    private void markAllNetworksDisabled(int disableReason) {
+        markAllNetworksDisabledExcept(WifiConfiguration.INVALID_NETWORK_ID, disableReason);
     }
 
     /**
@@ -1381,11 +1365,13 @@
     }
 
     /**
-     * Check the config. If it is temporarily disabled, check the disable time is expired or not, If
-     * expired, enabled it again for qualified network selection.
+     * Attempt to re-enable a network for qualified network selection, if this network was either:
+     *      a) Previously temporarily disabled, but its disable timeout has expired, or
+     *      b) Previously disabled because of a user switch, but is now visible to the current
+     *         user.
      * @param networkId the id of the network to be checked for possible unblock (due to timeout)
-     * @return true if network status has been changed
-     *         false network status is not changed
+     * @return true if the network identified by {@param networkId} was re-enabled for qualified
+     *         network selection, false otherwise.
      */
     public boolean tryEnableQualifiedNetwork(int networkId) {
         WifiConfiguration config = getWifiConfiguration(networkId);
@@ -1397,25 +1383,33 @@
     }
 
     /**
-     * Check the config. If it is temporarily disabled, check the disable is expired or not, If
-     * expired, enabled it again for qualified network selection.
-     * @param config network to be checked for possible unblock (due to timeout)
-     * @return true if network status has been changed
-     *         false network status is not changed
+     * Attempt to re-enable a network for qualified network selection, if this network was either:
+     *      a) Previously temporarily disabled, but its disable timeout has expired, or
+     *      b) Previously disabled because of a user switch, but is now visible to the current
+     *         user.
+     * @param config configuration for the network to be re-enabled for network selection. The
+     *               network corresponding to the config must be visible to the current user.
+     * @return true if the network identified by {@param config} was re-enabled for qualified
+     *         network selection, false otherwise.
      */
     private boolean tryEnableQualifiedNetwork(WifiConfiguration config) {
         WifiConfiguration.NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
         if (networkStatus.isNetworkTemporaryDisabled()) {
             //time difference in minutes
             long timeDifference =
-                    (mClock.elapsedRealtime() - networkStatus.getDisableTime()) / 1000 / 60;
+                    (mClock.getElapsedSinceBootMillis() - networkStatus.getDisableTime()) / 1000 / 60;
             if (timeDifference < 0 || timeDifference
                     >= NETWORK_SELECTION_DISABLE_TIMEOUT[
                     networkStatus.getNetworkSelectionDisableReason()]) {
                 updateNetworkSelectionStatus(config.networkId,
-                        networkStatus.NETWORK_SELECTION_ENABLE);
+                        WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
                 return true;
             }
+        } else if (networkStatus.isDisabledByReason(
+                WifiConfiguration.NetworkSelectionStatus.DISABLED_DUE_TO_USER_SWITCH)) {
+            updateNetworkSelectionStatus(config.networkId,
+                    WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
+            return true;
         }
         return false;
     }
@@ -1436,15 +1430,14 @@
         WifiConfiguration.NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
         if (reason < 0 || reason >= WifiConfiguration.NetworkSelectionStatus
                 .NETWORK_SELECTION_DISABLED_MAX) {
-            localLog("Invalid Network disable reason:" + reason);
+            localLog("Invalid Network disable reason: " + reason);
             return false;
         }
 
         if (reason == WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE) {
             if (networkStatus.isNetworkEnabled()) {
                 if (DBG) {
-                    localLog("Need not change Qualified network Selection status since"
-                            + " already enabled");
+                    localLog("Do nothing. Network is already enabled!");
                 }
                 return false;
             }
@@ -1455,17 +1448,13 @@
                     WifiConfiguration.NetworkSelectionStatus
                     .INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP);
             networkStatus.clearDisableReasonCounter();
-            String disableTime = DateFormat.getDateTimeInstance().format(new Date());
-            if (DBG) {
-                localLog("Re-enable network: " + config.SSID + " at " + disableTime);
-            }
+            config.status = Status.ENABLED;
             sendConfiguredNetworksChangedBroadcast(config, WifiManager.CHANGE_REASON_CONFIG_CHANGE);
         } else {
             //disable the network
             if (networkStatus.isNetworkPermanentlyDisabled()) {
-                //alreay permanent disable
                 if (DBG) {
-                    localLog("Do nothing. Alreay permanent disabled! "
+                    localLog("Do nothing. Network is already permanently disabled! Disable reason: "
                             + WifiConfiguration.NetworkSelectionStatus
                             .getNetworkDisableReasonString(reason));
                 }
@@ -1473,38 +1462,32 @@
             } else if (networkStatus.isNetworkTemporaryDisabled()
                     && reason < WifiConfiguration.NetworkSelectionStatus
                     .DISABLED_TLS_VERSION_MISMATCH) {
-                //alreay temporarily disable
                 if (DBG) {
-                    localLog("Do nothing. Already temporarily disabled! "
+                    localLog("Do nothing. Network is already temporarily disabled! Disable reason: "
                             + WifiConfiguration.NetworkSelectionStatus
                             .getNetworkDisableReasonString(reason));
                 }
                 return false;
             }
-
-            if (networkStatus.isNetworkEnabled()) {
-                disableNetworkNative(config);
-                sendConfiguredNetworksChangedBroadcast(config,
-                        WifiManager.CHANGE_REASON_CONFIG_CHANGE);
-                localLog("Disable network " + config.SSID + " reason:"
-                        + WifiConfiguration.NetworkSelectionStatus
-                        .getNetworkDisableReasonString(reason));
-            }
+            disableNetworkNative(config);
             if (reason < WifiConfiguration.NetworkSelectionStatus.DISABLED_TLS_VERSION_MISMATCH) {
                 networkStatus.setNetworkSelectionStatus(WifiConfiguration.NetworkSelectionStatus
                         .NETWORK_SELECTION_TEMPORARY_DISABLED);
-                networkStatus.setDisableTime(mClock.elapsedRealtime());
+                networkStatus.setDisableTime(mClock.getElapsedSinceBootMillis());
             } else {
                 networkStatus.setNetworkSelectionStatus(WifiConfiguration.NetworkSelectionStatus
                         .NETWORK_SELECTION_PERMANENTLY_DISABLED);
+                config.status = Status.DISABLED;
+                sendConfiguredNetworksChangedBroadcast(
+                        config, WifiManager.CHANGE_REASON_CONFIG_CHANGE);
             }
             networkStatus.setNetworkSelectionDisableReason(reason);
-            if (DBG) {
-                String disableTime = DateFormat.getDateTimeInstance().format(new Date());
-                localLog("Network:" + config.SSID + "Configure new status:"
-                        + networkStatus.getNetworkStatusString() + " with reason:"
-                        + networkStatus.getNetworkDisableReasonString() + " at: " + disableTime);
-            }
+        }
+        if (DBG) {
+            String time = DateFormat.getDateTimeInstance().format(new Date());
+            localLog("Network: " + config.SSID + " Configure new status: "
+                    + networkStatus.getNetworkStatusString() + " with reason: "
+                    + networkStatus.getNetworkDisableReasonString() + " at: " + time);
         }
         return true;
     }
@@ -1514,7 +1497,7 @@
      * @return {@code true} if it succeeds, {@code false} otherwise
      */
     boolean saveConfig() {
-        return mWifiConfigStore.saveConfig();
+        return mWifiSupplicantControl.saveConfig();
     }
 
     /**
@@ -1524,8 +1507,12 @@
      * @return Wps result containing status and pin
      */
     WpsResult startWpsWithPinFromAccessPoint(WpsInfo config) {
-        return mWifiConfigStore.startWpsWithPinFromAccessPoint(
-                config, mConfiguredNetworks.valuesForCurrentUser());
+        WpsResult result = mWifiSupplicantControl.startWpsWithPinFromAccessPoint(config);
+        /* WPS leaves all networks disabled */
+        if (result.status == WpsResult.Status.SUCCESS) {
+            markAllNetworksDisabled(WifiConfiguration.NetworkSelectionStatus.DISABLED_WPS_START);
+        }
+        return result;
     }
 
     /**
@@ -1534,8 +1521,12 @@
      * @return WpsResult indicating status and pin
      */
     WpsResult startWpsWithPinFromDevice(WpsInfo config) {
-        return mWifiConfigStore.startWpsWithPinFromDevice(
-            config, mConfiguredNetworks.valuesForCurrentUser());
+        WpsResult result = mWifiSupplicantControl.startWpsWithPinFromDevice(config);
+        /* WPS leaves all networks disabled */
+        if (result.status == WpsResult.Status.SUCCESS) {
+            markAllNetworksDisabled(WifiConfiguration.NetworkSelectionStatus.DISABLED_WPS_START);
+        }
+        return result;
     }
 
     /**
@@ -1544,8 +1535,12 @@
      * @return WpsResult indicating status and pin
      */
     WpsResult startWpsPbc(WpsInfo config) {
-        return mWifiConfigStore.startWpsPbc(
-            config, mConfiguredNetworks.valuesForCurrentUser());
+        WpsResult result = mWifiSupplicantControl.startWpsPbc(config);
+        /* WPS leaves all networks disabled */
+        if (result.status == WpsResult.Status.SUCCESS) {
+            markAllNetworksDisabled(WifiConfiguration.NetworkSelectionStatus.DISABLED_WPS_START);
+        }
+        return result;
     }
 
     /**
@@ -1647,7 +1642,7 @@
 
         final Map<String, WifiConfiguration> configs = new HashMap<>();
         final SparseArray<Map<String, String>> networkExtras = new SparseArray<>();
-        mLastPriority = mWifiConfigStore.loadNetworks(configs, networkExtras);
+        mLastPriority = mWifiSupplicantControl.loadNetworks(configs, networkExtras);
 
         readNetworkHistory(configs);
         readPasspointConfig(configs, networkExtras);
@@ -1658,15 +1653,16 @@
         // 2) mConfiguredNetworks caches a Passpoint network's FQDN the moment the network is added.
         //    Thus, we had to load the FQDNs first.
         mConfiguredNetworks.clear();
+        mScanDetailCaches.clear();
         for (Map.Entry<String, WifiConfiguration> entry : configs.entrySet()) {
             final String configKey = entry.getKey();
             final WifiConfiguration config = entry.getValue();
             if (!configKey.equals(config.configKey())) {
-                if (mShowNetworks) {
+                if (mVerboseLoggingEnabled) {
                     log("Ignoring network " + config.networkId + " because the configKey loaded "
                             + "from wpa_supplicant.conf is not valid.");
                 }
-                mWifiConfigStore.removeNetwork(config);
+                removeNetworkNative(config);
                 continue;
             }
             mConfiguredNetworks.put(config);
@@ -1676,7 +1672,7 @@
 
         sendConfiguredNetworksChangedBroadcast();
 
-        if (mShowNetworks) {
+        if (mVerboseLoggingEnabled) {
             localLog("loadConfiguredNetworks loaded " + mConfiguredNetworks.sizeForAllUsers()
                     + " networks (for all users)");
         }
@@ -1684,8 +1680,8 @@
         if (mConfiguredNetworks.sizeForAllUsers() == 0) {
             // no networks? Lets log if the file contents
             logKernelTime();
-            logContents(WifiConfigStore.SUPPLICANT_CONFIG_FILE);
-            logContents(WifiConfigStore.SUPPLICANT_CONFIG_FILE_BACKUP);
+            logContents(WifiSupplicantControl.SUPPLICANT_CONFIG_FILE);
+            logContents(WifiSupplicantControl.SUPPLICANT_CONFIG_FILE_BACKUP);
             logContents(WifiNetworkHistory.NETWORK_HISTORY_CONFIG_FILE);
         }
     }
@@ -1717,15 +1713,16 @@
     }
 
     private Map<String, String> readNetworkVariablesFromSupplicantFile(String key) {
-        return mWifiConfigStore.readNetworkVariablesFromSupplicantFile(key);
+        return mWifiSupplicantControl.readNetworkVariablesFromSupplicantFile(key);
     }
 
     private String readNetworkVariableFromSupplicantFile(String configKey, String key) {
-        long start = SystemClock.elapsedRealtimeNanos();
-        Map<String, String> data = mWifiConfigStore.readNetworkVariablesFromSupplicantFile(key);
-        long end = SystemClock.elapsedRealtimeNanos();
+        long start = mClock.getElapsedSinceBootNanos();
+        Map<String, String> data =
+                mWifiSupplicantControl.readNetworkVariablesFromSupplicantFile(key);
+        long end = mClock.getElapsedSinceBootNanos();
 
-        if (sVDBG) {
+        if (mVerboseLoggingEnabled) {
             localLog("readNetworkVariableFromSupplicantFile configKey=[" + configKey + "] key="
                     + key + " duration=" + (long) (end - start));
         }
@@ -1743,7 +1740,7 @@
             if (config.allowedKeyManagement.get(KeyMgmt.WPA_EAP)
                     && config.allowedKeyManagement.get(KeyMgmt.IEEE8021X)) {
 
-                if (needsSoftwareBackedKeyStore(config.enterpriseConfig)) {
+                if (mWifiKeyStore.needsSoftwareBackedKeyStore(config.enterpriseConfig)) {
                     return true;
                 }
             }
@@ -1773,7 +1770,8 @@
                     continue;
                 }
                 final String configFqdn =
-                        networkExtras.get(config.networkId).get(WifiConfigStore.ID_STRING_KEY_FQDN);
+                        networkExtras.get(config.networkId)
+                                .get(WifiSupplicantControl.ID_STRING_KEY_FQDN);
                 if (configFqdn != null && configFqdn.equals(fqdn)) {
                     Log.d(TAG, "Matched " + configFqdn + " with " + config.networkId);
                     ++matchedConfigs;
@@ -1838,7 +1836,7 @@
     }
 
     public void setAndEnableLastSelectedConfiguration(int netId) {
-        if (sVDBG) {
+        if (mVerboseLoggingEnabled) {
             logd("setLastSelectedConfiguration " + Integer.toString(netId));
         }
         if (netId == WifiConfiguration.INVALID_NETWORK_ID) {
@@ -1851,10 +1849,10 @@
                 mLastSelectedTimeStamp = -1;
             } else {
                 mLastSelectedConfiguration = selected.configKey();
-                mLastSelectedTimeStamp = mClock.elapsedRealtime();
+                mLastSelectedTimeStamp = mClock.getElapsedSinceBootMillis();
                 updateNetworkSelectionStatus(netId,
                         WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
-                if (sVDBG) {
+                if (mVerboseLoggingEnabled) {
                     logd("setLastSelectedConfiguration now: " + mLastSelectedConfiguration);
                 }
             }
@@ -1864,7 +1862,7 @@
     public void setLatestUserSelectedConfiguration(WifiConfiguration network) {
         if (network != null) {
             mLastSelectedConfiguration = network.configKey();
-            mLastSelectedTimeStamp = mClock.elapsedRealtime();
+            mLastSelectedTimeStamp = mClock.getElapsedSinceBootMillis();
         }
     }
 
@@ -1923,7 +1921,9 @@
          * refer to an existing configuration.
          */
 
-        if (sVDBG) localLog("addOrUpdateNetworkNative " + config.getPrintableSsid());
+        if (mVerboseLoggingEnabled) {
+            localLog("addOrUpdateNetworkNative " + config.getPrintableSsid());
+        }
         if (config.isPasspoint() && !mMOManager.isEnabled()) {
             Log.e(TAG, "Passpoint is not enabled");
             return new NetworkUpdateResult(INVALID_NETWORK_ID);
@@ -1955,9 +1955,19 @@
         // HasEverConnected to be set to false.
         WifiConfiguration originalConfig = new WifiConfiguration(currentConfig);
 
-        if (!mWifiConfigStore.addOrUpdateNetwork(config, currentConfig)) {
+        if (!mWifiSupplicantControl.addOrUpdateNetwork(config)) {
             return new NetworkUpdateResult(INVALID_NETWORK_ID);
         }
+        // Update the keys for enterprise networks and save the enterprise parameters to
+        // wpa_supplicant.
+        if (config.enterpriseConfig != null
+                && config.enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE) {
+            if (!(mWifiKeyStore.updateNetworkKeys(config, currentConfig)
+                    && mWifiSupplicantControl.saveEnterpriseConfiguration(config))) {
+                removeNetworkNative(config);
+                return new NetworkUpdateResult(INVALID_NETWORK_ID);
+            }
+        }
         int netId = config.networkId;
         String savedConfigKey = config.configKey();
 
@@ -2044,21 +2054,18 @@
         StringBuilder sb = new StringBuilder();
         sb.append("time=");
         Calendar c = Calendar.getInstance();
-        c.setTimeInMillis(mClock.currentTimeMillis());
+        c.setTimeInMillis(mClock.getWallClockMillis());
         sb.append(String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c));
 
         if (newNetwork) {
+            // New networks start out disabled according to public API.
+            updateNetworkSelectionStatus(currentConfig,
+                    WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WIFI_MANAGER);
             currentConfig.creationTime = sb.toString();
         } else {
             currentConfig.updateTime = sb.toString();
         }
 
-        if (currentConfig.status == WifiConfiguration.Status.ENABLED) {
-            // Make sure autojoin remain in sync with user modifying the configuration
-            updateNetworkSelectionStatus(currentConfig.networkId,
-                    WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
-        }
-
         if (currentConfig.configKey().equals(getLastSelectedConfiguration())
                 && currentConfig.ephemeral) {
             // Make the config non-ephemeral since the user just explicitly clicked it.
@@ -2069,15 +2076,17 @@
             }
         }
 
-        if (sVDBG) log("will read network variables netId=" + Integer.toString(netId));
+        if (mVerboseLoggingEnabled) {
+            log("will read network variables netId=" + Integer.toString(netId));
+        }
 
         readNetworkVariables(currentConfig);
         // When we read back the config from wpa_supplicant, some of the default values are set
         // which could change the configKey.
         if (!savedConfigKey.equals(currentConfig.configKey())) {
-            if (!mWifiConfigStore.saveNetworkMetadata(currentConfig)) {
+            if (!mWifiSupplicantControl.saveNetworkMetadata(currentConfig)) {
                 loge("Failed to set network metadata. Removing config " + config.networkId);
-                mWifiConfigStore.removeNetwork(config);
+                removeNetworkNative(config);
                 return new NetworkUpdateResult(INVALID_NETWORK_ID);
             }
         }
@@ -2113,10 +2122,25 @@
 
         saveConfig();
         writeKnownNetworkHistory();
-
+        // Stage the backup of the SettingsProvider package which backs this up.
+        mBackupManagerProxy.notifyDataChanged();
         return result;
     }
 
+    private boolean removeNetworkNative(WifiConfiguration config) {
+        if (!mWifiSupplicantControl.removeNetwork(config)) {
+            return false;
+        }
+        // Remove any associated keys.
+        if (config.enterpriseConfig != null
+                && config.enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE) {
+            mWifiKeyStore.removeKeys(config.enterpriseConfig);
+        }
+        // Stage the backup of the SettingsProvider package which backs this up.
+        mBackupManagerProxy.notifyDataChanged();
+        return true;
+    }
+
     private boolean wasBitSetUpdated(BitSet originalBitSet, BitSet currentBitSet) {
         if (originalBitSet != null && currentBitSet != null) {
             // both configs have values set, check if they are different
@@ -2249,7 +2273,9 @@
         if (config == null) return null;
         ScanDetailCache cache = mScanDetailCaches.get(config.networkId);
         if (cache == null && config.networkId != WifiConfiguration.INVALID_NETWORK_ID) {
-            cache = new ScanDetailCache(config);
+            cache =
+                    new ScanDetailCache(
+                            config, MAX_NUM_SCAN_CACHE_ENTRIES + 64, MAX_NUM_SCAN_CACHE_ENTRIES);
             mScanDetailCaches.put(config.networkId, cache);
         }
         return cache;
@@ -2301,7 +2327,7 @@
             if (config.defaultGwMacAddress != null && link.defaultGwMacAddress != null) {
                 // If both default GW are known, link only if they are equal
                 if (config.defaultGwMacAddress.equals(link.defaultGwMacAddress)) {
-                    if (sVDBG) {
+                    if (mVerboseLoggingEnabled) {
                         logd("linkConfiguration link due to same gw " + link.SSID
                                 + " and " + config.SSID + " GW " + config.defaultGwMacAddress);
                     }
@@ -2317,7 +2343,7 @@
 
                     for (String abssid : getScanDetailCache(config).keySet()) {
                         for (String bbssid : linkedScanDetailCache.keySet()) {
-                            if (sVVDBG) {
+                            if (mVerboseLoggingEnabled) {
                                 logd("linkConfiguration try to link due to DBDC BSSID match "
                                         + link.SSID + " and " + config.SSID + " bssida " + abssid
                                         + " bssidb " + bbssid);
@@ -2346,7 +2372,7 @@
             }
 
             if (doLink) {
-                if (sVDBG) {
+                if (mVerboseLoggingEnabled) {
                     logd("linkConfiguration: will link " + link.configKey()
                             + " and " + config.configKey());
                 }
@@ -2365,7 +2391,7 @@
             } else {
                 if (link.linkedConfigurations != null
                         && (link.linkedConfigurations.get(config.configKey()) != null)) {
-                    if (sVDBG) {
+                    if (mVerboseLoggingEnabled) {
                         logd("linkConfiguration: un-link " + config.configKey()
                                 + " from " + link.configKey());
                     }
@@ -2373,7 +2399,7 @@
                 }
                 if (config.linkedConfigurations != null
                         && (config.linkedConfigurations.get(link.configKey()) != null)) {
-                    if (sVDBG) {
+                    if (mVerboseLoggingEnabled) {
                         logd("linkConfiguration: un-link " + link.configKey()
                                 + " from " + config.configKey());
                     }
@@ -2387,7 +2413,7 @@
         if (config == null) {
             return null;
         }
-        long now_ms = mClock.currentTimeMillis();
+        long now_ms = mClock.getWallClockMillis();
 
         HashSet<Integer> channels = new HashSet<Integer>();
 
@@ -2396,7 +2422,7 @@
             return null;
         }
 
-        if (sVDBG) {
+        if (mVerboseLoggingEnabled) {
             StringBuilder dbg = new StringBuilder();
             dbg.append("makeChannelList age=" + Integer.toString(age)
                     + " for " + config.configKey()
@@ -2418,7 +2444,7 @@
                 if (numChannels > mMaxNumActiveChannelsForPartialScans.get()) {
                     break;
                 }
-                if (sVDBG) {
+                if (mVerboseLoggingEnabled) {
                     boolean test = (now_ms - result.seen) < age;
                     logd("has " + result.BSSID + " freq=" + Integer.toString(result.frequency)
                             + " age=" + Long.toString(now_ms - result.seen) + " ?=" + test);
@@ -2442,7 +2468,7 @@
                 }
                 for (ScanDetail scanDetail : getScanDetailCache(linked).values()) {
                     ScanResult result = scanDetail.getScanResult();
-                    if (sVDBG) {
+                    if (mVerboseLoggingEnabled) {
                         logd("has link: " + result.BSSID
                                 + " freq=" + Integer.toString(result.frequency)
                                 + " age=" + Long.toString(now_ms - result.seen));
@@ -2629,30 +2655,6 @@
             scanResult.untrusted = true;
         }
 
-        if (scanDetailCache.size() > (MAX_NUM_SCAN_CACHE_ENTRIES + 64)) {
-            long now_dbg = 0;
-            if (sVVDBG) {
-                logd(" Will trim config " + config.configKey()
-                        + " size " + scanDetailCache.size());
-
-                for (ScanDetail sd : scanDetailCache.values()) {
-                    logd("     " + sd.getBSSIDString() + " " + sd.getSeen());
-                }
-                now_dbg = SystemClock.elapsedRealtimeNanos();
-            }
-            // Trim the scan result cache to MAX_NUM_SCAN_CACHE_ENTRIES entries max
-            // Since this operation is expensive, make sure it is not performed
-            // until the cache has grown significantly above the trim treshold
-            scanDetailCache.trim(MAX_NUM_SCAN_CACHE_ENTRIES);
-            if (sVVDBG) {
-                long diff = SystemClock.elapsedRealtimeNanos() - now_dbg;
-                logd(" Finished trimming config, time(ns) " + diff);
-                for (ScanDetail sd : scanDetailCache.values()) {
-                    logd("     " + sd.getBSSIDString() + " " + sd.getSeen());
-                }
-            }
-        }
-
         // Add the scan result to this WifiConfiguration
         if (passpointMatch != null) {
             scanDetailCache.put(scanDetail, passpointMatch, getHomeSPForConfig(config));
@@ -2796,7 +2798,8 @@
         final List<WifiConfiguration> hiddenConfigurations =
                 mConfiguredNetworks.handleUserSwitch(mCurrentUserId);
         for (WifiConfiguration network : hiddenConfigurations) {
-            disableNetworkNative(network);
+            updateNetworkSelectionStatus(
+                    network, WifiConfiguration.NetworkSelectionStatus.DISABLED_DUE_TO_USER_SWITCH);
         }
         enableAllNetworks();
 
@@ -2895,7 +2898,7 @@
         }
 
         if (ipChanged || proxyChanged || isNewNetwork) {
-            if (sVDBG) {
+            if (mVerboseLoggingEnabled) {
                 logd("writeIpAndProxyConfigurationsOnChange: " + currentConfig.SSID + " -> "
                         + newConfig.SSID + " path: " + IP_CONFIG_FILE);
             }
@@ -2911,7 +2914,7 @@
      * @param config the {@link WifiConfiguration} object to be filled in.
      */
     private void readNetworkVariables(WifiConfiguration config) {
-        mWifiConfigStore.readNetworkVariables(config);
+        mWifiSupplicantControl.readNetworkVariables(config);
     }
 
     /* return the allowed key management based on a scan result */
@@ -2922,7 +2925,7 @@
 
         config.SSID = "\"" + result.SSID + "\"";
 
-        if (sVDBG) {
+        if (mVerboseLoggingEnabled) {
             logd("WifiConfiguration from scan results "
                     + config.SSID + " cap " + result.capabilities);
         }
@@ -3051,68 +3054,11 @@
         }
     }
 
-    static boolean needsSoftwareBackedKeyStore(WifiEnterpriseConfig config) {
-        String client = config.getClientCertificateAlias();
-        if (!TextUtils.isEmpty(client)) {
-            // a valid client certificate is configured
-
-            // BUGBUG: keyStore.get() never returns certBytes; because it is not
-            // taking WIFI_UID as a parameter. It always looks for certificate
-            // with SYSTEM_UID, and never finds any Wifi certificates. Assuming that
-            // all certificates need software keystore until we get the get() API
-            // fixed.
-
-            return true;
-        }
-
-        /*
-        try {
-
-            if (DBG) Slog.d(TAG, "Loading client certificate " + Credentials
-                    .USER_CERTIFICATE + client);
-
-            CertificateFactory factory = CertificateFactory.getInstance("X.509");
-            if (factory == null) {
-                Slog.e(TAG, "Error getting certificate factory");
-                return;
-            }
-
-            byte[] certBytes = keyStore.get(Credentials.USER_CERTIFICATE + client);
-            if (certBytes != null) {
-                Certificate cert = (X509Certificate) factory.generateCertificate(
-                        new ByteArrayInputStream(certBytes));
-
-                if (cert != null) {
-                    mNeedsSoftwareKeystore = hasHardwareBackedKey(cert);
-
-                    if (DBG) Slog.d(TAG, "Loaded client certificate " + Credentials
-                            .USER_CERTIFICATE + client);
-                    if (DBG) Slog.d(TAG, "It " + (mNeedsSoftwareKeystore ? "needs" :
-                            "does not need" ) + " software key store");
-                } else {
-                    Slog.d(TAG, "could not generate certificate");
-                }
-            } else {
-                Slog.e(TAG, "Could not load client certificate " + Credentials
-                        .USER_CERTIFICATE + client);
-                mNeedsSoftwareKeystore = true;
-            }
-
-        } catch(CertificateException e) {
-            Slog.e(TAG, "Could not read certificates");
-            mCaCert = null;
-            mClientCertificate = null;
-        }
-        */
-
-        return false;
-    }
-
     /**
      * Resets all sim networks from the network list.
      */
     public void resetSimNetworks() {
-        mWifiConfigStore.resetSimNetworks(mConfiguredNetworks.valuesForCurrentUser());
+        mWifiSupplicantControl.resetSimNetworks(mConfiguredNetworks.valuesForCurrentUser());
     }
 
     boolean isNetworkConfigured(WifiConfiguration config) {
@@ -3242,7 +3188,7 @@
             }
         }
         // Record last time Connectivity Service switched us away from WiFi and onto Cell
-        mLastUnwantedNetworkDisconnectTimestamp = mClock.currentTimeMillis();
+        mLastUnwantedNetworkDisconnectTimestamp = mClock.getWallClockMillis();
     }
 
     int getMaxDhcpRetries() {
@@ -3251,16 +3197,36 @@
                 DEFAULT_MAX_DHCP_RETRIES);
     }
 
+    /**
+     * Clear BSSID blacklist in wpa_supplicant & HAL.
+     */
     void clearBssidBlacklist() {
-        mWifiConfigStore.clearBssidBlacklist();
+        mBssidBlacklist.clear();
+        mWifiSupplicantControl.clearBssidBlacklist();
     }
 
+    /**
+     * Add a BSSID to the blacklist.
+     *
+     * @param bssid to be added.
+     */
     void blackListBssid(String bssid) {
-        mWifiConfigStore.blackListBssid(bssid);
+        if (TextUtils.isEmpty(bssid)) {
+            return;
+        }
+        mBssidBlacklist.add(bssid);
+        mWifiSupplicantControl.blackListBssid(bssid,
+                mBssidBlacklist.toArray(new String[mBssidBlacklist.size()]));
     }
 
+    /**
+     * Checks if the provided bssid is blacklisted or not.
+     *
+     * @param bssid bssid to be checked.
+     * @return true if present, false otherwise.
+     */
     public boolean isBssidBlacklisted(String bssid) {
-        return mWifiConfigStore.isBssidBlacklisted(bssid);
+        return mBssidBlacklist.contains(bssid);
     }
 
     public boolean getEnableAutoJoinWhenAssociated() {
@@ -3271,12 +3237,6 @@
         mEnableAutoJoinWhenAssociated.set(enabled);
     }
 
-    public void setActiveScanDetail(ScanDetail activeScanDetail) {
-        synchronized (mActiveScanDetailLock) {
-            mActiveScanDetail = activeScanDetail;
-        }
-    }
-
     /**
      * Check if the provided ephemeral network was deleted by the user or not.
      * @param ssid ssid of the network
diff --git a/service/java/com/android/server/wifi/WifiConfigManagerNew.java b/service/java/com/android/server/wifi/WifiConfigManagerNew.java
new file mode 100644
index 0000000..41e66bd
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiConfigManagerNew.java
@@ -0,0 +1,1341 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.app.ActivityManager;
+import android.app.admin.DeviceAdminInfo;
+import android.app.admin.DevicePolicyManagerInternal;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.IpConfiguration;
+import android.net.ProxyInfo;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
+import android.net.wifi.WifiEnterpriseConfig;
+import android.net.wifi.WifiManager;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.LocalLog;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+import com.android.server.wifi.util.ScanResultUtil;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * This class provides the APIs to manage configured Wi-Fi networks.
+ * It deals with the following:
+ * - Maintaining a list of configured networks for quick access.
+ * - Persisting the configurations to store when required.
+ * - Supporting WifiManager Public API calls:
+ *   > addOrUpdateNetwork()
+ *   > removeNetwork()
+ *   > enableNetwork()
+ *   > disableNetwork()
+ * - Handle user switching on multi-user devices.
+ *
+ * All network configurations retrieved from this class are copies of the original configuration
+ * stored in the internal database. So, any updates to the retrieved configuration object are
+ * meaningless and will not be reflected in the original database.
+ * This is done on purpose to ensure that only WifiConfigManager can modify configurations stored
+ * in the internal database. Any configuration updates should be triggered with appropriate helper
+ * methods of this class using the configuration's unique networkId.
+ *
+ * NOTE: These API's are not thread safe and should only be used from WifiStateMachine thread.
+ */
+public class WifiConfigManagerNew {
+    /**
+     * String used to mask passwords to public interface.
+     */
+    @VisibleForTesting
+    public static final String PASSWORD_MASK = "*";
+    /**
+     * Network Selection disable reason thresholds. These numbers are used to debounce network
+     * failures before we disable them.
+     * These are indexed using the disable reason constants defined in
+     * {@link android.net.wifi.WifiConfiguration.NetworkSelectionStatus}.
+     */
+    @VisibleForTesting
+    public static final int[] NETWORK_SELECTION_DISABLE_THRESHOLD = {
+            -1, //  threshold for NETWORK_SELECTION_ENABLE
+            1,  //  threshold for DISABLED_BAD_LINK
+            5,  //  threshold for DISABLED_ASSOCIATION_REJECTION
+            5,  //  threshold for DISABLED_AUTHENTICATION_FAILURE
+            5,  //  threshold for DISABLED_DHCP_FAILURE
+            5,  //  threshold for DISABLED_DNS_FAILURE
+            1,  //  threshold for DISABLED_WPS_START
+            6,  //  threshold for DISABLED_TLS_VERSION_MISMATCH
+            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
+    };
+    /**
+     * Network Selection disable timeout for each kind of error. After the timeout milliseconds,
+     * enable the network again.
+     * These are indexed using the disable reason constants defined in
+     * {@link android.net.wifi.WifiConfiguration.NetworkSelectionStatus}.
+     */
+    @VisibleForTesting
+    public static final int[] NETWORK_SELECTION_DISABLE_TIMEOUT_MS = {
+            Integer.MAX_VALUE,  // threshold for NETWORK_SELECTION_ENABLE
+            15 * 60 * 1000,     // threshold for DISABLED_BAD_LINK
+            5 * 60 * 1000,      // threshold for DISABLED_ASSOCIATION_REJECTION
+            5 * 60 * 1000,      // threshold for DISABLED_AUTHENTICATION_FAILURE
+            5 * 60 * 1000,      // threshold for DISABLED_DHCP_FAILURE
+            5 * 60 * 1000,      // threshold for DISABLED_DNS_FAILURE
+            0 * 60 * 1000,      // threshold for DISABLED_WPS_START
+            Integer.MAX_VALUE,  // threshold for DISABLED_TLS_VERSION
+            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
+    };
+    /**
+     * Max size of scan details to cache in {@link #mScanDetailCaches}.
+     */
+    @VisibleForTesting
+    public static final int SCAN_CACHE_ENTRIES_MAX_SIZE = 192;
+    /**
+     * Once the size of the scan details in the cache {@link #mScanDetailCaches} exceeds
+     * {@link #SCAN_CACHE_ENTRIES_MAX_SIZE}, trim it down to this value so that we have some
+     * buffer time before the next eviction.
+     */
+    @VisibleForTesting
+    public static final int SCAN_CACHE_ENTRIES_TRIM_SIZE = 128;
+    /**
+     * Flags to be passed in for |canModifyNetwork| to decide if the change is minor and can
+     * bypass the lockdown checks.
+     */
+    private static final boolean ALLOW_LOCKDOWN_CHECK_BYPASS = true;
+    private static final boolean DISALLOW_LOCKDOWN_CHECK_BYPASS = false;
+    /**
+     * Log tag for this class.
+     */
+    private static final String TAG = "WifiConfigManagerNew";
+    /**
+     * List of external dependencies for WifiConfigManager.
+     */
+    private final Context mContext;
+    private final FrameworkFacade mFacade;
+    private final Clock mClock;
+    private final UserManager mUserManager;
+    private final BackupManagerProxy mBackupManagerProxy;
+    private final WifiConfigStoreNew mWifiConfigStore;
+    private final WifiKeyStore mWifiKeyStore;
+    /**
+     * Local log used for debugging any WifiConfigManager issues.
+     */
+    private final LocalLog mLocalLog =
+            new LocalLog(ActivityManager.isLowRamDeviceStatic() ? 128 : 256);
+    /**
+     * Map of configured networks with network id as the key.
+     */
+    private final ConfigurationMap mConfiguredNetworks;
+    /**
+     * Stores a map of NetworkId to ScanDetailCache.
+     */
+    private final ConcurrentHashMap<Integer, ScanDetailCache> mScanDetailCaches;
+    /**
+     * Framework keeps a list of ephemeral SSIDs that where deleted by user,
+     * so as, framework knows not to autoconnect again those SSIDs based on scorer input.
+     * The list is never cleared up.
+     * The SSIDs are encoded in a String as per definition of WifiConfiguration.SSID field.
+     */
+    private final Set<String> mDeletedEphemeralSSIDs;
+
+    /**
+     * Verbose logging flag. Toggled by developer options.
+     */
+    private boolean mVerboseLoggingEnabled = false;
+    /**
+     * Current logged in user ID.
+     */
+    private int mCurrentUserId = UserHandle.USER_SYSTEM;
+    /**
+     * This is keeping track of the last network ID assigned. Any new networks will be assigned
+     * |mLastNetworkId + 1| as network ID.
+     */
+    private int mLastNetworkId;
+
+    /**
+     * Create new instance of WifiConfigManager.
+     */
+    WifiConfigManagerNew(
+            Context context, FrameworkFacade facade, Clock clock, UserManager userManager,
+            WifiKeyStore wifiKeyStore, WifiConfigStoreNew wifiConfigStore) {
+        mContext = context;
+        mFacade = facade;
+        mClock = clock;
+        mUserManager = userManager;
+        mBackupManagerProxy = new BackupManagerProxy();
+        mWifiConfigStore = wifiConfigStore;
+        mWifiKeyStore = wifiKeyStore;
+
+        mConfiguredNetworks = new ConfigurationMap(userManager);
+        mScanDetailCaches = new ConcurrentHashMap<>(16, 0.75f, 2);
+        mDeletedEphemeralSSIDs = new HashSet<String>();
+    }
+
+    /**
+     * Construct the string to be put in the |creationTime| & |updateTime| elements of
+     * WifiConfiguration from the provided wall clock millis.
+     *
+     * @param wallClockMillis Time in milliseconds to be converted to string.
+     */
+    @VisibleForTesting
+    public static String createDebugTimeStampString(long wallClockMillis) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("time=");
+        Calendar c = Calendar.getInstance();
+        c.setTimeInMillis(wallClockMillis);
+        sb.append(String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c));
+        return sb.toString();
+    }
+
+    /**
+     * Enable/disable verbose logging in WifiConfigManager & its helper classes.
+     */
+    public void enableVerboseLogging(int verbose) {
+        if (verbose > 0) {
+            mVerboseLoggingEnabled = true;
+        } else {
+            mVerboseLoggingEnabled = false;
+        }
+        mWifiConfigStore.enableVerboseLogging(mVerboseLoggingEnabled);
+        mWifiKeyStore.enableVerboseLogging(mVerboseLoggingEnabled);
+    }
+
+    /**
+     * Helper method to mask all passwords/keys from the provided WifiConfiguration object. This
+     * is needed when the network configurations are being requested via the public WifiManager
+     * API's.
+     * This currently masks the following elements: psk, wepKeys & enterprise config password.
+     */
+    private void maskPasswordsInWifiConfiguration(WifiConfiguration configuration) {
+        if (!TextUtils.isEmpty(configuration.preSharedKey)) {
+            configuration.preSharedKey = PASSWORD_MASK;
+        }
+        if (configuration.wepKeys != null) {
+            for (int i = 0; i < configuration.wepKeys.length; i++) {
+                if (!TextUtils.isEmpty(configuration.wepKeys[i])) {
+                    configuration.wepKeys[i] = PASSWORD_MASK;
+                }
+            }
+        }
+        if (!TextUtils.isEmpty(configuration.enterpriseConfig.getPassword())) {
+            configuration.enterpriseConfig.setPassword(PASSWORD_MASK);
+        }
+    }
+
+    /**
+     * Fetch the list of currently configured networks maintained in WifiConfigManager.
+     *
+     * This retrieves a copy of the internal configurations maintained by WifiConfigManager and
+     * should be used for any public interfaces.
+     *
+     * @param savedOnly     Retrieve only saved networks.
+     * @param maskPasswords Mask passwords or not.
+     * @return List of WifiConfiguration objects representing the networks.
+     */
+    private List<WifiConfiguration> getConfiguredNetworks(
+            boolean savedOnly, boolean maskPasswords) {
+        List<WifiConfiguration> networks = new ArrayList<>();
+        for (WifiConfiguration config : getInternalConfiguredNetworks()) {
+            if (savedOnly && config.ephemeral) {
+                continue;
+            }
+            WifiConfiguration newConfig = new WifiConfiguration(config);
+            if (maskPasswords) {
+                maskPasswordsInWifiConfiguration(newConfig);
+            }
+            networks.add(newConfig);
+        }
+        return networks;
+    }
+
+    /**
+     * Retrieves the list of all configured networks with passwords masked.
+     *
+     * @return List of WifiConfiguration objects representing the networks.
+     */
+    public List<WifiConfiguration> getConfiguredNetworks() {
+        return getConfiguredNetworks(false, true);
+    }
+
+    /**
+     * Retrieves the list of all configured networks with the passwords in plaintext.
+     *
+     * WARNING: Don't use this to pass network configurations to external apps. Should only be
+     * sent to system apps/wifi stack, when there is a need for passwords in plaintext.
+     * TODO: Need to understand the current use case of this API.
+     *
+     * @return List of WifiConfiguration objects representing the networks.
+     */
+    public List<WifiConfiguration> getConfiguredNetworksWithPasswords() {
+        return getConfiguredNetworks(false, false);
+    }
+
+    /**
+     * Retrieves the list of all configured networks with the passwords masked.
+     *
+     * @return List of WifiConfiguration objects representing the networks.
+     */
+    public List<WifiConfiguration> getSavedNetworks() {
+        return getConfiguredNetworks(true, true);
+    }
+
+    /**
+     * Retrieves the configured network corresponding to the provided networkId with password
+     * masked.
+     *
+     * @param networkId networkId of the requested network.
+     * @return WifiConfiguration object if found, null otherwise.
+     */
+    public WifiConfiguration getConfiguredNetwork(int networkId) {
+        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
+        if (config == null) {
+            return null;
+        }
+        // Create a new configuration object with the passwords masked to send out to the external
+        // world.
+        WifiConfiguration network = new WifiConfiguration(config);
+        maskPasswordsInWifiConfiguration(network);
+        return network;
+    }
+
+    /**
+     * Retrieves the configured network corresponding to the provided networkId with password
+     * in plaintext.
+     *
+     * WARNING: Don't use this to pass network configurations to external apps. Should only be
+     * sent to system apps/wifi stack, when there is a need for passwords in plaintext.
+     *
+     * @param networkId networkId of the requested network.
+     * @return WifiConfiguration object if found, null otherwise.
+     */
+    public WifiConfiguration getConfiguredNetworkWithPassword(int networkId) {
+        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
+        if (config == null) {
+            return null;
+        }
+        // Create a new configuration object without the passwords masked to send out to the
+        // external world.
+        WifiConfiguration network = new WifiConfiguration(config);
+        return network;
+    }
+
+    /**
+     * Helper method to retrieve all the internal WifiConfiguration objects corresponding to all
+     * the networks in our database.
+     */
+    private Collection<WifiConfiguration> getInternalConfiguredNetworks() {
+        return mConfiguredNetworks.valuesForCurrentUser();
+    }
+
+    /**
+     * Helper method to retrieve the internal WifiConfiguration object corresponding to the
+     * provided configuration in our database.
+     * This first attempts to find the network using the provided network ID in configuration,
+     * else it attempts to find a matching configuration using the configKey.
+     */
+    private WifiConfiguration getInternalConfiguredNetwork(WifiConfiguration config) {
+        WifiConfiguration internalConfig = mConfiguredNetworks.getForCurrentUser(config.networkId);
+        if (internalConfig != null) {
+            return internalConfig;
+        }
+        return mConfiguredNetworks.getByConfigKeyForCurrentUser(config.configKey());
+    }
+
+    /**
+     * Helper method to retrieve the internal WifiConfiguration object corresponding to the
+     * provided network ID in our database.
+     */
+    private WifiConfiguration getInternalConfiguredNetwork(int networkId) {
+        return mConfiguredNetworks.getForCurrentUser(networkId);
+    }
+
+    /**
+     * Helper method to check if the network is already configured internally or not.
+     */
+    private boolean isNetworkConfiguredInternally(WifiConfiguration config) {
+        return getInternalConfiguredNetwork(config) != null;
+    }
+
+    /**
+     * Helper method to check if the network is already configured internally or not.
+     */
+    private boolean isNetworkConfiguredInternally(int networkId) {
+        return getInternalConfiguredNetwork(networkId) != null;
+    }
+
+    /**
+     * Method to send out the configured networks change broadcast when a single network
+     * configuration is changed.
+     *
+     * @param network WifiConfiguration corresponding to the network that was changed.
+     * @param reason  The reason for the change, should be one of WifiManager.CHANGE_REASON_ADDED,
+     *                WifiManager.CHANGE_REASON_REMOVED, or WifiManager.CHANGE_REASON_CHANGE.
+     */
+    private void sendConfiguredNetworkChangedBroadcast(
+            WifiConfiguration network, int reason) {
+        Intent intent = new Intent(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        intent.putExtra(WifiManager.EXTRA_MULTIPLE_NETWORKS_CHANGED, false);
+        // Create a new WifiConfiguration with passwords masked before we send it out.
+        WifiConfiguration broadcastNetwork = new WifiConfiguration(network);
+        maskPasswordsInWifiConfiguration(broadcastNetwork);
+        intent.putExtra(WifiManager.EXTRA_WIFI_CONFIGURATION, broadcastNetwork);
+        intent.putExtra(WifiManager.EXTRA_CHANGE_REASON, reason);
+        mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+    }
+
+    /**
+     * Method to send out the configured networks change broadcast when multiple network
+     * configurations are changed.
+     */
+    private void sendConfiguredNetworksChangedBroadcast() {
+        Intent intent = new Intent(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        intent.putExtra(WifiManager.EXTRA_MULTIPLE_NETWORKS_CHANGED, true);
+        mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+    }
+
+    /**
+     * Checks if the app has the permission to override Wi-Fi network configuration or not.
+     *
+     * @param uid uid of the app.
+     * @return true if the app does have the permission, false otherwise.
+     */
+    private boolean checkConfigOverridePermission(int uid) {
+        try {
+            int permission =
+                    mFacade.checkUidPermission(
+                            android.Manifest.permission.OVERRIDE_WIFI_CONFIG, uid);
+            return (permission == PackageManager.PERMISSION_GRANTED);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error checking for permission " + e);
+            return false;
+        }
+    }
+
+    /**
+     * Checks if |uid| has permission to modify the provided configuration.
+     *
+     * @param config         WifiConfiguration object corresponding to the network to be modified.
+     * @param uid            UID of the app requesting the modification.
+     * @param ignoreLockdown Ignore the configuration lockdown checks for debug data updates.
+     */
+    private boolean canModifyNetwork(WifiConfiguration config, int uid, boolean ignoreLockdown) {
+        final DevicePolicyManagerInternal dpmi = LocalServices.getService(
+                DevicePolicyManagerInternal.class);
+
+        final boolean isUidDeviceOwner = dpmi != null && dpmi.isActiveAdminWithPolicy(uid,
+                DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+
+        // If |uid| corresponds to the device owner, allow all modifications.
+        if (isUidDeviceOwner) {
+            return true;
+        }
+
+        final boolean isCreator = (config.creatorUid == uid);
+
+        // Check if the |uid| is either the creator of the network or holds the
+        // |OVERRIDE_CONFIG_WIFI| permission if the caller asks us to bypass the lockdown checks.
+        if (ignoreLockdown) {
+            return isCreator || checkConfigOverridePermission(uid);
+        }
+
+        // Check if device has DPM capability. If it has and |dpmi| is still null, then we
+        // treat this case with suspicion and bail out.
+        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN)
+                && dpmi == null) {
+            Log.w(TAG, "Error retrieving DPMI service.");
+            return false;
+        }
+
+        // WiFi config lockdown related logic. At this point we know uid is NOT a Device Owner.
+
+        final boolean isConfigEligibleForLockdown = dpmi != null && dpmi.isActiveAdminWithPolicy(
+                config.creatorUid, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+        if (!isConfigEligibleForLockdown) {
+            return isCreator || checkConfigOverridePermission(uid);
+        }
+
+        final ContentResolver resolver = mContext.getContentResolver();
+        final boolean isLockdownFeatureEnabled = Settings.Global.getInt(resolver,
+                Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) != 0;
+        return !isLockdownFeatureEnabled && checkConfigOverridePermission(uid);
+    }
+
+    /**
+     * Copy over public elements from an external WifiConfiguration object to the internal
+     * configuration object if element has been set in the provided external WifiConfiguration.
+     * The only exception is the hidden |IpConfiguration| parameters, these need to be copied over
+     * for every update.
+     *
+     * This method updates all elements that are common to both network addition & update.
+     * The following fields of {@link WifiConfiguration} are not copied from external configs:
+     *  > networkId - These are allocated by Wi-Fi stack internally for any new configurations.
+     *  > status - The status needs to be explicitly updated using
+     *             {@link WifiManager#enableNetwork(int, boolean)} or
+     *             {@link WifiManager#disableNetwork(int)}.
+     *
+     * @param externalConfig WifiConfiguration object provided from the external API.
+     * @param internalConfig WifiConfiguration object in our internal map.
+     */
+    private void mergeWithInternalWifiConfiguration(
+            WifiConfiguration externalConfig, WifiConfiguration internalConfig) {
+        if (externalConfig.SSID != null) {
+            internalConfig.SSID = externalConfig.SSID;
+        }
+        if (externalConfig.BSSID != null) {
+            internalConfig.BSSID = externalConfig.BSSID;
+        }
+        internalConfig.hiddenSSID = externalConfig.hiddenSSID;
+        if (externalConfig.preSharedKey != null) {
+            internalConfig.preSharedKey = externalConfig.preSharedKey;
+        }
+        // Modify only wep keys are present in the provided configuration. This is a little tricky
+        // because there is no easy way to tell if the app is actually trying to null out the
+        // existing keys or not.
+        if (externalConfig.wepKeys != null) {
+            boolean hasWepKey = false;
+            for (int i = 0; i < internalConfig.wepKeys.length; i++) {
+                if (externalConfig.wepKeys[i] != null) {
+                    internalConfig.wepKeys[i] = externalConfig.wepKeys[i];
+                    hasWepKey = true;
+                }
+            }
+            if (hasWepKey) {
+                internalConfig.wepTxKeyIndex = externalConfig.wepTxKeyIndex;
+            }
+        }
+        if (externalConfig.FQDN != null) {
+            internalConfig.FQDN = externalConfig.FQDN;
+        }
+        if (externalConfig.providerFriendlyName != null) {
+            internalConfig.providerFriendlyName = externalConfig.providerFriendlyName;
+        }
+        if (externalConfig.roamingConsortiumIds != null) {
+            internalConfig.roamingConsortiumIds = externalConfig.roamingConsortiumIds;
+        }
+
+        // Copy over all the auth/protocol/key mgmt parameters if set.
+        if (externalConfig.allowedAuthAlgorithms != null
+                && !externalConfig.allowedAuthAlgorithms.isEmpty()) {
+            internalConfig.allowedAuthAlgorithms = externalConfig.allowedAuthAlgorithms;
+        }
+        if (externalConfig.allowedProtocols != null
+                && !externalConfig.allowedProtocols.isEmpty()) {
+            internalConfig.allowedProtocols = externalConfig.allowedProtocols;
+        }
+        if (externalConfig.allowedKeyManagement != null
+                && !externalConfig.allowedKeyManagement.isEmpty()) {
+            internalConfig.allowedKeyManagement = externalConfig.allowedKeyManagement;
+        }
+        if (externalConfig.allowedPairwiseCiphers != null
+                && !externalConfig.allowedPairwiseCiphers.isEmpty()) {
+            internalConfig.allowedPairwiseCiphers = externalConfig.allowedPairwiseCiphers;
+        }
+        if (externalConfig.allowedGroupCiphers != null
+                && !externalConfig.allowedGroupCiphers.isEmpty()) {
+            internalConfig.allowedGroupCiphers = externalConfig.allowedGroupCiphers;
+        }
+
+        // Copy over the |IpConfiguration| parameters if set.
+        if (externalConfig.getIpConfiguration() != null) {
+            if (externalConfig.getIpAssignment() != IpConfiguration.IpAssignment.UNASSIGNED) {
+                internalConfig.setIpAssignment(externalConfig.getIpAssignment());
+                internalConfig.setStaticIpConfiguration(externalConfig.getStaticIpConfiguration());
+            }
+            if (externalConfig.getProxySettings() != IpConfiguration.ProxySettings.UNASSIGNED) {
+                internalConfig.setProxySettings(externalConfig.getProxySettings());
+                internalConfig.setHttpProxy(externalConfig.getHttpProxy());
+            }
+        }
+
+        // Copy over the |WifiEnterpriseConfig| parameters if set.
+        if (externalConfig.enterpriseConfig != null) {
+            internalConfig.enterpriseConfig =
+                    new WifiEnterpriseConfig(externalConfig.enterpriseConfig);
+        }
+    }
+
+    /**
+     * Set all the exposed defaults in the newly created WifiConfiguration object.
+     * These fields have a default value advertised in our public documentation. The only exception
+     * is the hidden |IpConfiguration| parameters, these have a default value even though they're
+     * hidden.
+     *
+     * @param configuration provided WifiConfiguration object.
+     */
+    private void setDefaultsInWifiConfiguration(WifiConfiguration configuration) {
+        configuration.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
+        configuration.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED);
+
+        configuration.allowedProtocols.set(WifiConfiguration.Protocol.RSN);
+        configuration.allowedProtocols.set(WifiConfiguration.Protocol.WPA);
+
+        configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+        configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
+
+        configuration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
+        configuration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);
+
+        configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
+        configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
+        configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40);
+        configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104);
+
+        configuration.setIpAssignment(IpConfiguration.IpAssignment.DHCP);
+        configuration.setProxySettings(IpConfiguration.ProxySettings.NONE);
+    }
+
+    /**
+     * Create a new WifiConfiguration object by copying over parameters from the provided
+     * external configuration and set defaults for the appropriate parameters.
+     *
+     * @param config provided external WifiConfiguration object.
+     * @return New WifiConfiguration object with parameters merged from the provided external
+     * configuration.
+     */
+    private WifiConfiguration createNewInternalWifiConfigurationFromExternal(
+            WifiConfiguration config, int uid) {
+        WifiConfiguration newConfig = new WifiConfiguration();
+
+        // First allocate a new network ID for the configuration.
+        newConfig.networkId = mLastNetworkId++;
+
+        // First set defaults in the new configuration created.
+        setDefaultsInWifiConfiguration(newConfig);
+
+        // Copy over all the public elements from the provided configuration.
+        mergeWithInternalWifiConfiguration(config, newConfig);
+
+        // Copy over the hidden configuration parameters. These are the only parameters used by
+        // system apps to indicate some property about the network being added.
+        // These are only copied over for network additions and ignored for network updates.
+        newConfig.requirePMF = config.requirePMF;
+        newConfig.noInternetAccessExpected = config.noInternetAccessExpected;
+        newConfig.ephemeral = config.ephemeral;
+        newConfig.meteredHint = config.meteredHint;
+        newConfig.useExternalScores = config.useExternalScores;
+        newConfig.shared = config.shared;
+
+        // Add debug information for network addition.
+        newConfig.creatorUid = newConfig.lastUpdateUid = uid;
+        newConfig.creatorName = newConfig.lastUpdateName =
+                mContext.getPackageManager().getNameForUid(uid);
+        newConfig.creationTime = newConfig.updateTime =
+                createDebugTimeStampString(mClock.getWallClockMillis());
+
+        return newConfig;
+    }
+
+    /**
+     * Merges the provided external WifiConfiguration object with the existing internal
+     * WifiConfiguration object.
+     *
+     * @param config provided external WifiConfiguration object.
+     * @return Existing WifiConfiguration object with parameters merged from the provided
+     * configuration.
+     */
+    private WifiConfiguration updateExistingInternalWifiConfigurationFromExternal(
+            WifiConfiguration config, int uid) {
+        WifiConfiguration existingConfig = getInternalConfiguredNetwork(config);
+
+        // Copy over all the public elements from the provided configuration.
+        mergeWithInternalWifiConfiguration(config, existingConfig);
+
+        // Add debug information for network update.
+        existingConfig.lastUpdateUid = uid;
+        existingConfig.lastUpdateName = mContext.getPackageManager().getNameForUid(uid);
+        existingConfig.updateTime = createDebugTimeStampString(mClock.getWallClockMillis());
+
+        return existingConfig;
+    }
+
+    /**
+     * Compare existing and new IpConfiguration and return if IP assignment has changed or not.
+     *
+     * @param existingConfig Existing config corresponding to the network already stored in our
+     *                       database.
+     * @param newConfig      New updated config object in our database.
+     * @return true if IP assignment have changed, false otherwise.
+     */
+    private boolean hasIpChanged(WifiConfiguration existingConfig, WifiConfiguration newConfig) {
+        switch (newConfig.getIpAssignment()) {
+            case STATIC:
+                if (existingConfig.getIpAssignment() != newConfig.getIpAssignment()) {
+                    return true;
+                } else {
+                    return !Objects.equals(
+                            existingConfig.getStaticIpConfiguration(),
+                            newConfig.getStaticIpConfiguration());
+                }
+            case DHCP:
+                return (existingConfig.getIpAssignment() != newConfig.getIpAssignment());
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Compare existing and new IpConfiguration and return if Proxy settings has changed or not.
+     *
+     * @param existingConfig Existing config corresponding to the network already stored in our
+     *                       database.
+     * @param newConfig      New updated config object in our database.
+     * @return true if proxy settings have changed, false otherwise.
+     */
+    private boolean hasProxyChanged(WifiConfiguration existingConfig, WifiConfiguration newConfig) {
+        switch (newConfig.getProxySettings()) {
+            case STATIC:
+            case PAC:
+                ProxyInfo newHttpProxy = newConfig.getHttpProxy();
+                ProxyInfo currentHttpProxy = existingConfig.getHttpProxy();
+                if (newHttpProxy != null) {
+                    return !newHttpProxy.equals(currentHttpProxy);
+                } else {
+                    return (currentHttpProxy != null);
+                }
+            case NONE:
+                return (existingConfig.getProxySettings() != newConfig.getProxySettings());
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Add a network or update a network configuration to our database.
+     * If the supplied networkId is INVALID_NETWORK_ID, we create a new empty
+     * network configuration. Otherwise, the networkId should refer to an existing configuration.
+     *
+     * @param config provided WifiConfiguration object.
+     * @param uid    UID of the app requesting the network addition/deletion.
+     * @return NetworkUpdateResult object representing status of the update.
+     */
+    private NetworkUpdateResult addOrUpdateNetworkInternal(WifiConfiguration config, int uid) {
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "Adding/Updating network " + config.getPrintableSsid());
+        }
+        boolean newNetwork;
+        WifiConfiguration existingInternalConfig;
+        WifiConfiguration newInternalConfig;
+
+        if (!isNetworkConfiguredInternally(config)) {
+            newNetwork = true;
+            existingInternalConfig = null;
+            newInternalConfig = createNewInternalWifiConfigurationFromExternal(config, uid);
+        } else {
+            newNetwork = false;
+            existingInternalConfig =
+                    new WifiConfiguration(getInternalConfiguredNetwork(config));
+            // 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 "
+                        + config.configKey());
+                return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
+            }
+            newInternalConfig = updateExistingInternalWifiConfigurationFromExternal(config, uid);
+        }
+
+        // Update the keys for enterprise networks.
+        if (config.enterpriseConfig != null
+                && config.enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE) {
+            if (!(mWifiKeyStore.updateNetworkKeys(config, existingInternalConfig))) {
+                return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
+            }
+        }
+
+        // Add it our internal map.
+        mConfiguredNetworks.put(newInternalConfig);
+
+        // Stage the backup of the SettingsProvider package which backs this up.
+        mBackupManagerProxy.notifyDataChanged();
+
+        // This is needed to inform IpManager about any IP configuration changes.
+        boolean hasIpChanged =
+                newNetwork || hasIpChanged(existingInternalConfig, newInternalConfig);
+        boolean hasProxyChanged =
+                newNetwork || hasProxyChanged(existingInternalConfig, newInternalConfig);
+        NetworkUpdateResult result = new NetworkUpdateResult(hasIpChanged, hasProxyChanged);
+        result.setIsNewNetwork(newNetwork);
+        result.setNetworkId(newInternalConfig.networkId);
+
+        localLog("addOrUpdateNetworkInternal: added/updated config."
+                + " netId=" + newInternalConfig.networkId
+                + " configKey=" + newInternalConfig.configKey()
+                + " uid=" + Integer.toString(newInternalConfig.creatorUid)
+                + " name=" + newInternalConfig.creatorName);
+        return result;
+    }
+
+    /**
+     * Add a network or update a network configuration to our database.
+     * If the supplied networkId is INVALID_NETWORK_ID, we create a new empty
+     * network configuration. Otherwise, the networkId should refer to an existing configuration.
+     *
+     * @param config provided WifiConfiguration object.
+     * @param uid    UID of the app requesting the network addition/deletion.
+     * @return NetworkUpdateResult object representing status of the update.
+     */
+    public NetworkUpdateResult addOrUpdateNetwork(WifiConfiguration config, int uid) {
+        if (config == null) {
+            Log.e(TAG, "Cannot add/update network with null config");
+            return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
+        }
+        NetworkUpdateResult result = addOrUpdateNetworkInternal(config, uid);
+        if (!result.isSuccess()) {
+            Log.e(TAG, "Failed to add/update network " + config.getPrintableSsid());
+            return result;
+        }
+        WifiConfiguration newConfig = getInternalConfiguredNetwork(result.getNetworkId());
+        sendConfiguredNetworkChangedBroadcast(
+                newConfig,
+                result.isNewNetwork()
+                        ? WifiManager.CHANGE_REASON_ADDED
+                        : WifiManager.CHANGE_REASON_CONFIG_CHANGE);
+        // External modification, persist it immediately.
+        saveToStore(true);
+        return result;
+    }
+
+    /**
+     * Removes the specified network configuration from our database.
+     *
+     * @param config provided WifiConfiguration object.
+     * @return true if successful, false otherwise.
+     */
+    private boolean removeNetworkInternal(WifiConfiguration config) {
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "Removing network " + config.getPrintableSsid());
+        }
+        // Remove any associated enterprise keys.
+        if (config.enterpriseConfig != null
+                && config.enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE) {
+            mWifiKeyStore.removeKeys(config.enterpriseConfig);
+        }
+
+        mConfiguredNetworks.remove(config.networkId);
+        mScanDetailCaches.remove(config.networkId);
+        // Stage the backup of the SettingsProvider package which backs this up.
+        mBackupManagerProxy.notifyDataChanged();
+
+        localLog("removeNetworkInternal: removed config."
+                + " netId=" + config.networkId
+                + " configKey=" + config.configKey());
+        return true;
+    }
+
+    /**
+     * Removes the specified network configuration from our database.
+     *
+     * @param networkId network ID of the provided network.
+     * @return true if successful, false otherwise.
+     */
+    public boolean removeNetwork(int networkId) {
+        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
+        if (config == null) {
+            Log.e(TAG, "Cannot find network with networkId " + networkId);
+            return false;
+        }
+        if (!removeNetworkInternal(config)) {
+            Log.e(TAG, "Failed to remove network " + config.getPrintableSsid());
+            return false;
+        }
+        sendConfiguredNetworkChangedBroadcast(config, WifiManager.CHANGE_REASON_REMOVED);
+        // External modification, persist it immediately.
+        saveToStore(true);
+        return true;
+    }
+
+    /**
+     * Helper method to mark a network enabled for network selection.
+     */
+    private void setNetworkSelectionEnabled(NetworkSelectionStatus status) {
+        status.setNetworkSelectionStatus(
+                NetworkSelectionStatus.NETWORK_SELECTION_ENABLED);
+        status.setDisableTime(
+                NetworkSelectionStatus.INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP);
+        status.setNetworkSelectionDisableReason(NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
+
+        // Clear out all the disable reason counters.
+        status.clearDisableReasonCounter();
+    }
+
+    /**
+     * Helper method to mark a network temporarily disabled for network selection.
+     */
+    private void setNetworkSelectionTemporarilyDisabled(
+            NetworkSelectionStatus status, int disableReason) {
+        status.setNetworkSelectionStatus(
+                NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED);
+        // Only need a valid time filled in for temporarily disabled networks.
+        status.setDisableTime(mClock.getElapsedSinceBootMillis());
+        status.setNetworkSelectionDisableReason(disableReason);
+    }
+
+    /**
+     * Helper method to mark a network permanently disabled for network selection.
+     */
+    private void setNetworkSelectionPermanentlyDisabled(
+            NetworkSelectionStatus status, int disableReason) {
+        status.setNetworkSelectionStatus(
+                NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED);
+        status.setDisableTime(
+                NetworkSelectionStatus.INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP);
+        status.setNetworkSelectionDisableReason(disableReason);
+    }
+
+    /**
+     * Helper method to set the publicly exposed status for the network and send out the network
+     * status change broadcast.
+     */
+    private void setNetworkStatus(WifiConfiguration config, int status) {
+        config.status = status;
+        sendConfiguredNetworkChangedBroadcast(config, WifiManager.CHANGE_REASON_CONFIG_CHANGE);
+    }
+
+    /**
+     * Sets a network's status (both internal and public) according to the update reason and
+     * its current state.
+     *
+     * This updates the network's {@link WifiConfiguration#mNetworkSelectionStatus} field and the
+     * public {@link WifiConfiguration#status} field if the network is either enabled or
+     * permanently disabled.
+     *
+     * @param config network to be updated.
+     * @param reason reason code for update.
+     * @return true if the input configuration has been updated, false otherwise.
+     */
+    private boolean setNetworkSelectionStatus(WifiConfiguration config, int reason) {
+        NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
+        if (reason < 0 || reason >= NetworkSelectionStatus.NETWORK_SELECTION_DISABLED_MAX) {
+            Log.e(TAG, "Invalid Network disable reason " + reason);
+            return false;
+        }
+        if (reason == NetworkSelectionStatus.NETWORK_SELECTION_ENABLE) {
+            setNetworkSelectionEnabled(networkStatus);
+            setNetworkStatus(config, WifiConfiguration.Status.ENABLED);
+        } else if (reason < NetworkSelectionStatus.DISABLED_TLS_VERSION_MISMATCH) {
+            setNetworkSelectionTemporarilyDisabled(networkStatus, reason);
+        } else {
+            setNetworkSelectionPermanentlyDisabled(networkStatus, reason);
+            setNetworkStatus(config, WifiConfiguration.Status.DISABLED);
+        }
+        localLog("setNetworkSelectionStatus: configKey=" + config.configKey()
+                + " networkStatus=" + networkStatus.getNetworkStatusString() + " disableReason="
+                + networkStatus.getNetworkDisableReasonString() + " at="
+                + createDebugTimeStampString(mClock.getWallClockMillis()));
+        return true;
+    }
+
+    /**
+     * Update a network's status (both internal and public) according to the update reason and
+     * its current state.
+     *
+     * @param config network to be updated.
+     * @param reason reason code for update.
+     * @return true if the input configuration has been updated, false otherwise.
+     */
+    private boolean updateNetworkSelectionStatus(WifiConfiguration config, int reason) {
+        NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
+        if (reason != NetworkSelectionStatus.NETWORK_SELECTION_ENABLE) {
+            networkStatus.incrementDisableReasonCounter(reason);
+            // For network disable reasons, we should only update the status if we cross the
+            // threshold.
+            int disableReasonCounter = networkStatus.getDisableReasonCounter(reason);
+            int disableReasonThreshold = NETWORK_SELECTION_DISABLE_THRESHOLD[reason];
+            if (disableReasonCounter < disableReasonThreshold) {
+                if (mVerboseLoggingEnabled) {
+                    Log.v(TAG, "Disable counter for network " + config.getPrintableSsid()
+                            + " for reason "
+                            + NetworkSelectionStatus.getNetworkDisableReasonString(reason) + " is "
+                            + networkStatus.getDisableReasonCounter(reason) + " and threshold is "
+                            + disableReasonThreshold);
+                }
+                return true;
+            }
+        }
+        return setNetworkSelectionStatus(config, reason);
+    }
+
+    /**
+     * Update a network's status (both internal and public) according to the update reason and
+     * its current state.
+     *
+     * Each network has 2 status:
+     * 1. NetworkSelectionStatus: This is internal selection status of the network. This is used
+     * for temporarily disabling a network for QNS.
+     * 2. Status: This is the exposed status for a network. This is mostly set by
+     * the public API's {@link WifiManager#enableNetwork(int, boolean)} &
+     * {@link WifiManager#disableNetwork(int)}.
+     *
+     * @param networkId network ID of the network that needs the update.
+     * @param reason    reason to update the network.
+     * @return true if the input configuration has been updated, false otherwise.
+     */
+    public boolean updateNetworkSelectionStatus(int networkId, int reason) {
+        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
+        if (config == null) {
+            Log.e(TAG, "Cannot find network with networkId " + networkId);
+            return false;
+        }
+        return updateNetworkSelectionStatus(config, reason);
+    }
+
+    /**
+     * Attempt to re-enable a network for network selection, if this network was either:
+     * a) Previously temporarily disabled, but its disable timeout has expired, or
+     * b) Previously disabled because of a user switch, but is now visible to the current
+     * user.
+     *
+     * @param config configuration for the network to be re-enabled for network selection. The
+     *               network corresponding to the config must be visible to the current user.
+     * @return true if the network identified by {@param config} was re-enabled for qualified
+     * network selection, false otherwise.
+     */
+    private boolean tryEnableNetwork(WifiConfiguration config) {
+        NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
+        if (networkStatus.isNetworkTemporaryDisabled()) {
+            long timeDifferenceMs =
+                    mClock.getElapsedSinceBootMillis() - networkStatus.getDisableTime();
+            int disableReason = networkStatus.getNetworkSelectionDisableReason();
+            long disableTimeoutMs = NETWORK_SELECTION_DISABLE_TIMEOUT_MS[disableReason];
+            if (timeDifferenceMs >= disableTimeoutMs) {
+                return updateNetworkSelectionStatus(
+                        config, NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
+            }
+        } else if (networkStatus.isDisabledByReason(
+                NetworkSelectionStatus.DISABLED_DUE_TO_USER_SWITCH)) {
+            return updateNetworkSelectionStatus(
+                    config, NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
+        }
+        return false;
+    }
+
+    /**
+     * Attempt to re-enable a network for network selection, if this network was either:
+     * a) Previously temporarily disabled, but its disable timeout has expired, or
+     * b) Previously disabled because of a user switch, but is now visible to the current
+     * user.
+     *
+     * @param networkId the id of the network to be checked for possible unblock (due to timeout)
+     * @return true if the network identified by {@param networkId} was re-enabled for qualified
+     * network selection, false otherwise.
+     */
+    public boolean tryEnableNetwork(int networkId) {
+        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
+        if (config == null) {
+            Log.e(TAG, "Cannot find network with networkId " + networkId);
+            return false;
+        }
+        return tryEnableNetwork(config);
+    }
+
+    /**
+     * Enable a network using the public {@link WifiManager#enableNetwork(int, boolean)} API.
+     *
+     * @param networkId network ID of the network that needs the update.
+     * @param uid       uid of the app requesting the update.
+     * @return {@code true} if it succeeds, {@code false} otherwise
+     */
+    public boolean enableNetwork(int networkId, int uid) {
+        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
+        if (config == null) {
+            Log.e(TAG, "Cannot find network with networkId " + networkId);
+            return false;
+        }
+        if (!canModifyNetwork(config, uid, DISALLOW_LOCKDOWN_CHECK_BYPASS)) {
+            Log.e(TAG, "UID " + uid + " does not have permission to update configuration "
+                    + config.configKey());
+            return false;
+        }
+        return updateNetworkSelectionStatus(
+                networkId, WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
+    }
+
+    /**
+     * Disable a network using the public {@link WifiManager#disableNetwork(int)} API.
+     *
+     * @param networkId network ID of the network that needs the update.
+     * @param uid       uid of the app requesting the update.
+     * @return {@code true} if it succeeds, {@code false} otherwise
+     */
+    public boolean disableNetwork(int networkId, int uid) {
+        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
+        if (config == null) {
+            Log.e(TAG, "Cannot find network with networkId " + networkId);
+            return false;
+        }
+        if (!canModifyNetwork(config, uid, DISALLOW_LOCKDOWN_CHECK_BYPASS)) {
+            Log.e(TAG, "UID " + uid + " does not have permission to update configuration "
+                    + config.configKey());
+            return false;
+        }
+        return updateNetworkSelectionStatus(
+                networkId, NetworkSelectionStatus.DISABLED_BY_WIFI_MANAGER);
+    }
+
+    /**
+     * Checks if the |uid| has the necessary permission to override wifi config and updates the last
+     * connected UID for the provided configuration.
+     *
+     * @param networkId network ID corresponding to the network.
+     * @param uid       uid of the app requesting the connection.
+     * @return {@code true} if |uid| has the necessary permission to trigger connection to the
+     * network, {@code false} otherwise.
+     */
+    public boolean checkAndUpdateLastConnectUid(int networkId, int uid) {
+        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
+        if (config == null) {
+            Log.e(TAG, "Cannot find network with networkId " + networkId);
+            return false;
+        }
+        if (!canModifyNetwork(config, uid, ALLOW_LOCKDOWN_CHECK_BYPASS)) {
+            Log.e(TAG, "UID " + uid + " does not have permission to update configuration "
+                    + config.configKey());
+            return false;
+        }
+        config.lastConnectUid = uid;
+        return true;
+    }
+
+    /**
+     * Helper method to get the scan detail cache entry {@link #mScanDetailCaches} for the provided
+     * network.
+     *
+     * @param networkId network ID corresponding to the network.
+     * @return existing {@link ScanDetailCache} entry if one exists or null.
+     */
+    @VisibleForTesting
+    public ScanDetailCache getScanDetailCacheForNetwork(int networkId) {
+        return mScanDetailCaches.get(networkId);
+    }
+
+    /**
+     * Helper method to get or create a scan detail cache entry {@link #mScanDetailCaches} for
+     * the provided network.
+     *
+     * @param config configuration corresponding to the the network.
+     * @return existing {@link ScanDetailCache} entry if one exists or a new instance created for
+     * this network.
+     */
+    private ScanDetailCache getOrCreateScanDetailCacheForNetwork(WifiConfiguration config) {
+        if (config == null) return null;
+        ScanDetailCache cache = getScanDetailCacheForNetwork(config.networkId);
+        if (cache == null && config.networkId != WifiConfiguration.INVALID_NETWORK_ID) {
+            cache =
+                    new ScanDetailCache(
+                            config, SCAN_CACHE_ENTRIES_MAX_SIZE, SCAN_CACHE_ENTRIES_TRIM_SIZE);
+            mScanDetailCaches.put(config.networkId, cache);
+        }
+        return cache;
+    }
+
+    /**
+     * Saves the provided ScanDetail into the corresponding scan detail cache entry
+     * {@link #mScanDetailCaches} for the provided network.
+     *
+     * @param config     configuration corresponding to the the network.
+     * @param scanDetail new scan detail instance to be saved into the cache.
+     */
+    private void saveToScanDetailCacheForNetwork(
+            WifiConfiguration config, ScanDetail scanDetail) {
+        ScanResult scanResult = scanDetail.getScanResult();
+
+        ScanDetailCache scanDetailCache = getOrCreateScanDetailCacheForNetwork(config);
+        if (scanDetailCache == null) {
+            Log.e(TAG, "Could not allocate scan cache for " + config.getPrintableSsid());
+            return;
+        }
+
+        // Adding a new BSSID
+        ScanResult result = scanDetailCache.get(scanResult.BSSID);
+        if (result != null) {
+            // transfer the black list status
+            scanResult.blackListTimestamp = result.blackListTimestamp;
+            scanResult.numIpConfigFailures = result.numIpConfigFailures;
+            scanResult.numConnection = result.numConnection;
+            scanResult.isAutoJoinCandidate = result.isAutoJoinCandidate;
+        }
+        if (config.ephemeral) {
+            // For an ephemeral Wi-Fi config, the ScanResult should be considered
+            // untrusted.
+            scanResult.untrusted = true;
+        }
+
+        // Add the scan detail to this network's scan detail cache.
+        scanDetailCache.put(scanDetail);
+
+        // Since we added a scan result to this configuration, re-attempt linking
+        // TODO: linkConfiguration(config);
+    }
+
+    /**
+     * Retrieves a saved 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) {
+        ScanResult scanResult = scanDetail.getScanResult();
+        if (scanResult == null) {
+            Log.e(TAG, "No scan result found in scan detail");
+            return null;
+        }
+        // Add the double quotes to the scan result SSID for comparison with the network configs.
+        String ssidToCompare = "\"" + scanResult.SSID + "\"";
+        for (WifiConfiguration config : getInternalConfiguredNetworks()) {
+            if (config.SSID == null || !config.SSID.equals(ssidToCompare)) {
+                continue;
+            }
+            if (ScanResultUtil.doesScanResultEncryptionMatchWithNetwork(scanResult, config)) {
+                localLog("getSavedNetworkFromScanDetail: Found " + config.configKey()
+                        + " for " + scanResult.SSID + "[" + scanResult.capabilities + "]");
+                return config;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 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
+     * {@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);
+        if (network == null) {
+            return null;
+        }
+        saveToScanDetailCacheForNetwork(network, scanDetail);
+        return getConfiguredNetworkWithPassword(network.networkId);
+    }
+
+    /**
+     * Read the config store and load the in-memory lists from the store data retrieved.
+     * This reads all the network configurations from:
+     * 1. Shared WifiConfigStore.xml
+     * 2. User WifiConfigStore.xml
+     * 3. PerProviderSubscription.conf
+     */
+    private void loadFromStore() {
+        WifiConfigStoreData storeData;
+
+        long readStartTime = mClock.getElapsedSinceBootMillis();
+        try {
+            storeData = mWifiConfigStore.read();
+        } catch (Exception e) {
+            Log.wtf(TAG, "Reading from new store failed " + e + ". All saved networks are lost!");
+            return;
+        }
+        long readTime = mClock.getElapsedSinceBootMillis() - readStartTime;
+        Log.d(TAG, "Loading from store completed in " + readTime + " ms.");
+
+        // Clear out all the existing in-memory lists and load the lists from what was retrieved
+        // from the config store.
+        mConfiguredNetworks.clear();
+        mDeletedEphemeralSSIDs.clear();
+        mScanDetailCaches.clear();
+        for (WifiConfiguration configuration : storeData.configurations) {
+            configuration.networkId = mLastNetworkId++;
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "Adding network from store " + configuration.configKey());
+            }
+            mConfiguredNetworks.put(configuration);
+        }
+        for (String ssid : storeData.deletedEphemeralSSIDs) {
+            mDeletedEphemeralSSIDs.add(ssid);
+        }
+        if (mConfiguredNetworks.sizeForAllUsers() == 0) {
+            Log.w(TAG, "No stored networks found.");
+        }
+    }
+
+    /**
+     * Save the current snapshot of the in-memory lists to the config store.
+     *
+     * @param forceWrite Whether the write needs to be forced or not.
+     * @return Whether the write was successful or not, this is applicable only for force writes.
+     */
+    private boolean saveToStore(boolean forceWrite) {
+        ArrayList<WifiConfiguration> configurations = new ArrayList<>();
+        // Don't persist ephemeral networks to store.
+        for (WifiConfiguration config : mConfiguredNetworks.valuesForCurrentUser()) {
+            if (!config.ephemeral) {
+                configurations.add(config);
+            }
+        }
+        WifiConfigStoreData storeData =
+                new WifiConfigStoreData(configurations, mDeletedEphemeralSSIDs);
+
+        long writeStartTime = mClock.getElapsedSinceBootMillis();
+        try {
+            mWifiConfigStore.write(forceWrite, storeData);
+        } catch (Exception e) {
+            Log.wtf(TAG, "Writing to store failed " + e + ". Saved networks maybe lost!");
+            return false;
+        }
+        long writeTime = mClock.getElapsedSinceBootMillis() - writeStartTime;
+        Log.d(TAG, "Writing to store completed in " + writeTime + " ms.");
+        return true;
+    }
+
+    /**
+     * Helper method for logging into local log buffer.
+     */
+    private void localLog(String s) {
+        if (mLocalLog != null) {
+            mLocalLog.log(s);
+        }
+    }
+
+    /**
+     * Dump the local log buffer.
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("Dump of WifiConfigManager");
+        pw.println("WifiConfigManager - Log Begin ----");
+        mLocalLog.dump(fd, pw, args);
+        pw.println("WifiConfigManager - Log End ----");
+    }
+}
diff --git a/service/java/com/android/server/wifi/WifiConfigStoreData.java b/service/java/com/android/server/wifi/WifiConfigStoreData.java
new file mode 100644
index 0000000..4d1802f
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiConfigStoreData.java
@@ -0,0 +1,521 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.net.IpConfiguration;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
+import android.net.wifi.WifiEnterpriseConfig;
+import android.util.Pair;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+import com.android.server.wifi.util.XmlUtil;
+import com.android.server.wifi.util.XmlUtil.IpConfigurationXmlUtil;
+import com.android.server.wifi.util.XmlUtil.NetworkSelectionStatusXmlUtil;
+import com.android.server.wifi.util.XmlUtil.WifiConfigurationXmlUtil;
+import com.android.server.wifi.util.XmlUtil.WifiEnterpriseConfigXmlUtil;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Class to encapsulate all the data to be stored across all the stores. This is a snapshot
+ * of all the settings passed from {@link WifiConfigManager} to persistent store.
+ * Instances of this class are passed from/to WifiConfigManager and WifiConfigStore for
+ * writing/parsing data to/from the store files.
+ *
+ * Note: Nesting of objects during serialization makes it hard to deserialize data especially
+ * when we have elements added to the parent object in future revisions. So, when we serialize
+ * {@link WifiConfiguration} objects (representing saved networks), we add separate sections in the
+ * XML for each nested object (such as {@link IpConfiguration} and {@link NetworkSelectionStatus})
+ * within WifiConfiguration object.
+ */
+public class WifiConfigStoreData {
+    /**
+     * Current config store data version. This will be incremented for any additions.
+     */
+    private static final int CURRENT_CONFIG_STORE_DATA_VERSION = 1;
+    /** This list of older versions will be used to restore data from older config store. */
+    /**
+     * First version of the config store data format.
+     */
+    private static final int INITIAL_CONFIG_STORE_DATA_VERSION = 1;
+    /**
+     * List of XML section header tags in the config store data.
+     */
+    private static final String XML_TAG_DOCUMENT_HEADER = "WifiConfigStoreData";
+    private static final String XML_TAG_VERSION = "Version";
+    private static final String XML_TAG_SECTION_HEADER_NETWORK_LIST = "NetworkList";
+    private static final String XML_TAG_SECTION_HEADER_NETWORK = "Network";
+    private static final String XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION = "WifiConfiguration";
+    private static final String XML_TAG_SECTION_HEADER_NETWORK_STATUS = "NetworkStatus";
+    private static final String XML_TAG_SECTION_HEADER_IP_CONFIGURATION = "IpConfiguration";
+    private static final String XML_TAG_SECTION_HEADER_WIFI_ENTERPRISE_CONFIGURATION =
+            "WifiEnterpriseConfiguration";
+    private static final String XML_TAG_SECTION_HEADER_DELETED_EPHEMERAL_SSID_LIST =
+            "DeletedEphemeralSSIDList";
+    /**
+     * List of saved networks visible to the current user to be stored (includes shared & private).
+     */
+    public List<WifiConfiguration> configurations;
+    /**
+     * List of deleted ephemeral ssids to be stored.
+     */
+    public Set<String> deletedEphemeralSSIDs;
+
+    /**
+     * Create a new instance of store data to be written to the store files.
+     *
+     * @param configurations        list of saved networks to be stored.
+     *                              See {@link WifiConfigManager#mConfiguredNetworks}.
+     * @param deletedEphemeralSSIDs list of deleted ephemeral ssids to be stored.
+     *                              See {@link WifiConfigManager#mDeletedEphemeralSSIDs}
+     */
+    public WifiConfigStoreData(
+            List<WifiConfiguration> configurations, Set<String> deletedEphemeralSSIDs) {
+        this.configurations = configurations;
+        this.deletedEphemeralSSIDs = deletedEphemeralSSIDs;
+    }
+
+    /**
+     * Create a new instance of the store data parsed from the store file data.
+     *
+     * Note: If any of the raw data is null or empty, will create an empty corresponding store data.
+     * This is to handle fresh install devices where these stores are not yet created.
+     *
+     * @param sharedDataBytes raw data retrieved from the shared store file.
+     * @param userDataBytes   raw data retrieved from the user store file.
+     * @return new instance of store data.
+     */
+    public static WifiConfigStoreData parseRawData(byte[] sharedDataBytes, byte[] userDataBytes)
+            throws XmlPullParserException, IOException {
+        SharedData sharedData;
+        UserData userData;
+        try {
+            if (sharedDataBytes != null && sharedDataBytes.length > 0) {
+                sharedData = SharedData.parseRawData(sharedDataBytes);
+            } else {
+                sharedData = new SharedData(new ArrayList<WifiConfiguration>());
+            }
+            if (userDataBytes != null && userDataBytes.length > 0) {
+                userData = UserData.parseRawData(userDataBytes);
+            } else {
+                userData = new UserData(new ArrayList<WifiConfiguration>(), new HashSet<String>());
+            }
+            return getStoreData(sharedData, userData);
+        } catch (ClassCastException e) {
+            throw new XmlPullParserException("Wrong value type parsed: " + e);
+        }
+    }
+
+    /**
+     * Create a WifiConfigStoreData instance from the retrieved UserData & SharedData instance.
+     */
+    private static WifiConfigStoreData getStoreData(SharedData sharedData, UserData userData) {
+        List<WifiConfiguration> configurations = new ArrayList<>();
+        configurations.addAll(sharedData.configurations);
+        configurations.addAll(userData.configurations);
+        return new WifiConfigStoreData(configurations, userData.deletedEphemeralSSIDs);
+
+    }
+
+    /**
+     * Write the list of networks to the XML stream.
+     *
+     * @param out            XmlSerializer instance pointing to the XML stream.
+     * @param configurations list of WifiConfiguration objects corresponding to the networks.
+     */
+    private static void writeNetworksToXml(
+            XmlSerializer out, List<WifiConfiguration> configurations)
+            throws XmlPullParserException, IOException {
+        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_NETWORK_LIST);
+        for (WifiConfiguration configuration : configurations) {
+            // Write this configuration data now.
+            XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_NETWORK);
+            writeNetworkToXml(out, configuration);
+            XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_NETWORK);
+        }
+        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_NETWORK_LIST);
+    }
+
+    /**
+     * Write a network to the XML stream.
+     * Nested objects within the provided WifiConfiguration object are written into separate XML
+     * sections.
+     *
+     * @param out           XmlSerializer instance pointing to the XML stream.
+     * @param configuration WifiConfiguration object corresponding to the network.
+     */
+    private static void writeNetworkToXml(
+            XmlSerializer out, WifiConfiguration configuration)
+            throws XmlPullParserException, IOException {
+        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION);
+        WifiConfigurationXmlUtil.writeToXmlForConfigStore(out, configuration);
+        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION);
+
+        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_NETWORK_STATUS);
+        NetworkSelectionStatusXmlUtil.writeToXml(out, configuration.getNetworkSelectionStatus());
+        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_NETWORK_STATUS);
+
+        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_IP_CONFIGURATION);
+        IpConfigurationXmlUtil.writeToXml(out, configuration.getIpConfiguration());
+        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_IP_CONFIGURATION);
+
+        // Store the enterprise configuration for enterprise networks.
+        if (configuration.isEnterprise()) {
+            XmlUtil.writeNextSectionStart(
+                    out, XML_TAG_SECTION_HEADER_WIFI_ENTERPRISE_CONFIGURATION);
+            WifiEnterpriseConfigXmlUtil.writeToXml(out, configuration.enterpriseConfig);
+            XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_WIFI_ENTERPRISE_CONFIGURATION);
+        }
+    }
+
+    /**
+     * Parses the list of networks from the provided XML stream.
+     *
+     * @param in            XmlPullParser instance pointing to the XML stream.
+     * @param outerTagDepth depth of the outer tag in the XML document.
+     * @param dataVersion   version number parsed from incoming data.
+     * @return list of WifiConfiguration objects corresponding to the networks if parsing is
+     * successful, null otherwise.
+     */
+    private static List<WifiConfiguration> parseNetworksFromXml(
+            XmlPullParser in, int outerTagDepth, int dataVersion)
+            throws XmlPullParserException, IOException {
+        // Find the configuration list section.
+        XmlUtil.gotoNextSectionWithName(in, XML_TAG_SECTION_HEADER_NETWORK_LIST, outerTagDepth);
+        // Find all the configurations within the configuration list section.
+        int networkListTagDepth = outerTagDepth + 1;
+        List<WifiConfiguration> configurations = new ArrayList<>();
+        while (XmlUtil.gotoNextSectionWithNameOrEnd(
+                in, XML_TAG_SECTION_HEADER_NETWORK, networkListTagDepth)) {
+            WifiConfiguration configuration =
+                    parseNetworkFromXml(in, networkListTagDepth, dataVersion);
+            if (configuration != null) {
+                configurations.add(configuration);
+            }
+        }
+        return configurations;
+    }
+
+    /**
+     * Helper method to parse the WifiConfiguration object and validate the configKey parsed.
+     */
+    private static WifiConfiguration parseWifiConfigurationFromXmlAndValidateConfigKey(
+            XmlPullParser in, int outerTagDepth)
+            throws XmlPullParserException, IOException {
+        Pair<String, WifiConfiguration> parsedConfig =
+                WifiConfigurationXmlUtil.parseFromXml(in, outerTagDepth);
+        if (parsedConfig == null || parsedConfig.first == null || parsedConfig.second == null) {
+            throw new XmlPullParserException("XML parsing of wifi configuration failed");
+        }
+        String configKeyParsed = parsedConfig.first;
+        WifiConfiguration configuration = parsedConfig.second;
+        String configKeyCalculated = configuration.configKey();
+        if (!configKeyParsed.equals(configKeyCalculated)) {
+            throw new XmlPullParserException(
+                    "Configuration key does not match. Retrieved: " + configKeyParsed
+                            + ", Calculated: " + configKeyCalculated);
+        }
+        return configuration;
+    }
+
+    /**
+     * Parses a network from the provided XML stream.
+     *
+     * @param in            XmlPullParser instance pointing to the XML stream.
+     * @param outerTagDepth depth of the outer tag in the XML document.
+     * @param dataVersion   version number parsed from incoming data.
+     * @return WifiConfiguration object corresponding to the network if parsing is successful,
+     * null otherwise.
+     */
+    private static WifiConfiguration parseNetworkFromXml(
+            XmlPullParser in, int outerTagDepth, int dataVersion)
+            throws XmlPullParserException, IOException {
+        // Any version migration needs to be handled here in future.
+        if (dataVersion == INITIAL_CONFIG_STORE_DATA_VERSION) {
+            WifiConfiguration configuration = null;
+
+            int networkTagDepth = outerTagDepth + 1;
+            XmlUtil.gotoNextSectionWithName(
+                    in, XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION, networkTagDepth);
+            int configTagDepth = networkTagDepth + 1;
+            configuration = parseWifiConfigurationFromXmlAndValidateConfigKey(in, configTagDepth);
+
+            XmlUtil.gotoNextSectionWithName(
+                    in, XML_TAG_SECTION_HEADER_NETWORK_STATUS, networkTagDepth);
+            NetworkSelectionStatus status =
+                    NetworkSelectionStatusXmlUtil.parseFromXml(in, configTagDepth);
+            configuration.setNetworkSelectionStatus(status);
+
+            XmlUtil.gotoNextSectionWithName(
+                    in, XML_TAG_SECTION_HEADER_IP_CONFIGURATION, networkTagDepth);
+            IpConfiguration ipConfiguration =
+                    IpConfigurationXmlUtil.parseFromXml(in, configTagDepth);
+            configuration.setIpConfiguration(ipConfiguration);
+
+            // Check if there is an enterprise configuration section.
+            if (XmlUtil.gotoNextSectionWithNameOrEnd(
+                    in, XML_TAG_SECTION_HEADER_WIFI_ENTERPRISE_CONFIGURATION, networkTagDepth)) {
+                WifiEnterpriseConfig enterpriseConfig =
+                        WifiEnterpriseConfigXmlUtil.parseFromXml(in, configTagDepth);
+                configuration.enterpriseConfig = enterpriseConfig;
+            }
+
+            return configuration;
+        }
+        return null;
+    }
+
+    /**
+     * Write the document start and version to the XML stream.
+     * This is used for both the shared and user config store data.
+     *
+     * @param out XmlSerializer instance pointing to the XML stream.
+     */
+    private static void writeDocumentStartAndVersionToXml(XmlSerializer out)
+            throws XmlPullParserException, IOException {
+        XmlUtil.writeDocumentStart(out, XML_TAG_DOCUMENT_HEADER);
+        XmlUtil.writeNextValue(out, XML_TAG_VERSION, CURRENT_CONFIG_STORE_DATA_VERSION);
+    }
+
+    /**
+     * Parse the document start and version from the XML stream.
+     * This is used for both the shared and user config store data.
+     *
+     * @param in XmlPullParser instance pointing to the XML stream.
+     * @return version number retrieved from the Xml stream.
+     */
+    private static int parseDocumentStartAndVersionFromXml(XmlPullParser in)
+            throws XmlPullParserException, IOException {
+        XmlUtil.gotoDocumentStart(in, XML_TAG_DOCUMENT_HEADER);
+        int version = (int) XmlUtil.readNextValueWithName(in, XML_TAG_VERSION);
+        if (version < INITIAL_CONFIG_STORE_DATA_VERSION
+                || version > CURRENT_CONFIG_STORE_DATA_VERSION) {
+            throw new XmlPullParserException("Invalid version of data: " + version);
+        }
+        return version;
+    }
+
+    /**
+     * Create raw byte array to be stored in the share store file.
+     * This method serializes the data to a byte array in XML format.
+     *
+     * @return byte array with the serialized output.
+     */
+    public byte[] createSharedRawData() throws XmlPullParserException, IOException {
+        SharedData sharedData = getSharedData();
+        return sharedData.createRawData();
+    }
+
+    /**
+     * Create raw byte array to be stored in the user store file.
+     * This method serializes the data to a byte array in XML format.
+     *
+     * @return byte array with the serialized output.
+     */
+    public byte[] createUserRawData() throws XmlPullParserException, IOException {
+        UserData userData = getUserData();
+        return userData.createRawData();
+    }
+
+    /**
+     * Retrieve the shared data to be stored in the shared config store file.
+     *
+     * @return SharedData instance.
+     */
+    private SharedData getSharedData() {
+        List<WifiConfiguration> sharedConfigurations = new ArrayList<>();
+        for (WifiConfiguration configuration : configurations) {
+            if (configuration.shared) {
+                sharedConfigurations.add(configuration);
+            }
+        }
+        return new SharedData(sharedConfigurations);
+    }
+
+    /**
+     * Retrieve the user specific data to be stored in the user config store file.
+     *
+     * @return UserData instance.
+     */
+    private UserData getUserData() {
+        List<WifiConfiguration> userConfigurations = new ArrayList<>();
+        for (WifiConfiguration configuration : configurations) {
+            if (!configuration.shared) {
+                userConfigurations.add(configuration);
+            }
+        }
+        return new UserData(userConfigurations, deletedEphemeralSSIDs);
+    }
+
+    /**
+     * Class to encapsulate all the data to be stored in the shared store.
+     */
+    public static class SharedData {
+        public List<WifiConfiguration> configurations;
+
+        /**
+         * Create a new instance of shared store data to be written to the store files.
+         *
+         * @param configurations list of shared saved networks to be stored.
+         */
+        public SharedData(List<WifiConfiguration> configurations) {
+            this.configurations = configurations;
+        }
+
+        /**
+         * Create a new instance of the shared store data parsed from the store file.
+         * This method deserializes the provided byte array in XML format to a new SharedData
+         * instance.
+         *
+         * @param sharedDataBytes raw data retrieved from the shared store file.
+         * @return new instance of store data.
+         */
+        public static SharedData parseRawData(byte[] sharedDataBytes)
+                throws XmlPullParserException, IOException{
+            final XmlPullParser in = Xml.newPullParser();
+            final ByteArrayInputStream inputStream = new ByteArrayInputStream(sharedDataBytes);
+            in.setInput(inputStream, StandardCharsets.UTF_8.name());
+
+            // Start parsing the XML stream.
+            int rootTagDepth = in.getDepth() + 1;
+            int version = parseDocumentStartAndVersionFromXml(in);
+
+            List<WifiConfiguration> configurations =
+                    parseNetworksFromXml(in, rootTagDepth, version);
+
+            return new SharedData(configurations);
+        }
+
+        /**
+         * Create raw byte array to be stored in the store file.
+         * This method serializes the data to a byte array in XML format.
+         *
+         * @return byte array with the serialized output.
+         */
+        public byte[] createRawData() throws XmlPullParserException, IOException  {
+            final XmlSerializer out = new FastXmlSerializer();
+            final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+            out.setOutput(outputStream, StandardCharsets.UTF_8.name());
+
+            // Start writing the XML stream.
+            writeDocumentStartAndVersionToXml(out);
+
+            // Write all the shared network configurations.
+            writeNetworksToXml(out, configurations);
+
+            XmlUtil.writeDocumentEnd(out, XML_TAG_DOCUMENT_HEADER);
+
+            byte[] data = outputStream.toByteArray();
+
+            return data;
+        }
+    }
+
+    /**
+     * Class to encapsulate all the data to be stored in the user specific store.
+     */
+    public static class UserData {
+        private static final String XML_TAG_SSID_LIST = "SSIDList";
+
+        public List<WifiConfiguration> configurations;
+        public Set<String> deletedEphemeralSSIDs;
+
+        /**
+         * Create a new instance of user specific store data to be written to the store files.
+         *
+         * @param configurations        list of user specific saved networks to be stored.
+         * @param deletedEphemeralSSIDs list of deleted ephemeral ssids to be stored.
+         */
+        public UserData(List<WifiConfiguration> configurations, Set<String> deletedEphemeralSSIDs) {
+            this.configurations = configurations;
+            this.deletedEphemeralSSIDs = deletedEphemeralSSIDs;
+        }
+
+        /**
+         * Create a new instance of the user store data parsed from the store file.
+         * This method deserializes the provided byte array in XML format to a new UserData
+         * instance.
+         *
+         * @param userDataBytes raw data retrieved from the user store file.
+         * @return new instance of store data.
+         */
+        public static UserData parseRawData(byte[] userDataBytes)
+            throws XmlPullParserException, IOException {
+            final XmlPullParser in = Xml.newPullParser();
+            final ByteArrayInputStream inputStream = new ByteArrayInputStream(userDataBytes);
+            in.setInput(inputStream, StandardCharsets.UTF_8.name());
+
+            // Start parsing the XML stream.
+            int rootTagDepth = in.getDepth() + 1;
+            int version = parseDocumentStartAndVersionFromXml(in);
+
+            List<WifiConfiguration> configurations =
+                    parseNetworksFromXml(in, rootTagDepth, version);
+
+            XmlUtil.gotoNextSectionWithName(
+                    in, XML_TAG_SECTION_HEADER_DELETED_EPHEMERAL_SSID_LIST, rootTagDepth);
+            Set<String> deletedEphemralList =
+                    (Set<String>) XmlUtil.readNextValueWithName(in, XML_TAG_SSID_LIST);
+
+            return new UserData(configurations, deletedEphemralList);
+        }
+
+        /**
+         * Create raw byte array to be stored in the store file.
+         * This method serializes the data to a byte array in XML format.
+         *
+         * @return byte array with the serialized output.
+         */
+        public byte[] createRawData() throws XmlPullParserException, IOException {
+            final XmlSerializer out = new FastXmlSerializer();
+            final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+            out.setOutput(outputStream, StandardCharsets.UTF_8.name());
+
+            // Start writing the XML stream.
+            writeDocumentStartAndVersionToXml(out);
+
+            // Write all the user network configurations.
+            writeNetworksToXml(out, configurations);
+
+            XmlUtil.writeNextSectionStart(
+                    out, XML_TAG_SECTION_HEADER_DELETED_EPHEMERAL_SSID_LIST);
+            XmlUtil.writeNextValue(out, XML_TAG_SSID_LIST, deletedEphemeralSSIDs);
+            XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_DELETED_EPHEMERAL_SSID_LIST);
+
+            XmlUtil.writeDocumentEnd(out, XML_TAG_DOCUMENT_HEADER);
+
+            byte[] data = outputStream.toByteArray();
+
+            return data;
+        }
+    }
+}
+
+
diff --git a/service/java/com/android/server/wifi/WifiConfigStoreNew.java b/service/java/com/android/server/wifi/WifiConfigStoreNew.java
new file mode 100644
index 0000000..cd0e1d2
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiConfigStoreNew.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.app.AlarmManager;
+import android.content.Context;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.AtomicFile;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * This class provides the API's to save/load/modify network configurations from a persistent
+ * store. Uses keystore for certificate/key management operations.
+ * NOTE: This class should only be used from WifiConfigManager and is not thread-safe!
+ */
+public class WifiConfigStoreNew {
+    /**
+     * Alarm tag to use for starting alarms for buffering file writes.
+     */
+    @VisibleForTesting
+    public static final String BUFFERED_WRITE_ALARM_TAG = "WriteBufferAlarm";
+    /**
+     * Log tag.
+     */
+    private static final String TAG = "WifiConfigStoreNew";
+    /**
+     * Config store file name for both shared & user specific stores.
+     */
+    private static final String STORE_FILE_NAME = "WifiConfigStore.xml";
+    /**
+     * Directory to store the shared config store file.
+     */
+    private static final String SHARED_STORE_DIRECTORY =
+            new File(Environment.getDataDirectory(), "misc/wifi").getAbsolutePath();
+    /**
+     * Time interval for buffering file writes for non-forced writes
+     */
+    private static final int BUFFERED_WRITE_ALARM_INTERVAL_MS = 10 * 1000;
+    /**
+     * Handler instance to post alarm timeouts to
+     */
+    private final Handler mEventHandler;
+    /**
+     * Alarm manager instance to start buffer timeout alarms.
+     */
+    private final AlarmManager mAlarmManager;
+    /**
+     * Clock instance to retrieve timestamps for alarms.
+     */
+    private final Clock mClock;
+    /**
+     * Shared config store file instance.
+     */
+    private StoreFile mSharedStore;
+    /**
+     * User specific store file instance.
+     */
+    private StoreFile mUserStore;
+    /**
+     * Verbose logging flag.
+     */
+    private boolean mVerboseLoggingEnabled = false;
+    /**
+     * Flag to indicate if there is a buffered write pending.
+     */
+    private boolean mBufferedWritePending = false;
+    /**
+     * Alarm listener for flushing out any buffered writes.
+     */
+    private final AlarmManager.OnAlarmListener mBufferedWriteListener =
+            new AlarmManager.OnAlarmListener() {
+                public void onAlarm() {
+                    try {
+                        writeBufferedData();
+                    } catch (IOException e) {
+                        Log.wtf(TAG, "Buffered write failed");
+                    }
+
+                }
+            };
+
+    /**
+     * Create a new instance of WifiConfigStore.
+     * Note: The store file instances have been made inputs to this class to ease unit-testing.
+     *
+     * @param context     context to use for retrieving the alarm manager.
+     * @param looper      looper instance to post alarm timeouts to.
+     * @param clock       clock instance to retrieve timestamps for alarms.
+     * @param sharedStore StoreFile instance pointing to the shared store file. This should
+     *                    be retrieved using {@link #createSharedFile()} method.
+     * @param userStore   StoreFile instance pointing to the user specific store file. This should
+     *                    be retrieved using {@link #createUserFile(int)} method.
+     */
+    public WifiConfigStoreNew(Context context, Looper looper, Clock clock,
+            StoreFile sharedStore, StoreFile userStore) {
+
+        mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+        mEventHandler = new Handler(looper);
+        mClock = clock;
+
+        // Initialize the store files.
+        mSharedStore = sharedStore;
+        mUserStore = userStore;
+    }
+
+    /**
+     * Create a new instance of the shared store file.
+     *
+     * @return new instance of the store file.
+     */
+    public static StoreFile createSharedFile() {
+        return new StoreFile(new File(SHARED_STORE_DIRECTORY, STORE_FILE_NAME));
+    }
+
+    /**
+     * Create a new instance of the user specific store file.
+     * The user store file is inside the user's encrypted data directory.
+     *
+     * @param userId userId corresponding to the currently logged-in user.
+     * @return new instance of the store file.
+     */
+    public static StoreFile createUserFile(int userId) {
+        final String userDir = Environment.getDataSystemCeDirectory(userId).getAbsolutePath();
+        return new StoreFile(new File(userDir, STORE_FILE_NAME));
+    }
+
+    /**
+     * Enable verbose logging.
+     */
+    public void enableVerboseLogging(boolean verbose) {
+        mVerboseLoggingEnabled = verbose;
+    }
+
+    /**
+     * API to write the provided store data to config stores.
+     * The method writes the user specific configurations to user specific config store and the
+     * shared configurations to shared config store.
+     *
+     * @param forceSync boolean to force write the config stores now. if false, the writes are
+     *                  buffered and written after the configured interval.
+     * @param storeData The entire data to be stored across all the config store files.
+     */
+    public void write(boolean forceSync, WifiConfigStoreData storeData)
+            throws XmlPullParserException, IOException {
+        // Serialize the provided data and send it to the respective stores. The actual write will
+        // be performed later depending on the |forceSync| flag .
+        byte[] sharedDataBytes = storeData.createSharedRawData();
+        byte[] userDataBytes = storeData.createUserRawData();
+
+        mSharedStore.storeRawDataToWrite(sharedDataBytes);
+        mUserStore.storeRawDataToWrite(userDataBytes);
+
+        // Every write provides a new snapshot to be persisted, so |forceSync| flag overrides any
+        // pending buffer writes.
+        if (forceSync) {
+            writeBufferedData();
+        } else {
+            startBufferedWriteAlarm();
+        }
+    }
+
+    /**
+     * Helper method to start a buffered write alarm if one doesn't already exist.
+     */
+    private void startBufferedWriteAlarm() {
+        if (!mBufferedWritePending) {
+            mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                    mClock.getElapsedSinceBootMillis() + BUFFERED_WRITE_ALARM_INTERVAL_MS,
+                    BUFFERED_WRITE_ALARM_TAG, mBufferedWriteListener, mEventHandler);
+            mBufferedWritePending = true;
+        }
+    }
+
+    /**
+     * Helper method to stop a buffered write alarm if one exists.
+     */
+    private void stopBufferedWriteAlarm() {
+        if (mBufferedWritePending) {
+            mAlarmManager.cancel(mBufferedWriteListener);
+            mBufferedWritePending = false;
+        }
+    }
+
+    /**
+     * Helper method to actually perform the writes to the file. This flushes out any write data
+     * being buffered in the respective stores and cancels any pending buffer write alarms.
+     */
+    private void writeBufferedData() throws IOException {
+        stopBufferedWriteAlarm();
+        mSharedStore.writeBufferedRawData();
+        mUserStore.writeBufferedRawData();
+    }
+
+    /**
+     * API to read the store data from the config stores.
+     * The method reads the user specific configurations from user specific config store and the
+     * shared configurations from the shared config store.
+     *
+     * @return storeData The entire data retrieved across all the config store files.
+     */
+    public WifiConfigStoreData read() throws XmlPullParserException, IOException {
+        byte[] sharedDataBytes = mSharedStore.readRawData();
+        byte[] userDataBytes = mUserStore.readRawData();
+
+        return WifiConfigStoreData.parseRawData(sharedDataBytes, userDataBytes);
+    }
+
+    /**
+     * Handle a user switch. This changes the user specific store.
+     *
+     * @param userStore StoreFile instance pointing to the user specific store file. This should
+     *                  be retrieved using {@link #createUserFile(int)} method.
+     */
+    public void handleUserSwitch(StoreFile userStore) throws IOException {
+        // Flush out any stored data if present before switching the user stores.
+        writeBufferedData();
+        mUserStore = userStore;
+    }
+
+    /**
+     * Class to encapsulate all file writes. This is a wrapper over {@link AtomicFile} to write/read
+     * raw data from the persistent file. This class provides helper methods to read/write the
+     * entire file into a byte array.
+     * This helps to separate out the processing/parsing from the actual file writing.
+     */
+    public static class StoreFile {
+        /**
+         * File permissions to lock down the file.
+         */
+        private static final int FILE_MODE = 0600;
+        /**
+         * The store file to be written to.
+         */
+        private final AtomicFile mAtomicFile;
+        /**
+         * This is an intermediate buffer to store the data to be written.
+         */
+        private byte[] mWriteData;
+        /**
+         * Store the file name for setting the file permissions/logging purposes.
+         */
+        private String mFileName;
+
+        public StoreFile(File file) {
+            mAtomicFile = new AtomicFile(file);
+            mFileName = mAtomicFile.getBaseFile().getAbsolutePath();
+        }
+
+        /**
+         * Read the entire raw data from the store file and return in a byte array.
+         *
+         * @return raw data read from the file or null if the file is not found.
+         * @throws IOException if an error occurs. The input stream is always closed by the method
+         * even when an exception is encountered.
+         */
+        public byte[] readRawData() throws IOException {
+            try {
+                return mAtomicFile.readFully();
+            } catch (FileNotFoundException e) {
+                return null;
+            }
+        }
+
+        /**
+         * Store the provided byte array to be written when {@link #writeBufferedRawData()} method
+         * is invoked.
+         * This intermediate step is needed to help in buffering file writes.
+         *
+         * @param data raw data to be written to the file.
+         */
+        public void storeRawDataToWrite(byte[] data) {
+            mWriteData = data;
+        }
+
+        /**
+         * Write the stored raw data to the store file.
+         * After the write to file, the mWriteData member is reset.
+         * @throws IOException if an error occurs. The output stream is always closed by the method
+         * even when an exception is encountered.
+         */
+        public void writeBufferedRawData() throws IOException {
+            if (mWriteData == null) {
+                Log.w(TAG, "No data stored for writing to file: " + mFileName);
+                return;
+            }
+            // Write the data to the atomic file.
+            FileOutputStream out = null;
+            try {
+                out = mAtomicFile.startWrite();
+                FileUtils.setPermissions(mFileName, FILE_MODE, -1, -1);
+                out.write(mWriteData);
+                mAtomicFile.finishWrite(out);
+            } catch (IOException e) {
+                if (out != null) {
+                    mAtomicFile.failWrite(out);
+                }
+                throw e;
+            }
+            // Reset the pending write data after write.
+            mWriteData = null;
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/WifiConfigurationUtil.java b/service/java/com/android/server/wifi/WifiConfigurationUtil.java
index 7d0fb3c..9059c45 100644
--- a/service/java/com/android/server/wifi/WifiConfigurationUtil.java
+++ b/service/java/com/android/server/wifi/WifiConfigurationUtil.java
@@ -23,14 +23,17 @@
 import java.util.List;
 
 /**
- * Helper for working with {@link android.net.wifi.WifiConfiguration} objects.
+ * WifiConfiguration utility for any {@link android.net.wifi.WifiConfiguration} related operations.
+ * Currently contains:
+ *   > Helper method to check if the WifiConfiguration object is visible to the provided users.
+ *   > Helper methods to identify the encryption of a WifiConfiguration object.
  */
 public class WifiConfigurationUtil {
     /**
      * Check whether a network configuration is visible to a user or any of its managed profiles.
      * @param config the network configuration whose visibility should be checked
      * @param profiles the user IDs of the user itself and all its managed profiles (can be obtained
-     *         via {@link android.os.UserManager.getProfiles})
+     *         via {@link android.os.UserManager#getProfiles})
      * @return whether the network configuration is visible to the user or any of its managed
      *         profiles
      */
@@ -46,4 +49,48 @@
         }
         return false;
     }
+
+    /**
+     * Checks if the provided |wepKeys| array contains any non-null value;
+     */
+    public static boolean hasAnyValidWepKey(String[] wepKeys) {
+        for (int i = 0; i < wepKeys.length; i++) {
+            if (wepKeys[i] != null) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Helper method to check if the provided |config| corresponds to a PSK network or not.
+     */
+    public static boolean isConfigForPskNetwork(WifiConfiguration config) {
+        return config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK);
+    }
+
+    /**
+     * Helper method to check if the provided |config| corresponds to a EAP network or not.
+     */
+    public static boolean isConfigForEapNetwork(WifiConfiguration config) {
+        return (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP)
+                || config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X));
+    }
+
+    /**
+     * Helper method to check if the provided |config| corresponds to a WEP network or not.
+     */
+    public static boolean isConfigForWepNetwork(WifiConfiguration config) {
+        return (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)
+                && hasAnyValidWepKey(config.wepKeys));
+    }
+
+    /**
+     * Helper method to check if the provided |config| corresponds to an open network or not.
+     */
+    public static boolean isConfigForOpenNetwork(WifiConfiguration config) {
+        return !(isConfigForWepNetwork(config) || isConfigForPskNetwork(config)
+                || isConfigForEapNetwork(config));
+    }
+
 }
diff --git a/service/java/com/android/server/wifi/WifiConnectivityManager.java b/service/java/com/android/server/wifi/WifiConnectivityManager.java
index 7f3d5d7..b73d149 100644
--- a/service/java/com/android/server/wifi/WifiConnectivityManager.java
+++ b/service/java/com/android/server/wifi/WifiConnectivityManager.java
@@ -36,7 +36,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.wifi.util.ScanDetailUtil;
+import com.android.server.wifi.util.ScanResultUtil;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -220,10 +220,9 @@
         localLog(listenerName + " onResults: start QNS");
         WifiConfiguration candidate =
                 mQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                mUntrustedConnectionAllowed, scanDetails,
-                mStateMachine.isLinkDebouncing(), mStateMachine.isConnected(),
-                mStateMachine.isDisconnected(),
-                mStateMachine.isSupplicantTransientState());
+                mUntrustedConnectionAllowed, mStateMachine.isLinkDebouncing(),
+                mStateMachine.isConnected(), mStateMachine.isDisconnected(),
+                mStateMachine.isSupplicantTransientState(), scanDetails);
         mWifiLastResortWatchdog.updateAvailableNetworks(
                 mQualifiedNetworkSelector.getFilteredScanDetails());
         if (candidate != null) {
@@ -286,7 +285,7 @@
                             + fullScanResult.capabilities);
             }
 
-            mScanDetails.add(ScanDetailUtil.toScanDetail(fullScanResult));
+            mScanDetails.add(ScanResultUtil.toScanDetail(fullScanResult));
         }
     }
 
@@ -355,7 +354,7 @@
                             + fullScanResult.capabilities);
             }
 
-            mScanDetails.add(ScanDetailUtil.toScanDetail(fullScanResult));
+            mScanDetails.add(ScanResultUtil.toScanDetail(fullScanResult));
         }
     }
 
@@ -479,7 +478,7 @@
             localLog("PnoScanListener: onPnoNetworkFound: results len = " + results.length);
 
             for (ScanResult result: results) {
-                mScanDetails.add(ScanDetailUtil.toScanDetail(result));
+                mScanDetails.add(ScanResultUtil.toScanDetail(result));
             }
 
             boolean wasConnectAttempted;
@@ -615,7 +614,7 @@
             return;
         }
 
-        Long elapsedTimeMillis = mClock.elapsedRealtime();
+        Long elapsedTimeMillis = mClock.getElapsedSinceBootMillis();
         if (!mScreenOn && shouldSkipConnectionAttempt(elapsedTimeMillis)) {
             localLog("connectToNetwork: Too many connection attempts. Skipping this attempt!");
             mTotalConnectivityAttemptsRateLimited++;
@@ -705,7 +704,7 @@
 
     // Start a single scan and set up the interval for next single scan.
     private void startPeriodicSingleScan() {
-        long currentTimeStamp = mClock.elapsedRealtime();
+        long currentTimeStamp = mClock.getElapsedSinceBootMillis();
 
         if (mLastPeriodicSingleScanTimeStamp != RESET_TIME_STAMP) {
             long msSinceLastScan = currentTimeStamp - mLastPeriodicSingleScanTimeStamp;
@@ -923,7 +922,7 @@
         Log.i(TAG, "scheduleWatchdogTimer");
 
         mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                            mClock.elapsedRealtime() + WATCHDOG_INTERVAL_MS,
+                            mClock.getElapsedSinceBootMillis() + WATCHDOG_INTERVAL_MS,
                             WATCHDOG_TIMER_TAG,
                             mWatchdogListener, mEventHandler);
     }
@@ -931,7 +930,7 @@
     // Set up periodic scan timer
     private void schedulePeriodicScanTimer(int intervalMs) {
         mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                            mClock.elapsedRealtime() + intervalMs,
+                            mClock.getElapsedSinceBootMillis() + intervalMs,
                             PERIODIC_SCAN_TIMER_TAG,
                             mPeriodicScanTimerListener, mEventHandler);
         mPeriodicScanTimerSet = true;
@@ -952,7 +951,7 @@
         RestartSingleScanListener restartSingleScanListener =
                 new RestartSingleScanListener(isFullBandScan);
         mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                            mClock.elapsedRealtime() + RESTART_SCAN_DELAY_MS,
+                            mClock.getElapsedSinceBootMillis() + RESTART_SCAN_DELAY_MS,
                             RESTART_SINGLE_SCAN_TIMER_TAG,
                             restartSingleScanListener, mEventHandler);
     }
@@ -962,7 +961,7 @@
         localLog("scheduleDelayedConnectivityScan");
 
         mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                            mClock.elapsedRealtime() + msFromNow,
+                            mClock.getElapsedSinceBootMillis() + msFromNow,
                             RESTART_CONNECTIVITY_SCAN_TIMER_TAG,
                             mRestartScanListener, mEventHandler);
 
diff --git a/service/java/com/android/server/wifi/WifiInjector.java b/service/java/com/android/server/wifi/WifiInjector.java
index 6939d45..cc117da 100644
--- a/service/java/com/android/server/wifi/WifiInjector.java
+++ b/service/java/com/android/server/wifi/WifiInjector.java
@@ -16,34 +16,170 @@
 
 package com.android.server.wifi;
 
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.wifi.IApInterface;
+import android.net.wifi.IWificond;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.INetworkManagementService;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.os.UserManager;
+import android.provider.Settings;
 import android.security.KeyStore;
 
+import com.android.internal.R;
+import com.android.server.am.BatteryStatsService;
+
+import java.util.ArrayList;
+
 /**
- *  WiFi dependency injector using thread-safe lazy singleton pattern. To be used for accessing
- *  various wifi class instances and as a handle for mock injection.
+ *  WiFi dependency injector. To be used for accessing various WiFi class instances and as a
+ *  handle for mock injection.
+ *
+ *  Some WiFi class instances currently depend on having a Looper from a HandlerThread that has
+ *  been started. To accommodate this, we have a two-phased approach to initialize and retrieve
+ *  an instance of the WifiInjector.
  */
 public class WifiInjector {
-    // see: https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom
-    private static class LazyHolder {
-        public static final WifiInjector sInstance = new WifiInjector();
-    }
+    private static final String BOOT_DEFAULT_WIFI_COUNTRY_CODE = "ro.boot.wificountrycode";
+    private static final String WIFICOND_SERVICE_NAME = "wificond";
 
-    public static WifiInjector getInstance() {
-        return LazyHolder.sInstance;
-    }
+    static WifiInjector sWifiInjector = null;
 
+    private final Context mContext;
+    private final FrameworkFacade mFrameworkFacade = new FrameworkFacade();
+    private final HandlerThread mWifiServiceHandlerThread;
+    private final HandlerThread mWifiStateMachineHandlerThread;
+    private final WifiTrafficPoller mTrafficPoller;
+    private final WifiCountryCode mCountryCode;
+    private final BackupManagerProxy mBackupManagerProxy = new BackupManagerProxy();
+    private final WifiStateMachine mWifiStateMachine;
+    private final WifiSettingsStore mSettingsStore;
+    private final WifiCertManager mCertManager;
+    private final WifiNotificationController mNotificationController;
+    private final WifiLockManager mLockManager;
+    private final WifiController mWifiController;
     private final Clock mClock = new Clock();
     private final WifiMetrics mWifiMetrics = new WifiMetrics(mClock);
-    private final WifiLastResortWatchdog mWifiLastResortWatchdog =
-            new WifiLastResortWatchdog(mWifiMetrics);
+    private final WifiLastResortWatchdog mWifiLastResortWatchdog;
     private final PropertyService mPropertyService = new SystemPropertyService();
     private final BuildProperties mBuildProperties = new SystemBuildProperties();
     private final KeyStore mKeyStore = KeyStore.getInstance();
+    private final WifiBackupRestore mWifiBackupRestore = new WifiBackupRestore();
+    private final WifiMulticastLockManager mWifiMulticastLockManager;
+
+    public WifiInjector(Context context) {
+        if (context == null) {
+            throw new IllegalStateException(
+                    "WifiInjector should not be initialized with a null Context.");
+        }
+
+        if (sWifiInjector != null) {
+            throw new IllegalStateException(
+                    "WifiInjector was already created, use getInstance instead.");
+        }
+
+        sWifiInjector = this;
+
+        mContext = context;
+        // Now create and start handler threads
+        mWifiServiceHandlerThread = new HandlerThread("WifiService");
+        mWifiServiceHandlerThread.start();
+        mWifiStateMachineHandlerThread = new HandlerThread("WifiStateMachine");
+        mWifiStateMachineHandlerThread.start();
+
+        // Now get instances of all the objects that depend on the HandlerThreads
+        mTrafficPoller =  new WifiTrafficPoller(mContext, mWifiServiceHandlerThread.getLooper(),
+                WifiNative.getWlanNativeInterface().getInterfaceName());
+        mCountryCode = new WifiCountryCode(WifiNative.getWlanNativeInterface(),
+                SystemProperties.get(BOOT_DEFAULT_WIFI_COUNTRY_CODE),
+                mFrameworkFacade.getStringSetting(mContext, Settings.Global.WIFI_COUNTRY_CODE),
+                mContext.getResources()
+                        .getBoolean(R.bool.config_wifi_revert_country_code_on_cellular_loss));
+        mWifiStateMachine = new WifiStateMachine(mContext, mFrameworkFacade,
+                mWifiStateMachineHandlerThread.getLooper(), UserManager.get(mContext),
+                this, mBackupManagerProxy, mCountryCode);
+        mSettingsStore = new WifiSettingsStore(mContext);
+        mCertManager = new WifiCertManager(mContext);
+        mNotificationController = new WifiNotificationController(mContext,
+                mWifiServiceHandlerThread.getLooper(), mWifiStateMachine,
+                mFrameworkFacade, null);
+        mLockManager = new WifiLockManager(mContext, BatteryStatsService.getService());
+        mWifiController = new WifiController(mContext, mWifiStateMachine, mSettingsStore,
+                mLockManager, mWifiServiceHandlerThread.getLooper(), mFrameworkFacade);
+        mWifiLastResortWatchdog = new WifiLastResortWatchdog(mWifiController, mWifiMetrics);
+        mWifiMulticastLockManager = new WifiMulticastLockManager(mWifiStateMachine,
+                BatteryStatsService.getService());
+    }
+
+    /**
+     *  Obtain an instance of the WifiInjector class.
+     *
+     *  This is the generic method to get an instance of the class. The first instance should be
+     *  retrieved using the getInstanceWithContext method.
+     */
+    public static WifiInjector getInstance() {
+        if (sWifiInjector == null) {
+            throw new IllegalStateException(
+                    "Attempted to retrieve a WifiInjector instance before constructor was called.");
+        }
+        return sWifiInjector;
+    }
 
     public WifiMetrics getWifiMetrics() {
         return mWifiMetrics;
     }
 
+    public BackupManagerProxy getBackupManagerProxy() {
+        return mBackupManagerProxy;
+    }
+
+    public FrameworkFacade getFrameworkFacade() {
+        return mFrameworkFacade;
+    }
+
+    public HandlerThread getWifiServiceHandlerThread() {
+        return mWifiServiceHandlerThread;
+    }
+
+    public HandlerThread getWifiStateMachineHandlerThread() {
+        return mWifiStateMachineHandlerThread;
+    }
+
+    public WifiTrafficPoller getWifiTrafficPoller() {
+        return mTrafficPoller;
+    }
+
+    public WifiCountryCode getWifiCountryCode() {
+        return mCountryCode;
+    }
+
+    public WifiStateMachine getWifiStateMachine() {
+        return mWifiStateMachine;
+    }
+
+    public WifiSettingsStore getWifiSettingsStore() {
+        return mSettingsStore;
+    }
+
+    public WifiCertManager getWifiCertManager() {
+        return mCertManager;
+    }
+
+    public WifiNotificationController getWifiNotificationController() {
+        return mNotificationController;
+    }
+
+    public WifiLockManager getWifiLockManager() {
+        return mLockManager;
+    }
+
+    public WifiController getWifiController() {
+        return mWifiController;
+    }
+
     public WifiLastResortWatchdog getWifiLastResortWatchdog() {
         return mWifiLastResortWatchdog;
     }
@@ -56,9 +192,47 @@
         return mPropertyService;
     }
 
-    public BuildProperties getBuildProperties() { return mBuildProperties; }
+    public BuildProperties getBuildProperties() {
+        return mBuildProperties;
+    }
 
     public KeyStore getKeyStore() {
         return mKeyStore;
     }
+
+    public WifiBackupRestore getWifiBackupRestore() {
+        return mWifiBackupRestore;
+    }
+
+    public WifiMulticastLockManager getWifiMulticastLockManager() {
+        return mWifiMulticastLockManager;
+    }
+
+    public IWificond makeWificond() {
+        // We depend on being able to refresh our binder in WifiStateMachine, so don't cache it.
+        IBinder binder = ServiceManager.getService(WIFICOND_SERVICE_NAME);
+        return IWificond.Stub.asInterface(binder);
+    }
+
+    /**
+     * Create a SoftApManager.
+     * @param wifiNative reference to WifiNative
+     * @param nmService reference to NetworkManagementService
+     * @param cm reference to ConnectivityManager
+     * @param countryCode Country code
+     * @param allowed2GChannels list of allowed 2G channels
+     * @param listener listener for SoftApManager
+     * @param apInterface network interface to start hostapd against
+     * @return an instance of SoftApManager
+     */
+    public SoftApManager makeSoftApManager(
+            WifiNative wifiNative,
+            INetworkManagementService nmService, ConnectivityManager cm,
+            String countryCode, ArrayList<Integer> allowed2GChannels,
+            SoftApManager.Listener listener, IApInterface apInterface) {
+        return new SoftApManager(
+                mWifiServiceHandlerThread.getLooper(),
+                wifiNative, nmService, countryCode,
+                allowed2GChannels, listener, apInterface);
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiKeyStore.java b/service/java/com/android/server/wifi/WifiKeyStore.java
new file mode 100644
index 0000000..f921988
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiKeyStore.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiEnterpriseConfig;
+import android.os.Process;
+import android.security.Credentials;
+import android.security.KeyChain;
+import android.security.KeyStore;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.Log;
+
+import java.io.IOException;
+import java.security.Key;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * This class provides the methods to access keystore for certificate management.
+ *
+ * NOTE: This class should only be used from WifiConfigManager!
+ */
+public class WifiKeyStore {
+    private static final String TAG = "WifiKeyStore";
+
+    private boolean mVerboseLoggingEnabled = false;
+
+    private final KeyStore mKeyStore;
+
+    WifiKeyStore(KeyStore keyStore) {
+        mKeyStore = keyStore;
+    }
+
+    /**
+     * Enable verbose logging.
+     */
+    void enableVerboseLogging(boolean verbose) {
+        mVerboseLoggingEnabled = verbose;
+    }
+
+    // Certificate and private key management for EnterpriseConfig
+    private static boolean needsKeyStore(WifiEnterpriseConfig config) {
+        return (config.getClientCertificate() != null || config.getCaCertificate() != null);
+    }
+
+    private static boolean isHardwareBackedKey(Key key) {
+        return KeyChain.isBoundKeyAlgorithm(key.getAlgorithm());
+    }
+
+    private static boolean hasHardwareBackedKey(Certificate certificate) {
+        return isHardwareBackedKey(certificate.getPublicKey());
+    }
+
+    /**
+     * Install keys for given enterprise network.
+     *
+     * @param existingConfig Existing config corresponding to the network already stored in our
+     *                       database. This maybe null if it's a new network.
+     * @param config         Config corresponding to the network.
+     * @return true if successful, false otherwise.
+     */
+    private boolean installKeys(WifiEnterpriseConfig existingConfig, WifiEnterpriseConfig config,
+            String name) {
+        boolean ret = true;
+        String privKeyName = Credentials.USER_PRIVATE_KEY + name;
+        String userCertName = Credentials.USER_CERTIFICATE + name;
+        if (config.getClientCertificate() != null) {
+            byte[] privKeyData = config.getClientPrivateKey().getEncoded();
+            if (mVerboseLoggingEnabled) {
+                if (isHardwareBackedKey(config.getClientPrivateKey())) {
+                    Log.d(TAG, "importing keys " + name + " in hardware backed store");
+                } else {
+                    Log.d(TAG, "importing keys " + name + " in software backed store");
+                }
+            }
+            ret = mKeyStore.importKey(privKeyName, privKeyData, Process.WIFI_UID,
+                    KeyStore.FLAG_NONE);
+
+            if (!ret) {
+                return ret;
+            }
+
+            ret = putCertInKeyStore(userCertName, config.getClientCertificate());
+            if (!ret) {
+                // Remove private key installed
+                mKeyStore.delete(privKeyName, Process.WIFI_UID);
+                return ret;
+            }
+        }
+
+        X509Certificate[] caCertificates = config.getCaCertificates();
+        Set<String> oldCaCertificatesToRemove = new ArraySet<>();
+        if (existingConfig != null && existingConfig.getCaCertificateAliases() != null) {
+            oldCaCertificatesToRemove.addAll(
+                    Arrays.asList(existingConfig.getCaCertificateAliases()));
+        }
+        List<String> caCertificateAliases = null;
+        if (caCertificates != null) {
+            caCertificateAliases = new ArrayList<>();
+            for (int i = 0; i < caCertificates.length; i++) {
+                String alias = caCertificates.length == 1 ? name
+                        : String.format("%s_%d", name, i);
+
+                oldCaCertificatesToRemove.remove(alias);
+                ret = putCertInKeyStore(Credentials.CA_CERTIFICATE + alias, caCertificates[i]);
+                if (!ret) {
+                    // Remove client key+cert
+                    if (config.getClientCertificate() != null) {
+                        mKeyStore.delete(privKeyName, Process.WIFI_UID);
+                        mKeyStore.delete(userCertName, Process.WIFI_UID);
+                    }
+                    // Remove added CA certs.
+                    for (String addedAlias : caCertificateAliases) {
+                        mKeyStore.delete(Credentials.CA_CERTIFICATE + addedAlias, Process.WIFI_UID);
+                    }
+                    return ret;
+                } else {
+                    caCertificateAliases.add(alias);
+                }
+            }
+        }
+        // Remove old CA certs.
+        for (String oldAlias : oldCaCertificatesToRemove) {
+            mKeyStore.delete(Credentials.CA_CERTIFICATE + oldAlias, Process.WIFI_UID);
+        }
+        // Set alias names
+        if (config.getClientCertificate() != null) {
+            config.setClientCertificateAlias(name);
+            config.resetClientKeyEntry();
+        }
+
+        if (caCertificates != null) {
+            config.setCaCertificateAliases(
+                    caCertificateAliases.toArray(new String[caCertificateAliases.size()]));
+            config.resetCaCertificate();
+        }
+        return ret;
+    }
+
+    private boolean putCertInKeyStore(String name, Certificate cert) {
+        try {
+            byte[] certData = Credentials.convertToPem(cert);
+            if (mVerboseLoggingEnabled) Log.d(TAG, "putting certificate " + name + " in keystore");
+            return mKeyStore.put(name, certData, Process.WIFI_UID, KeyStore.FLAG_NONE);
+        } catch (IOException e1) {
+            return false;
+        } catch (CertificateException e2) {
+            return false;
+        }
+    }
+
+    /**
+     * Remove enterprise keys from the network config.
+     *
+     * @param config Config corresponding to the network.
+     */
+    public void removeKeys(WifiEnterpriseConfig config) {
+        String client = config.getClientCertificateAlias();
+        // a valid client certificate is configured
+        if (!TextUtils.isEmpty(client)) {
+            if (mVerboseLoggingEnabled) Log.d(TAG, "removing client private key and user cert");
+            mKeyStore.delete(Credentials.USER_PRIVATE_KEY + client, Process.WIFI_UID);
+            mKeyStore.delete(Credentials.USER_CERTIFICATE + client, Process.WIFI_UID);
+        }
+
+        String[] aliases = config.getCaCertificateAliases();
+        // a valid ca certificate is configured
+        if (aliases != null) {
+            for (String ca : aliases) {
+                if (!TextUtils.isEmpty(ca)) {
+                    if (mVerboseLoggingEnabled) Log.d(TAG, "removing CA cert: " + ca);
+                    mKeyStore.delete(Credentials.CA_CERTIFICATE + ca, Process.WIFI_UID);
+                }
+            }
+        }
+    }
+
+    /**
+     * Update/Install keys for given enterprise network.
+     *
+     * @param config         Config corresponding to the network.
+     * @param existingConfig Existing config corresponding to the network already stored in our
+     *                       database. This maybe null if it's a new network.
+     * @return true if successful, false otherwise.
+     */
+    public boolean updateNetworkKeys(WifiConfiguration config, WifiConfiguration existingConfig) {
+        WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
+        if (needsKeyStore(enterpriseConfig)) {
+            try {
+                /* config passed may include only fields being updated.
+                 * In order to generate the key id, fetch uninitialized
+                 * fields from the currently tracked configuration
+                 */
+                String keyId = config.getKeyIdForCredentials(existingConfig);
+                if (!installKeys(existingConfig != null
+                        ? existingConfig.enterpriseConfig : null, enterpriseConfig, keyId)) {
+                    Log.e(TAG, config.SSID + ": failed to install keys");
+                    return false;
+                }
+            } catch (IllegalStateException e) {
+                Log.e(TAG, config.SSID + " invalid config for key installation: " + e.getMessage());
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Checks whether the configuration requires a software backed keystore or not.
+     * @param config WifiEnterprise config instance pointing to the enterprise configuration of the
+     *               network.
+     */
+    public static boolean needsSoftwareBackedKeyStore(WifiEnterpriseConfig config) {
+        String client = config.getClientCertificateAlias();
+        if (!TextUtils.isEmpty(client)) {
+            // a valid client certificate is configured
+
+            // BUGBUG(b/29578316): keyStore.get() never returns certBytes; because it is not
+            // taking WIFI_UID as a parameter. It always looks for certificate
+            // with SYSTEM_UID, and never finds any Wifi certificates. Assuming that
+            // all certificates need software keystore until we get the get() API
+            // fixed.
+            return true;
+        }
+        return false;
+    }
+
+}
diff --git a/service/java/com/android/server/wifi/WifiLastResortWatchdog.java b/service/java/com/android/server/wifi/WifiLastResortWatchdog.java
index 558b50e..6fb2050 100644
--- a/service/java/com/android/server/wifi/WifiLastResortWatchdog.java
+++ b/service/java/com/android/server/wifi/WifiLastResortWatchdog.java
@@ -34,8 +34,7 @@
  */
 public class WifiLastResortWatchdog {
     private static final String TAG = "WifiLastResortWatchdog";
-    private static final boolean VDBG = false;
-    private static final boolean DBG = true;
+    private boolean mVerboseLoggingEnabled = false;
     /**
      * Association Failure code
      */
@@ -80,9 +79,10 @@
 
     private WifiMetrics mWifiMetrics;
 
-    private WifiController mWifiController = null;
+    private WifiController mWifiController;
 
-    WifiLastResortWatchdog(WifiMetrics wifiMetrics) {
+    WifiLastResortWatchdog(WifiController wifiController, WifiMetrics wifiMetrics) {
+        mWifiController = wifiController;
         mWifiMetrics = wifiMetrics;
     }
 
@@ -95,7 +95,9 @@
      */
     public void updateAvailableNetworks(
             List<Pair<ScanDetail, WifiConfiguration>> availableNetworks) {
-        if (VDBG) Log.v(TAG, "updateAvailableNetworks: size = " + availableNetworks.size());
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "updateAvailableNetworks: size = " + availableNetworks.size());
+        }
         // Add new networks to mRecentAvailableNetworks
         if (availableNetworks != null) {
             for (Pair<ScanDetail, WifiConfiguration> pair : availableNetworks) {
@@ -105,7 +107,9 @@
                 if (scanResult == null) continue;
                 String bssid = scanResult.BSSID;
                 String ssid = "\"" + scanDetail.getSSID() + "\"";
-                if (VDBG) Log.v(TAG, " " + bssid + ": " + scanDetail.getSSID());
+                if (mVerboseLoggingEnabled) {
+                    Log.v(TAG, " " + bssid + ": " + scanDetail.getSSID());
+                }
                 // Cache the scanResult & WifiConfig
                 AvailableNetworkFailureCount availableNetworkFailureCount =
                         mRecentAvailableNetworks.get(bssid);
@@ -161,15 +165,12 @@
                         mSsidFailureCount.remove(ssid);
                     }
                 } else {
-                    if (DBG) {
-                        Log.d(TAG, "updateAvailableNetworks: SSID to AP count mismatch for "
-                                + ssid);
-                    }
+                    Log.d(TAG, "updateAvailableNetworks: SSID to AP count mismatch for " + ssid);
                 }
                 it.remove();
             }
         }
-        if (VDBG) Log.v(TAG, toString());
+        if (mVerboseLoggingEnabled) Log.v(TAG, toString());
     }
 
     /**
@@ -180,7 +181,7 @@
      * @return true if watchdog triggers, returned for test visibility
      */
     public boolean noteConnectionFailureAndTriggerIfNeeded(String ssid, String bssid, int reason) {
-        if (VDBG) {
+        if (mVerboseLoggingEnabled) {
             Log.v(TAG, "noteConnectionFailureAndTriggerIfNeeded: [" + ssid + ", " + bssid + ", "
                     + reason + "]");
         }
@@ -189,7 +190,9 @@
 
         // Have we met conditions to trigger the Watchdog Wifi restart?
         boolean isRestartNeeded = checkTriggerCondition();
-        if (VDBG) Log.v(TAG, "isRestartNeeded = " + isRestartNeeded);
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "isRestartNeeded = " + isRestartNeeded);
+        }
         if (isRestartNeeded) {
             // Stop the watchdog from triggering until re-enabled
             setWatchdogTriggerEnabled(false);
@@ -207,7 +210,9 @@
      * @param isEntering true if called from ConnectedState.enter(), false for exit()
      */
     public void connectedStateTransition(boolean isEntering) {
-        if (VDBG) Log.v(TAG, "connectedStateTransition: isEntering = " + isEntering);
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "connectedStateTransition: isEntering = " + isEntering);
+        }
         mWifiIsConnected = isEntering;
         if (isEntering) {
             // We connected to something! Reset failure counts for everything
@@ -228,7 +233,7 @@
      * @param reason Message id from WifiStateMachine for this failure
      */
     private void updateFailureCountForNetwork(String ssid, String bssid, int reason) {
-        if (VDBG) {
+        if (mVerboseLoggingEnabled) {
             Log.v(TAG, "updateFailureCountForNetwork: [" + ssid + ", " + bssid + ", "
                     + reason + "]");
         }
@@ -249,9 +254,7 @@
     private void incrementSsidFailureCount(String ssid, int reason) {
         Pair<AvailableNetworkFailureCount, Integer> ssidFails = mSsidFailureCount.get(ssid);
         if (ssidFails == null) {
-            if (DBG) {
-                Log.v(TAG, "updateFailureCountForNetwork: No networks for ssid = " + ssid);
-            }
+            Log.d(TAG, "updateFailureCountForNetwork: No networks for ssid = " + ssid);
             return;
         }
         AvailableNetworkFailureCount failureCount = ssidFails.first;
@@ -267,22 +270,18 @@
         AvailableNetworkFailureCount availableNetworkFailureCount =
                 mRecentAvailableNetworks.get(bssid);
         if (availableNetworkFailureCount == null) {
-            if (DBG) {
-                Log.d(TAG, "updateFailureCountForNetwork: Unable to find Network [" + ssid
-                        + ", " + bssid + "]");
-            }
+            Log.d(TAG, "updateFailureCountForNetwork: Unable to find Network [" + ssid
+                    + ", " + bssid + "]");
             return;
         }
         if (!availableNetworkFailureCount.ssid.equals(ssid)) {
-            if (DBG) {
-                Log.d(TAG, "updateFailureCountForNetwork: Failed connection attempt has"
-                        + " wrong ssid. Failed [" + ssid + ", " + bssid + "], buffered ["
-                        + availableNetworkFailureCount.ssid + ", " + bssid + "]");
-            }
+            Log.d(TAG, "updateFailureCountForNetwork: Failed connection attempt has"
+                    + " wrong ssid. Failed [" + ssid + ", " + bssid + "], buffered ["
+                    + availableNetworkFailureCount.ssid + ", " + bssid + "]");
             return;
         }
         if (availableNetworkFailureCount.config == null) {
-            if (VDBG) {
+            if (mVerboseLoggingEnabled) {
                 Log.v(TAG, "updateFailureCountForNetwork: network has no config ["
                         + ssid + ", " + bssid + "]");
             }
@@ -297,7 +296,7 @@
      * @return is the trigger condition true
      */
     private boolean checkTriggerCondition() {
-        if (VDBG) Log.v(TAG, "checkTriggerCondition.");
+        if (mVerboseLoggingEnabled) Log.v(TAG, "checkTriggerCondition.");
         // Don't check Watchdog trigger if wifi is in a connected state
         // (This should not occur, but we want to protect against any race conditions)
         if (mWifiIsConnected) return false;
@@ -319,7 +318,7 @@
         }
         // We have met the failure count for every available network & there is at-least one network
         // we have previously connected to present.
-        if (VDBG) {
+        if (mVerboseLoggingEnabled) {
             Log.v(TAG, "checkTriggerCondition: return = " + atleastOneNetworkHasEverConnected);
         }
         return atleastOneNetworkHasEverConnected;
@@ -329,7 +328,7 @@
      * Trigger a restart of the wifi stack.
      */
     private void restartWifiStack() {
-        if (VDBG) Log.v(TAG, "restartWifiStack.");
+        if (mVerboseLoggingEnabled) Log.v(TAG, "restartWifiStack.");
 
         // First verify that we can send the trigger message.
         if (mWifiController == null) {
@@ -337,17 +336,15 @@
             return;
         }
 
-        if (DBG) Log.d(TAG, toString());
-
         mWifiController.sendMessage(WifiController.CMD_RESTART_WIFI);
-        Log.i(TAG, "Triggered WiFi stack restart.");
+        Log.i(TAG, "Triggered WiFi stack restart.\n" + toString());
     }
 
     /**
      * Update WifiMetrics with various Watchdog stats (trigger counts, failed network counts)
      */
     private void incrementWifiMetricsTriggerCounts() {
-        if (VDBG) Log.v(TAG, "incrementWifiMetricsTriggerCounts.");
+        if (mVerboseLoggingEnabled) Log.v(TAG, "incrementWifiMetricsTriggerCounts.");
         mWifiMetrics.incrementNumLastResortWatchdogTriggers();
         mWifiMetrics.addCountToNumLastResortWatchdogAvailableNetworksTotal(
                 mSsidFailureCount.size());
@@ -379,7 +376,7 @@
      * Clear failure counts for each network in recentAvailableNetworks
      */
     private void clearAllFailureCounts() {
-        if (VDBG) Log.v(TAG, "clearAllFailureCounts.");
+        if (mVerboseLoggingEnabled) Log.v(TAG, "clearAllFailureCounts.");
         for (Map.Entry<String, AvailableNetworkFailureCount> entry
                 : mRecentAvailableNetworks.entrySet()) {
             final AvailableNetworkFailureCount failureCount = entry.getValue();
@@ -403,7 +400,7 @@
      * @param enable true to enable the Watchdog trigger, false to disable it
      */
     private void setWatchdogTriggerEnabled(boolean enable) {
-        if (VDBG) Log.v(TAG, "setWatchdogTriggerEnabled: enable = " + enable);
+        if (mVerboseLoggingEnabled) Log.v(TAG, "setWatchdogTriggerEnabled: enable = " + enable);
         mWatchdogAllowedToTrigger = enable;
     }
 
@@ -417,14 +414,15 @@
         sb.append("\nmRecentAvailableNetworks: ").append(mRecentAvailableNetworks.size());
         for (Map.Entry<String, AvailableNetworkFailureCount> entry
                 : mRecentAvailableNetworks.entrySet()) {
-            sb.append("\n ").append(entry.getKey()).append(": ").append(entry.getValue());
+            sb.append("\n ").append(entry.getKey()).append(": ").append(entry.getValue())
+                .append(", Age: ").append(entry.getValue().age);
         }
         sb.append("\nmSsidFailureCount:");
         for (Map.Entry<String, Pair<AvailableNetworkFailureCount, Integer>> entry :
                 mSsidFailureCount.entrySet()) {
             final AvailableNetworkFailureCount failureCount = entry.getValue().first;
             final Integer apCount = entry.getValue().second;
-            sb.append("\n").append(entry.getKey()).append(": ").append(apCount).append(", ")
+            sb.append("\n").append(entry.getKey()).append(": ").append(apCount).append(",")
                     .append(failureCount.toString());
         }
         return sb.toString();
@@ -457,9 +455,7 @@
         String ssid = availableNetworkFailureCount.ssid;
         Pair<AvailableNetworkFailureCount, Integer> ssidFails = mSsidFailureCount.get(ssid);
         if (ssidFails == null) {
-            if (DBG) {
-                Log.d(TAG, "getFailureCount: Could not find SSID count for " + ssid);
-            }
+            Log.d(TAG, "getFailureCount: Could not find SSID count for " + ssid);
             return 0;
         }
         final AvailableNetworkFailureCount failCount = ssidFails.first;
@@ -475,6 +471,14 @@
         }
     }
 
+    protected void enableVerboseLogging(int verbose) {
+        if (verbose > 0) {
+            mVerboseLoggingEnabled = true;
+        } else {
+            mVerboseLoggingEnabled = false;
+        }
+    }
+
     /**
      * This class holds the failure counts for an 'available network' (one of the potential
      * candidates for connection, as determined by framework).
@@ -537,24 +541,13 @@
         }
 
         public String toString() {
-            return  ssid + ", HasEverConnected: " + ((config != null)
+            return  ssid + " HasEverConnected: " + ((config != null)
                     ? config.getNetworkSelectionStatus().getHasEverConnected() : "null_config")
                     + ", Failures: {"
                     + "Assoc: " + associationRejection
                     + ", Auth: " + authenticationFailure
                     + ", Dhcp: " + dhcpFailure
-                    + "}"
-                    + ", Age: " + age;
+                    + "}";
         }
     }
-
-    /**
-     * Method used to set the WifiController for the this watchdog.
-     *
-     * The WifiController is used to send the restart wifi command to carry out the wifi restart.
-     * @param wifiController WifiController instance that will be sent the CMD_RESTART_WIFI message.
-     */
-    public void setWifiController(WifiController wifiController) {
-        mWifiController = wifiController;
-    }
 }
diff --git a/service/java/com/android/server/wifi/WifiMetrics.java b/service/java/com/android/server/wifi/WifiMetrics.java
index 0dc5ccf..0eaf029 100644
--- a/service/java/com/android/server/wifi/WifiMetrics.java
+++ b/service/java/com/android/server/wifi/WifiMetrics.java
@@ -77,7 +77,7 @@
      */
     private final SparseIntArray mWifiSystemStateEntries = new SparseIntArray();
     /**
-     * Records the elapsedRealtime (in seconds) that represents the beginning of data
+     * Records the getElapsedSinceBootMillis (in seconds) that represents the beginning of data
      * capture for for this WifiMetricsProto
      */
     private final SparseIntArray mRssiPollCounts = new SparseIntArray();
@@ -313,7 +313,7 @@
         mCurrentConnectionEvent = null;
         mScreenOn = true;
         mWifiState = WifiMetricsProto.WifiLog.WIFI_DISABLED;
-        mRecordStartTimeSec = mClock.elapsedRealtime() / 1000;
+        mRecordStartTimeSec = mClock.getElapsedSinceBootMillis() / 1000;
     }
 
     // Values used for indexing SystemStateEntries
@@ -356,12 +356,12 @@
             }
             mCurrentConnectionEvent = new ConnectionEvent();
             mCurrentConnectionEvent.mConnectionEvent.startTimeMillis =
-                    mClock.currentTimeMillis();
+                    mClock.getWallClockMillis();
             mCurrentConnectionEvent.mConfigBssid = targetBSSID;
             mCurrentConnectionEvent.mConnectionEvent.roamType = roamType;
             mCurrentConnectionEvent.mRouterFingerPrint.updateFromWifiConfiguration(config);
             mCurrentConnectionEvent.mConfigBssid = "any";
-            mCurrentConnectionEvent.mRealStartTime = mClock.elapsedRealtime();
+            mCurrentConnectionEvent.mRealStartTime = mClock.getElapsedSinceBootMillis();
             mCurrentConnectionEvent.mWifiState = mWifiState;
             mCurrentConnectionEvent.mScreenOn = mScreenOn;
             mConnectionEventList.add(mCurrentConnectionEvent);
@@ -414,7 +414,7 @@
                 boolean result = (level2FailureCode == 1)
                         && (connectivityFailureCode == WifiMetricsProto.ConnectionEvent.HLF_NONE);
                 mCurrentConnectionEvent.mConnectionEvent.connectionResult = result ? 1 : 0;
-                mCurrentConnectionEvent.mRealEndTime = mClock.elapsedRealtime();
+                mCurrentConnectionEvent.mRealEndTime = mClock.getElapsedSinceBootMillis();
                 mCurrentConnectionEvent.mConnectionEvent.durationTakenToConnectMillis = (int)
                         (mCurrentConnectionEvent.mRealEndTime
                         - mCurrentConnectionEvent.mRealStartTime);
@@ -922,7 +922,7 @@
                 pw.println("mWifiLogProto.numLastResortWatchdogTriggersWithBadOther="
                         + mWifiLogProto.numLastResortWatchdogTriggersWithBadOther);
                 pw.println("mWifiLogProto.recordDurationSec="
-                        + ((mClock.elapsedRealtime() / 1000) - mRecordStartTimeSec));
+                        + ((mClock.getElapsedSinceBootMillis() / 1000) - mRecordStartTimeSec));
                 pw.println("mWifiLogProto.rssiPollRssiCount: Printing counts for [" + MIN_RSSI_POLL
                         + ", " + MAX_RSSI_POLL + "]");
                 StringBuilder sb = new StringBuilder();
@@ -986,7 +986,7 @@
                 mWifiLogProto.wifiSystemStateEntries[i].isScreenOn =
                         (mWifiSystemStateEntries.keyAt(i) % 2) > 0;
             }
-            mWifiLogProto.recordDurationSec = (int) ((mClock.elapsedRealtime() / 1000)
+            mWifiLogProto.recordDurationSec = (int) ((mClock.getElapsedSinceBootMillis() / 1000)
                     - mRecordStartTimeSec);
 
             /**
@@ -1014,7 +1014,7 @@
             }
             mScanReturnEntries.clear();
             mWifiSystemStateEntries.clear();
-            mRecordStartTimeSec = mClock.elapsedRealtime() / 1000;
+            mRecordStartTimeSec = mClock.getElapsedSinceBootMillis() / 1000;
             mRssiPollCounts.clear();
             mWifiLogProto.clear();
         }
diff --git a/service/java/com/android/server/wifi/WifiMonitor.java b/service/java/com/android/server/wifi/WifiMonitor.java
index 1f2b397..8c0d36a 100644
--- a/service/java/com/android/server/wifi/WifiMonitor.java
+++ b/service/java/com/android/server/wifi/WifiMonitor.java
@@ -59,8 +59,7 @@
  */
 public class WifiMonitor {
 
-    private static boolean DBG = false;
-    private static final boolean VDBG = false;
+    private static final boolean DBG = false;
     private static final String TAG = "WifiMonitor";
 
     /** Events we receive from the supplicant daemon */
@@ -536,12 +535,13 @@
     }
 
     private int mRecvErrors = 0;
+    private boolean mVerboseLoggingEnabled = false;
 
     void enableVerboseLogging(int verbose) {
         if (verbose > 0) {
-            DBG = true;
+            mVerboseLoggingEnabled = true;
         } else {
-            DBG = false;
+            mVerboseLoggingEnabled = false;
         }
     }
 
@@ -589,7 +589,7 @@
             return true;
         }
 
-        if (DBG) Log.d(TAG, "connecting to supplicant");
+        if (mVerboseLoggingEnabled) Log.d(TAG, "connecting to supplicant");
         int connectTries = 0;
         while (true) {
             if (mWifiNative.connectToSupplicant()) {
@@ -625,7 +625,7 @@
     }
 
     public synchronized void stopMonitoring(String iface) {
-        if (DBG) Log.d(TAG, "stopMonitoring(" + iface + ")");
+        if (mVerboseLoggingEnabled) Log.d(TAG, "stopMonitoring(" + iface + ")");
         setMonitoring(iface, true);
         sendMessage(iface, SUP_DISCONNECTION_EVENT);
         setMonitoring(iface, false);
@@ -693,10 +693,14 @@
                     }
                 }
             } else {
-                if (DBG) Log.d(TAG, "Dropping event because (" + iface + ") is stopped");
+                if (mVerboseLoggingEnabled) {
+                    Log.d(TAG, "Dropping event because (" + iface + ") is stopped");
+                }
             }
         } else {
-            if (DBG) Log.d(TAG, "Sending to all monitors because there's no matching iface");
+            if (mVerboseLoggingEnabled) {
+                Log.d(TAG, "Sending to all monitors because there's no matching iface");
+            }
             boolean firstHandler = true;
             for (Map.Entry<String, SparseArray<Set<Handler>>> entry : mHandlerMap.entrySet()) {
                 if (isMonitoring(entry.getKey())) {
@@ -731,25 +735,29 @@
         }
 
         public void run() {
-            if (DBG) {
+            if (mVerboseLoggingEnabled) {
                 Log.d(TAG, "MonitorThread start with mConnected=" + mConnected);
             }
             //noinspection InfiniteLoopStatement
             for (;;) {
                 if (!mConnected) {
-                    if (DBG) Log.d(TAG, "MonitorThread exit because mConnected is false");
+                    if (mVerboseLoggingEnabled) {
+                        Log.d(TAG, "MonitorThread exit because mConnected is false");
+                    }
                     break;
                 }
                 String eventStr = mWifiNative.waitForEvent();
 
                 // Skip logging the common but mostly uninteresting events
                 if (!eventStr.contains(BSS_ADDED_STR) && !eventStr.contains(BSS_REMOVED_STR)) {
-                    if (DBG) Log.d(TAG, "Event [" + eventStr + "]");
+                    if (mVerboseLoggingEnabled) Log.d(TAG, "Event [" + eventStr + "]");
                     mLocalLog.log("Event [" + eventStr + "]");
                 }
 
                 if (dispatchEvent(eventStr)) {
-                    if (DBG) Log.d(TAG, "Disconnecting from the supplicant, no more events");
+                    if (mVerboseLoggingEnabled) {
+                        Log.d(TAG, "Disconnecting from the supplicant, no more events");
+                    }
                     break;
                 }
             }
@@ -782,7 +790,7 @@
             iface = "p2p0";
         }
 
-        if (VDBG) Log.d(TAG, "Dispatching event to interface: " + iface);
+        if (DBG) Log.d(TAG, "Dispatching event to interface: " + iface);
 
         if (dispatchEvent(eventStr, iface)) {
             mConnected = false;
@@ -803,7 +811,7 @@
 
     /* @return true if the event was supplicant disconnection */
     private boolean dispatchEvent(String eventStr, String iface) {
-        if (DBG) {
+        if (mVerboseLoggingEnabled) {
             // Dont log CTRL-EVENT-BSS-ADDED which are too verbose and not handled
             if (eventStr != null && !eventStr.contains("CTRL-EVENT-BSS-ADDED")) {
                 Log.d(TAG, iface + " cnt=" + Integer.toString(eventLogCounter)
@@ -856,7 +864,9 @@
                     eventStr.endsWith(AUTH_TIMEOUT_STR)) {
                 sendMessage(iface, AUTHENTICATION_FAILURE_EVENT);
             } else {
-                if (DBG) Log.w(TAG, "couldn't identify event type - " + eventStr);
+                if (mVerboseLoggingEnabled) {
+                    Log.w(TAG, "couldn't identify event type - " + eventStr);
+                }
             }
             eventLogCounter++;
             return false;
@@ -867,7 +877,9 @@
         if (nameEnd != -1)
             eventName = eventName.substring(0, nameEnd);
         if (eventName.length() == 0) {
-            if (DBG) Log.i(TAG, "Received wpa_supplicant event with empty event name");
+            if (mVerboseLoggingEnabled) {
+                Log.i(TAG, "Received wpa_supplicant event with empty event name");
+            }
             eventLogCounter++;
             return false;
         }
@@ -978,7 +990,7 @@
              */
             if (eventData.startsWith(WPA_RECV_ERROR_STR)) {
                 if (++mRecvErrors > MAX_RECV_ERRORS) {
-                    if (DBG) {
+                    if (mVerboseLoggingEnabled) {
                         Log.d(TAG, "too many recv errors, closing connection");
                     }
                 } else {
@@ -999,7 +1011,9 @@
             String BSSID = "";
             int status = -1;
             if (!match.find()) {
-                if (DBG) Log.d(TAG, "Assoc Reject: Could not parse assoc reject string");
+                if (mVerboseLoggingEnabled) {
+                    Log.d(TAG, "Assoc Reject: Could not parse assoc reject string");
+                }
             } else {
                 int groupNumber = match.groupCount();
                 int statusGroupNumber = -1;
@@ -1018,9 +1032,9 @@
                 }
             }
             sendMessage(iface, ASSOCIATION_REJECTION_EVENT, eventLogCounter, status, BSSID);
-        } else if (event == BSS_ADDED && !VDBG) {
+        } else if (event == BSS_ADDED && !DBG) {
             // Ignore that event - it is not handled, and dont log it as it is too verbose
-        } else if (event == BSS_REMOVED && !VDBG) {
+        } else if (event == BSS_REMOVED && !DBG) {
             // Ignore that event - it is not handled, and dont log it as it is too verbose
         }  else {
             handleEvent(event, eventData, iface);
@@ -1046,7 +1060,7 @@
      * event name and &quot;&#8195;&#8212;&#8195;&quot;
      */
     private void handleEvent(int event, String remainder, String iface) {
-        if (DBG) {
+        if (mVerboseLoggingEnabled) {
             Log.d(TAG, "handleEvent " + Integer.toString(event) + " " + remainder);
         }
         switch (event) {
@@ -1067,7 +1081,7 @@
                 break;
 
             case UNKNOWN:
-                if (DBG) {
+                if (mVerboseLoggingEnabled) {
                     Log.w(TAG, "handleEvent unknown: " + Integer.toString(event) + " " + remainder);
                 }
                 break;
@@ -1345,7 +1359,9 @@
             }
 
         } else {
-            if (DBG) Log.w(TAG, "couldn't identify request type - " + dataString);
+            if (mVerboseLoggingEnabled) {
+                Log.w(TAG, "couldn't identify request type - " + dataString);
+            }
         }
     }
 
@@ -1418,7 +1434,9 @@
         if (newState == NetworkInfo.DetailedState.CONNECTED) {
             match = mConnectedEventPattern.matcher(data);
             if (!match.find()) {
-               if (DBG) Log.d(TAG, "handleNetworkStateChange: Couldnt find BSSID in event string");
+                if (mVerboseLoggingEnabled) {
+                    Log.d(TAG, "handleNetworkStateChange: Couldnt find BSSID in event string");
+                }
             } else {
                 BSSID = match.group(1);
                 try {
@@ -1431,7 +1449,9 @@
         } else if (newState == NetworkInfo.DetailedState.DISCONNECTED) {
             match = mDisconnectedEventPattern.matcher(data);
             if (!match.find()) {
-               if (DBG) Log.d(TAG, "handleNetworkStateChange: Could not parse disconnect string");
+                if (mVerboseLoggingEnabled) {
+                    Log.d(TAG, "handleNetworkStateChange: Could not parse disconnect string");
+                }
             } else {
                 BSSID = match.group(1);
                 try {
@@ -1445,9 +1465,10 @@
                     local = -1;
                 }
             }
-            if (DBG) Log.d(TAG, "WifiMonitor notify network disconnect: "
-                    + BSSID
-                    + " reason=" + Integer.toString(reason));
+            if (mVerboseLoggingEnabled) {
+                Log.d(TAG, "WifiMonitor notify network disconnect: " + BSSID
+                        + " reason=" + Integer.toString(reason));
+            }
             sendMessage(iface, NETWORK_DISCONNECTION_EVENT, local, reason, BSSID);
         }
     }
diff --git a/service/java/com/android/server/wifi/WifiMulticastLockManager.java b/service/java/com/android/server/wifi/WifiMulticastLockManager.java
new file mode 100644
index 0000000..40b259d
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiMulticastLockManager.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.app.IBatteryStats;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * WifiMulticastLockManager tracks holders of multicast locks and
+ * triggers enabling and disabling of filtering.
+ *
+ * @hide
+ */
+public class WifiMulticastLockManager {
+    private static final String TAG = "WifiMulticastLockManager";
+    private final List<Multicaster> mMulticasters = new ArrayList<>();
+    private int mMulticastEnabled = 0;
+    private int mMulticastDisabled = 0;
+    private boolean mVerboseLoggingEnabled = false;
+    private final IBatteryStats mBatteryStats;
+    private final FilterController mFilterController;
+
+    /** Delegate for handling state change events for multicast filtering. */
+    public interface FilterController {
+        /** Called when multicast filtering should be enabled */
+        void startFilteringMulticastPackets();
+
+        /** Called when multicast filtering should be disabled */
+        void stopFilteringMulticastPackets();
+    }
+
+    public WifiMulticastLockManager(FilterController filterController, IBatteryStats batteryStats) {
+        mBatteryStats = batteryStats;
+        mFilterController = filterController;
+    }
+
+    private class Multicaster implements IBinder.DeathRecipient {
+        String mTag;
+        int mUid;
+        IBinder mBinder;
+
+        Multicaster(String tag, IBinder binder) {
+            mTag = tag;
+            mUid = Binder.getCallingUid();
+            mBinder = binder;
+            try {
+                mBinder.linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                binderDied();
+            }
+        }
+
+        @Override
+        public void binderDied() {
+            Slog.e(TAG, "Multicaster binderDied");
+            synchronized (mMulticasters) {
+                int i = mMulticasters.indexOf(this);
+                if (i != -1) {
+                    removeMulticasterLocked(i, mUid);
+                }
+            }
+        }
+
+        void unlinkDeathRecipient() {
+            mBinder.unlinkToDeath(this, 0);
+        }
+
+        public int getUid() {
+            return mUid;
+        }
+
+        public String toString() {
+            return "Multicaster{" + mTag + " uid=" + mUid  + "}";
+        }
+    }
+
+    protected void dump(PrintWriter pw) {
+        pw.println("mMulticastEnabled " + mMulticastEnabled);
+        pw.println("mMulticastDisabled " + mMulticastDisabled);
+        pw.println("Multicast Locks held:");
+        for (Multicaster l : mMulticasters) {
+            pw.print("    ");
+            pw.println(l);
+        }
+    }
+
+    protected void enableVerboseLogging(int verbose) {
+        if (verbose > 0) {
+            mVerboseLoggingEnabled = true;
+        } else {
+            mVerboseLoggingEnabled = false;
+        }
+    }
+
+    /** Start filtering if  no multicasters exist. */
+    public void initializeFiltering() {
+        synchronized (mMulticasters) {
+            // if anybody had requested filters be off, leave off
+            if (mMulticasters.size() != 0) {
+                return;
+            } else {
+                mFilterController.startFilteringMulticastPackets();
+            }
+        }
+    }
+
+    /**
+     * Acquire a multicast lock.
+     * @param binder a binder used to ensure caller is still alive
+     * @param tag string name of the caller.
+     */
+    public void acquireLock(IBinder binder, String tag) {
+        synchronized (mMulticasters) {
+            mMulticastEnabled++;
+            mMulticasters.add(new Multicaster(tag, binder));
+            // Note that we could call stopFilteringMulticastPackets only when
+            // our new size == 1 (first call), but this function won't
+            // be called often and by making the stopPacket call each
+            // time we're less fragile and self-healing.
+            mFilterController.stopFilteringMulticastPackets();
+        }
+
+        int uid = Binder.getCallingUid();
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mBatteryStats.noteWifiMulticastEnabled(uid);
+        } catch (RemoteException e) {
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    /** Releases a multicast lock */
+    public void releaseLock() {
+        int uid = Binder.getCallingUid();
+        synchronized (mMulticasters) {
+            mMulticastDisabled++;
+            int size = mMulticasters.size();
+            for (int i = size - 1; i >= 0; i--) {
+                Multicaster m = mMulticasters.get(i);
+                if ((m != null) && (m.getUid() == uid)) {
+                    removeMulticasterLocked(i, uid);
+                }
+            }
+        }
+    }
+
+    private void removeMulticasterLocked(int i, int uid) {
+        Multicaster removed = mMulticasters.remove(i);
+
+        if (removed != null) {
+            removed.unlinkDeathRecipient();
+        }
+        if (mMulticasters.size() == 0) {
+            mFilterController.startFilteringMulticastPackets();
+        }
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mBatteryStats.noteWifiMulticastDisabled(uid);
+        } catch (RemoteException e) {
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    /** Returns whether multicast should be allowed (filterning disabled). */
+    public boolean isMulticastEnabled() {
+        synchronized (mMulticasters) {
+            return (mMulticasters.size() > 0);
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/WifiNative.java b/service/java/com/android/server/wifi/WifiNative.java
index 73765ee..75f5915 100644
--- a/service/java/com/android/server/wifi/WifiNative.java
+++ b/service/java/com/android/server/wifi/WifiNative.java
@@ -18,12 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.net.apf.ApfCapabilities;
 import android.net.wifi.RttManager;
 import android.net.wifi.RttManager.ResponderConfig;
@@ -46,6 +41,7 @@
 import android.util.Log;
 
 import com.android.internal.annotations.Immutable;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.HexDump;
 import com.android.server.connectivity.KeepalivePacketData;
 import com.android.server.wifi.hotspot2.NetworkDetail;
@@ -339,24 +335,6 @@
     }
 
     /**
-     * Create a comma separate string from integer set.
-     * @param values List of integers.
-     * @return comma separated string.
-     */
-    private static String createCSVStringFromIntegerSet(Set<Integer> values) {
-        StringBuilder list = new StringBuilder();
-        boolean first = true;
-        for (Integer value : values) {
-            if (!first) {
-                list.append(",");
-            }
-            list.append(value);
-            first = false;
-        }
-        return list.toString();
-    }
-
-    /**
      * Start a scan using wpa_supplicant for the given frequencies.
      * @param freqs list of frequencies to scan for, if null scan all supported channels.
      * @param hiddenNetworkIds List of hidden networks to be scanned for.
@@ -365,10 +343,10 @@
         String freqList = null;
         String hiddenNetworkIdList = null;
         if (freqs != null && freqs.size() != 0) {
-            freqList = createCSVStringFromIntegerSet(freqs);
+            freqList = TextUtils.join(",", freqs);
         }
         if (hiddenNetworkIds != null && hiddenNetworkIds.size() != 0) {
-            hiddenNetworkIdList = createCSVStringFromIntegerSet(hiddenNetworkIds);
+            hiddenNetworkIdList = TextUtils.join(",", hiddenNetworkIds);
         }
         return scanWithParams(freqList, hiddenNetworkIdList);
     }
@@ -408,17 +386,26 @@
     }
 
     public boolean setNetworkExtra(int netId, String name, Map<String, String> values) {
+        String encoded = createNetworkExtra(values);
+        if (encoded == null) {
+            return false;
+        }
+        return setNetworkVariable(netId, name, "\"" + encoded + "\"");
+    }
+
+    @VisibleForTesting
+    public static String createNetworkExtra(Map<String, String> values) {
         final String encoded;
         try {
             encoded = URLEncoder.encode(new JSONObject(values).toString(), "UTF-8");
         } catch (NullPointerException e) {
             Log.e(TAG, "Unable to serialize networkExtra: " + e.toString());
-            return false;
+            return null;
         } catch (UnsupportedEncodingException e) {
             Log.e(TAG, "Unable to serialize networkExtra: " + e.toString());
-            return false;
+            return null;
         }
-        return setNetworkVariable(netId, name, "\"" + encoded + "\"");
+        return encoded;
     }
 
     public boolean setNetworkVariable(int netId, String name, String value) {
@@ -432,12 +419,16 @@
     }
 
     public Map<String, String> getNetworkExtra(int netId, String name) {
-        final String wrapped = getNetworkVariable(netId, name);
-        if (wrapped == null || !wrapped.startsWith("\"") || !wrapped.endsWith("\"")) {
+        final String extraString = getNetworkVariable(netId, name);
+        return parseNetworkExtra(extraString);
+    }
+
+    public static Map<String, String> parseNetworkExtra(String extraSting) {
+        if (extraSting == null || !extraSting.startsWith("\"") || !extraSting.endsWith("\"")) {
             return null;
         }
         try {
-            final String encoded = wrapped.substring(1, wrapped.length() - 1);
+            final String encoded = extraSting.substring(1, extraSting.length() - 1);
             // This method reads a JSON dictionary that was written by setNetworkExtra(). However,
             // on devices that upgraded from Marshmallow, it may encounter a legacy value instead -
             // an FQDN stored as a plain string. If such a value is encountered, the JSONObject
@@ -882,7 +873,7 @@
      * some of the low-level scan parameters used by the driver are changed to
      * reduce interference with A2DP streaming.
      *
-     * @param isSet whether to enable or disable this mode
+     * @param setCoexScanMode whether to enable or disable this mode
      * @return {@code true} if the command succeeded, {@code false} otherwise.
      */
     public boolean setBluetoothCoexistenceScanMode(boolean setCoexScanMode) {
@@ -1557,6 +1548,7 @@
     private static native void waitForHalEventNative();
 
     private static class MonitorThread extends Thread {
+        @Override
         public void run() {
             Log.i(TAG, "Waiting for HAL events mWifiHalHandle=" + Long.toString(sWifiHalHandle));
             waitForHalEventNative();
diff --git a/service/java/com/android/server/wifi/WifiNetworkHistory.java b/service/java/com/android/server/wifi/WifiNetworkHistory.java
index edbc516..06081f5 100644
--- a/service/java/com/android/server/wifi/WifiNetworkHistory.java
+++ b/service/java/com/android/server/wifi/WifiNetworkHistory.java
@@ -633,7 +633,10 @@
         if (config == null || scanDetailCaches == null) return null;
         ScanDetailCache cache = scanDetailCaches.get(config.networkId);
         if (cache == null && config.networkId != WifiConfiguration.INVALID_NETWORK_ID) {
-            cache = new ScanDetailCache(config);
+            cache =
+                    new ScanDetailCache(
+                            config, WifiConfigManager.MAX_NUM_SCAN_CACHE_ENTRIES + 64,
+                            WifiConfigManager.MAX_NUM_SCAN_CACHE_ENTRIES);
             scanDetailCaches.put(config.networkId, cache);
         }
         return cache;
diff --git a/service/java/com/android/server/wifi/WifiQualifiedNetworkSelector.java b/service/java/com/android/server/wifi/WifiQualifiedNetworkSelector.java
index ff8b7f6..1dba35f 100644
--- a/service/java/com/android/server/wifi/WifiQualifiedNetworkSelector.java
+++ b/service/java/com/android/server/wifi/WifiQualifiedNetworkSelector.java
@@ -45,7 +45,7 @@
 
 /**
  * This class looks at all the connectivity scan results then
- * select an network for the phone to connect/roam to.
+ * selects a network for the phone to connect or roam to.
  */
 public class WifiQualifiedNetworkSelector {
     private WifiConfigManager mWifiConfigManager;
@@ -59,42 +59,44 @@
     private boolean mDbg = FORCE_DEBUG;
     private WifiConfiguration mCurrentConnectedNetwork = null;
     private String mCurrentBssid = null;
-    //buffer most recent scan results
-    private List<ScanDetail> mScanDetails = null;
-    //buffer of filtered scan results (Scan results considered by network selection) & associated
-    //WifiConfiguration (if any)
+
+    // Buffer of filtered scan results (Scan results considered by network selection) & associated
+    // WifiConfiguration (if any).
     private volatile List<Pair<ScanDetail, WifiConfiguration>> mFilteredScanDetails = null;
 
-    //Minimum time gap between last successful Qualified Network Selection and new selection attempt
-    //usable only when current state is connected state   default 10 s
-    private static final int MINIMUM_QUALIFIED_NETWORK_SELECTION_INTERVAL = 10 * 1000;
+    // Minimum time gap between last successful Qualified Network Selection and a new selection
+    // attempt.
+    private static final int MINIMUM_QUALIFIED_NETWORK_SELECTION_INTERVAL_MS = 10 * 1000;
 
-    //if current network is on 2.4GHz band and has a RSSI over this, need not new network selection
+    // A 2.4GHz network with RSSI value above this threshold is considered qualified. No new
+    // selection attempt necessary.
     public static final int QUALIFIED_RSSI_24G_BAND = -73;
-    //if current network is on 5GHz band and has a RSSI over this, need not new network selection
+    // A 5GHz network with RSSI value above this threshold is considered qualified. No new
+    // selection attempt necessary.
     public static final int QUALIFIED_RSSI_5G_BAND = -70;
-    //any RSSI larger than this will benefit the traffic very limited
+    // A RSSI vaule larger than this threshold is considered saturated and switching to a
+    // higher RSSI value network won't benefit the connection much.
     public static final int RSSI_SATURATION_2G_BAND = -60;
     public static final int RSSI_SATURATION_5G_BAND = -57;
-    //Any value below this will be considered not usable
+    // Any RSSI value below this is considered unacceptable, and the network will be filtered out.
     public static final int MINIMUM_2G_ACCEPT_RSSI = -85;
     public static final int MINIMUM_5G_ACCEPT_RSSI = -82;
 
+    // Constants for BSSID scoring formula.
     public static final int RSSI_SCORE_SLOPE = 4;
     public static final int RSSI_SCORE_OFFSET = 85;
-
     public static final int BAND_AWARD_5GHz = 40;
     public static final int SAME_NETWORK_AWARD = 16;
-
     public static final int SAME_BSSID_AWARD = 24;
     public static final int LAST_SELECTION_AWARD = 480;
     public static final int PASSPOINT_SECURITY_AWARD = 40;
     public static final int SECURITY_AWARD = 80;
+
+    // BSSID blacklist parameters.
     public static final int BSSID_BLACKLIST_THRESHOLD = 3;
-    public static final int BSSID_BLACKLIST_EXPIRE_TIME = 5 * 60 * 1000;
+    public static final int BSSID_BLACKLIST_EXPIRE_TIME_MS = 5 * 60 * 1000;
+
     private final int mNoIntnetPenalty;
-    //TODO: check whether we still need this one when we update the scan manager
-    public static final int SCAN_RESULT_MAXIMUNM_AGE = 40000;
     private static final int INVALID_TIME_STAMP = -1;
     private long mLastQualifiedNetworkSelectionTimeStamp = INVALID_TIME_STAMP;
 
@@ -110,10 +112,11 @@
             new HashMap<String, BssidBlacklistStatus>();
 
     /**
-     * class save the blacklist status of a given BSSID
+     * Class that saves the blacklist status of a given BSSID.
      */
     private static class BssidBlacklistStatus {
-        //how many times it is requested to be blacklisted (association rejection trigger this)
+        // Number of times this BSSID has been requested to be blacklisted.
+        // Association rejection triggers such a request.
         int mCounter;
         boolean mIsBlacklisted;
         long mBlacklistedTimeStamp = INVALID_TIME_STAMP;
@@ -152,7 +155,7 @@
     }
 
     /**
-     * set the user selected preferred band
+     * Set the user preferred band.
      *
      * @param band preferred band user selected
      */
@@ -171,8 +174,7 @@
             mNetworkScoreCache = new WifiNetworkScoreCache(context);
             mScoreManager.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache);
         } else {
-            localLoge("No network score service: Couldn't register as a WiFi score Manager, type="
-                    + NetworkKey.TYPE_WIFI + " service= " + Context.NETWORK_SCORE_SERVICE);
+            localLoge("Couldn't get NETWORK_SCORE_SERVICE.");
             mNetworkScoreCache = null;
         }
 
@@ -207,50 +209,58 @@
     }
 
     /**
-     * check whether current network is good enough we need not consider any potential switch
+     * Check if the current connected network is already qualified so that network
+     * selection from the new scan results is not necessary.
      *
      * @param currentNetwork -- current connected network
-     * @return true -- qualified and do not consider potential network switch
-     *         false -- not good enough and should try potential network switch
      */
-    private boolean isNetworkQualified(WifiConfiguration currentNetwork) {
-
+    private boolean isCurrentNetworkQualified(WifiConfiguration currentNetwork) {
         if (currentNetwork == null) {
-            localLog("Disconnected");
+            localLog("No current connected network");
             return false;
         } else {
-            localLog("Current network is: " + currentNetwork.SSID + " ,ID is: "
-                    + currentNetwork.networkId);
+            localLog("Current connected network: " + currentNetwork.SSID
+                    + " , ID: " + currentNetwork.networkId);
         }
 
-        //if current connected network is an ephemeral network,we will consider
-        // there is no current network
+        // Ephemeral networks are not qualified.
         if (currentNetwork.ephemeral) {
-            localLog("Current is ephemeral. Start reselect");
+            localLog("Current network is an ephemeral one");
             return false;
         }
 
-        //if current network is open network, not qualified
+        // Open networks are not qualified.
         if (mWifiConfigManager.isOpenNetwork(currentNetwork)) {
-            localLog("Current network is open network");
+            localLog("Current network is a open one");
             return false;
         }
 
-        // Current network band must match with user preference selection
-        if (mWifiInfo.is24GHz() && (mUserPreferedBand != WifiManager.WIFI_FREQUENCY_BAND_2GHZ)) {
-            localLog("Current band dose not match user preference. Start Qualified Network"
-                    + " Selection Current band = " + (mWifiInfo.is24GHz() ? "2.4GHz band"
-                    : "5GHz band") + "UserPreference band = " + mUserPreferedBand);
+        // Does the current network band match the user preference?
+        //
+        // Note, here the check for 2.4GHz band is different from the one for 5GHz band
+        // such that 5GHz band is always favored.
+        // When the current network is 2.4GHz, it is considered as not qualified as long
+        // as the band preference set by user is not 2.4GHz only. This gives QNS an
+        // opportunity to recommend a 5GHz network if one is available.
+        // When the current network is 5GHz, it's considered as not qualified only if
+        // the band preference set by user is 2.4GHz only.
+        if ((mWifiInfo.is24GHz() && (mUserPreferedBand != WifiManager.WIFI_FREQUENCY_BAND_2GHZ))
+                || (mWifiInfo.is5GHz()
+                     && (mUserPreferedBand == WifiManager.WIFI_FREQUENCY_BAND_2GHZ))) {
+            localLog("Current network band does not match user preference: "
+                    + "current network band=" + (mWifiInfo.is24GHz() ? "2.4GHz" : "5GHz")
+                    + ", however user preferred band=" + mUserPreferedBand);
             return false;
         }
 
+        // Is the current network's singnal strength qualified?
         int currentRssi = mWifiInfo.getRssi();
         if ((mWifiInfo.is24GHz()
                         && currentRssi < mWifiConfigManager.mThresholdQualifiedRssi24.get())
                 || (mWifiInfo.is5GHz()
                         && currentRssi < mWifiConfigManager.mThresholdQualifiedRssi5.get())) {
-            localLog("Current band = " + (mWifiInfo.is24GHz() ? "2.4GHz band" : "5GHz band")
-                    + "current RSSI is: " + currentRssi);
+            localLog("Current network band=" + (mWifiInfo.is24GHz() ? "2.4GHz" : "5GHz")
+                    + ", RSSI[" + currentRssi + "]-acceptable but not qualified");
             return false;
         }
 
@@ -258,7 +268,7 @@
     }
 
     /**
-     * check whether QualifiedNetworkSelection is needed or not
+     * Check whether QualifiedNetworkSelection is needed.
      *
      * @param isLinkDebouncing true -- Link layer is under debouncing
      *                         false -- Link layer is not under debouncing
@@ -268,37 +278,31 @@
      *                       false -- WifiStateMachine is not at disconnected state
      * @param isSupplicantTransientState true -- supplicant is in a transient state now
      *                                   false -- supplicant is not in a transient state now
-     * @return true -- need a Qualified Network Selection procedure
-     *         false -- do not need a QualifiedNetworkSelection procedure
      */
     private boolean needQualifiedNetworkSelection(boolean isLinkDebouncing, boolean isConnected,
             boolean isDisconnected, boolean isSupplicantTransientState) {
-        if (mScanDetails.size() == 0) {
-            localLog("empty scan result");
-            return false;
-        }
-
-        // Do not trigger Qualified Network Selection during L2 link debouncing procedure
+        // No Qualified Network Selection during the L2 link debouncing procedure.
         if (isLinkDebouncing) {
-            localLog("Need not Qualified Network Selection during L2 debouncing");
+            localLog("No QNS during L2 debouncing");
             return false;
         }
 
         if (isConnected) {
-            //already connected. Just try to find better candidate
-            //if switch network is not allowed in connected mode, do not trigger Qualified Network
-            //Selection
+            // Already connected. Looking for a better candidate.
+
+            // Is network switching allowed in connected state?
             if (!mWifiConfigManager.getEnableAutoJoinWhenAssociated()) {
-                localLog("Switch network under connection is not allowed");
+                localLog("Switching networks in connected state is not allowed");
                 return false;
             }
 
-            //Do not select again if last selection is within
-            //MINIMUM_QUALIFIED_NETWORK_SELECTION_INTERVAL
+            // Do not select again if last selection is within
+            // MINIMUM_QUALIFIED_NETWORK_SELECTION_INTERVAL_MS.
             if (mLastQualifiedNetworkSelectionTimeStamp != INVALID_TIME_STAMP) {
-                long gap = mClock.elapsedRealtime() - mLastQualifiedNetworkSelectionTimeStamp;
-                if (gap < MINIMUM_QUALIFIED_NETWORK_SELECTION_INTERVAL) {
-                    localLog("Too short to last successful Qualified Network Selection Gap is:"
+                long gap = mClock.getElapsedSinceBootMillis()
+                            - mLastQualifiedNetworkSelectionTimeStamp;
+                if (gap < MINIMUM_QUALIFIED_NETWORK_SELECTION_INTERVAL_MS) {
+                    localLog("Too short from last successful Qualified Network Selection. Gap is:"
                             + gap + " ms!");
                     return false;
                 }
@@ -308,14 +312,15 @@
                     mWifiConfigManager.getWifiConfiguration(mWifiInfo.getNetworkId());
             if (currentNetwork == null) {
                 // WifiStateMachine in connected state but WifiInfo is not. It means there is a race
-                // condition happened. Do not make QNS until WifiStateMachine goes into
-                // disconnected state
+                // condition. Defer QNS until WifiStateMachine enters the disconnected state.
+                //
+                // TODO(b/28249371): Root cause this race condition.
                 return false;
             }
 
-            if (!isNetworkQualified(mCurrentConnectedNetwork)) {
-                //need not trigger Qualified Network Selection if current network is qualified
-                localLog("Current network is not qualified");
+            // Already connected to a qualified network?
+            if (!isCurrentNetworkQualified(mCurrentConnectedNetwork)) {
+                localLog("Current connected network is not qualified");
                 return true;
             } else {
                 return false;
@@ -323,12 +328,13 @@
         } else if (isDisconnected) {
             mCurrentConnectedNetwork = null;
             mCurrentBssid = null;
-            //Do not start Qualified Network Selection if current state is a transient state
+            // Defer Qualified Network Selection if wpa_supplicant is in the transient state.
             if (isSupplicantTransientState) {
                 return false;
             }
         } else {
-            //Do not allow new network selection in other state
+            // Do not allow new network selection if WifiStateMachine is in a state
+            // other than connected or disconnected.
             localLog("WifiStateMachine is not on connected or disconnected state");
             return false;
         }
@@ -341,68 +347,67 @@
             StringBuffer sbuf) {
 
         int score = 0;
-        //calculate the RSSI score
+        // Calculate the RSSI score.
         int rssi = scanResult.level <= mWifiConfigManager.mThresholdSaturatedRssi24.get()
                 ? scanResult.level : mWifiConfigManager.mThresholdSaturatedRssi24.get();
         score += (rssi + mRssiScoreOffset) * mRssiScoreSlope;
-        sbuf.append(" RSSI score: " +  score);
+        sbuf.append("RSSI score: ").append(score);
+
+        // 5GHz band bonus.
         if (scanResult.is5GHz()) {
-            //5GHz band
             score += mWifiConfigManager.mBandAward5Ghz.get();
-            sbuf.append(" 5GHz bonus: " + mWifiConfigManager.mBandAward5Ghz.get());
+            sbuf.append(" 5GHz bonus: ").append(mWifiConfigManager.mBandAward5Ghz.get());
         }
 
-        //last user selection award
+        // Last user selection award.
         if (sameSelect) {
-            long timeDifference = mClock.elapsedRealtime()
+            long timeDifference = mClock.getElapsedSinceBootMillis()
                     - mWifiConfigManager.getLastSelectedTimeStamp();
 
             if (timeDifference > 0) {
                 int bonus = mLastSelectionAward - (int) (timeDifference / 1000 / 60);
                 score += bonus > 0 ? bonus : 0;
-                sbuf.append(" User selected it last time " + (timeDifference / 1000 / 60)
-                        + " minutes ago, bonus:" + bonus);
+                sbuf.append(" User selected it last time ").append(timeDifference / 1000 / 60)
+                        .append(" minutes ago, bonus: ").append(bonus);
             }
         }
 
-        //same network award
+        // Same network award.
         if (network == currentNetwork || network.isLinked(currentNetwork)) {
             score += mWifiConfigManager.mCurrentNetworkBoost.get();
-            sbuf.append(" Same network with current associated. Bonus: "
-                    + mWifiConfigManager.mCurrentNetworkBoost.get());
+            sbuf.append(" Same network as the current one, bonus: ")
+                    .append(mWifiConfigManager.mCurrentNetworkBoost.get());
         }
 
-        //same BSSID award
+        // Same BSSID award.
         if (sameBssid) {
             score += mSameBssidAward;
-            sbuf.append(" Same BSSID with current association. Bonus: " + mSameBssidAward);
+            sbuf.append(" Same BSSID as the current one, bonus: ").append(mSameBssidAward);
         }
 
-        //security award
+        // Security award.
         if (network.isPasspoint()) {
             score += mPasspointSecurityAward;
-            sbuf.append(" Passpoint Bonus:" + mPasspointSecurityAward);
+            sbuf.append(" Passpoint bonus: ").append(mPasspointSecurityAward);
         } else if (!mWifiConfigManager.isOpenNetwork(network)) {
             score += mSecurityAward;
-            sbuf.append(" Secure network Bonus:" + mSecurityAward);
+            sbuf.append(" Secure network bonus: ").append(mSecurityAward);
         }
 
-        //Penalty for no internet network. Make sure if there is any network with Internet,
-        //however, if there is no any other network with internet, this network can be chosen
+        // No internet penalty.
         if (network.numNoInternetAccessReports > 0 && !network.validatedInternetAccess) {
             score -= mNoIntnetPenalty;
-            sbuf.append(" No internet Penalty:-" + mNoIntnetPenalty);
+            sbuf.append(" No internet penalty: -").append(mNoIntnetPenalty);
         }
 
-
-        sbuf.append(" Score for scanResult: " + scanResult +  " and Network ID: "
-                + network.networkId + " final score:" + score + "\n\n");
+        sbuf.append("    -- ScanResult: ").append(scanResult).append(" for network: ")
+                .append(network.networkId).append(" score: ").append(score).append(" --\n");
 
         return score;
     }
 
     /**
-     * This API try to update all the saved networks' network selection status
+     * Update all the saved networks' selection status
      */
     private void updateSavedNetworkSelectionStatus() {
         List<WifiConfiguration> savedNetworks = mWifiConfigManager.getSavedNetworks();
@@ -411,62 +416,65 @@
             return;
         }
 
-        StringBuffer sbuf = new StringBuffer("Saved Network List\n");
+        StringBuffer sbuf = new StringBuffer("Saved Network List: \n");
         for (WifiConfiguration network : savedNetworks) {
             WifiConfiguration config = mWifiConfigManager.getWifiConfiguration(network.networkId);
             WifiConfiguration.NetworkSelectionStatus status =
                     config.getNetworkSelectionStatus();
 
-            //If the configuration is temporarily disabled, try to re-enable it
+            // If a configuration is temporarily disabled, re-enable it before trying
+            // to connect to it.
             if (status.isNetworkTemporaryDisabled()) {
                 mWifiConfigManager.tryEnableQualifiedNetwork(network.networkId);
             }
 
-            //clean the cached candidate, score and seen
+            // Clear the cached candidate, score and seen.
             status.setCandidate(null);
             status.setCandidateScore(Integer.MIN_VALUE);
             status.setSeenInLastQualifiedNetworkSelection(false);
 
-            //print the debug messages
-            sbuf.append("    " + getNetworkString(network) + " " + " User Preferred BSSID:"
-                    + network.BSSID + " FQDN:" + network.FQDN + " "
-                    + status.getNetworkStatusString() + " Disable account: ");
-            for (int index = status.NETWORK_SELECTION_ENABLE;
-                    index < status.NETWORK_SELECTION_DISABLED_MAX; index++) {
-                sbuf.append(status.getDisableReasonCounter(index) + " ");
+            sbuf.append(" ").append(getNetworkString(network)).append(" ")
+                    .append(" User Preferred BSSID: ").append(network.BSSID)
+                    .append(" FQDN: ").append(network.FQDN).append(" ")
+                    .append(status.getNetworkStatusString()).append(" Disable account: ");
+            for (int index = WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE;
+                    index < WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_DISABLED_MAX;
+                    index++) {
+                sbuf.append(status.getDisableReasonCounter(index)).append(" ");
             }
-            sbuf.append("Connect Choice:" + status.getConnectChoice() + " set time:"
-                    + status.getConnectChoiceTimestamp());
-            sbuf.append("\n");
+            sbuf.append("Connect Choice: ").append(status.getConnectChoice())
+                .append(" set time: ").append(status.getConnectChoiceTimestamp())
+                .append("\n");
         }
         localLog(sbuf.toString());
     }
 
     /**
-     * This API is called when user explicitly select a network. Currently, it is used in following
+     * This API is called when user explicitly selects a network. Currently, it is used in following
      * cases:
-     * (1) User explicitly choose to connect to a saved network
-     * (2) User save a network after add a new network
-     * (3) User save a network after modify a saved network
+     * (1) User explicitly chooses to connect to a saved network.
+     * (2) User saves a network after adding a new network.
+     * (3) User saves a network after modifying a saved network.
      * Following actions will be triggered:
-     * 1. if this network is disabled, we need re-enable it again
-     * 2. we considered user prefer this network over all the networks visible in latest network
-     *    selection procedure
+     * 1. If this network is disabled, we need re-enable it again.
+     * 2. This network is favored over all the other networks visible in latest network
+     *    selection procedure.
      *
-     * @param netId new network ID for either the network the user choose or add
-     * @param persist whether user has the authority to overwrite current connect choice
-     * @return true -- There is change made to connection choice of any saved network
-     *         false -- There is no change made to connection choice of any saved network
+     * @param netId  ID for the network chosen by the user
+     * @param persist  whether user has the authority to overwrite current connect choice
+     * @return true -- There is change made to connection choice of any saved network.
+     *         false -- There is no change made to connection choice of any saved network.
      */
     public boolean userSelectNetwork(int netId, boolean persist) {
+        localLog("userSelectNetwork: network ID=" + netId + " persist=" + persist);
+
         WifiConfiguration selected = mWifiConfigManager.getWifiConfiguration(netId);
-        localLog("userSelectNetwork:" + netId + " persist:" + persist);
         if (selected == null || selected.SSID == null) {
-            localLoge("userSelectNetwork: Bad configuration with nid=" + netId);
+            localLoge("userSelectNetwork: Invalid configuration with nid=" + netId);
             return false;
         }
 
-
+        // Enable the network if it is disabled.
         if (!selected.getNetworkSelectionStatus().isNetworkEnabled()) {
             mWifiConfigManager.updateNetworkSelectionStatus(netId,
                     WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
@@ -480,7 +488,7 @@
         boolean change = false;
         String key = selected.configKey();
         // This is only used for setting the connect choice timestamp for debugging purposes.
-        long currentTime = mClock.currentTimeMillis();
+        long currentTime = mClock.getWallClockMillis();
         List<WifiConfiguration> savedNetworks = mWifiConfigManager.getSavedNetworks();
 
         for (WifiConfiguration network : savedNetworks) {
@@ -502,14 +510,15 @@
             if (status.getSeenInLastQualifiedNetworkSelection()
                     && (status.getConnectChoice() == null
                     || !status.getConnectChoice().equals(key))) {
-                localLog("Add key:" + key + " Set Time: " + currentTime + " to "
+                localLog("Add key: " + key + " Set Time: " + currentTime + " to "
                         + getNetworkString(config));
                 status.setConnectChoice(key);
                 status.setConnectChoiceTimestamp(currentTime);
                 change = true;
             }
         }
-        //Write this change to file
+
+        // Persist changes.
         if (change) {
             mWifiConfigManager.writeKnownNetworkHistory();
             return true;
@@ -519,7 +528,7 @@
     }
 
     /**
-     * enable/disable a BSSID for Quality Network Selection
+     * Enable/disable a BSSID for Quality Network Selection
      * When an association rejection event is obtained, Quality Network Selector will disable this
      * BSSID but supplicant still can try to connect to this bssid. If supplicant connect to it
      * successfully later, this bssid can be re-enabled.
@@ -535,7 +544,7 @@
             if (bssid != null) {
                 BssidBlacklistStatus status = mBssidBlacklist.get(bssid);
                 if (status == null) {
-                    //first time
+                    // First time for this BSSID
                     BssidBlacklistStatus newStatus = new BssidBlacklistStatus();
                     newStatus.mCounter++;
                     mBssidBlacklist.put(bssid, newStatus);
@@ -543,7 +552,7 @@
                     status.mCounter++;
                     if (status.mCounter >= BSSID_BLACKLIST_THRESHOLD) {
                         status.mIsBlacklisted = true;
-                        status.mBlacklistedTimeStamp = mClock.elapsedRealtime();
+                        status.mBlacklistedTimeStamp = mClock.getElapsedSinceBootMillis();
                         return true;
                     }
                 }
@@ -553,18 +562,18 @@
     }
 
     /**
-     * update the buffered BSSID blacklist
+     * Update the buffered BSSID blacklist
      *
      * Go through the whole buffered BSSIDs blacklist and check when the BSSIDs is blocked. If they
-     * were blacked before BSSID_BLACKLIST_EXPIRE_TIME, re-enable it again.
+     * have been blacklisted for BSSID_BLACKLIST_EXPIRE_TIME_MS, re-enable them.
      */
     private void updateBssidBlacklist() {
         Iterator<BssidBlacklistStatus> iter = mBssidBlacklist.values().iterator();
         while (iter.hasNext()) {
             BssidBlacklistStatus status = iter.next();
             if (status != null && status.mIsBlacklisted) {
-                if (mClock.elapsedRealtime() - status.mBlacklistedTimeStamp
-                            >= BSSID_BLACKLIST_EXPIRE_TIME) {
+                if (mClock.getElapsedSinceBootMillis() - status.mBlacklistedTimeStamp
+                            >= BSSID_BLACKLIST_EXPIRE_TIME_MS) {
                     iter.remove();
                 }
             }
@@ -574,8 +583,6 @@
     /**
      * Check whether a bssid is disabled
      * @param bssid -- the bssid to check
-     * @return true -- bssid is disabled
-     *         false -- bssid is not disabled
      */
     public boolean isBssidDisabled(String bssid) {
         BssidBlacklistStatus status = mBssidBlacklist.get(bssid);
@@ -583,35 +590,36 @@
     }
 
     /**
-     * ToDo: This should be called in Connectivity Manager when it gets new scan result
-     * check whether a network slection is needed. If need, check all the new scan results and
-     * select a new qualified network/BSSID to connect to
+     * Select the best network candidate from the new scan results for WifiConnectivityManager
+     * to connect/roam to.
      *
-     * @param forceSelectNetwork true -- start a qualified network selection anyway,no matter
-     *                           current network is already qualified or not.
-     *                           false -- if current network is already qualified, do not do new
-     *                           selection
-     * @param isUntrustedConnectionsAllowed true -- user allow to connect to untrusted network
-     *                                      false -- user do not allow to connect to untrusted
-     *                                      network
-     * @param scanDetails latest scan result obtained (should be connectivity scan only)
-     * @param isLinkDebouncing true -- Link layer is under debouncing
-     *                         false -- Link layer is not under debouncing
-     * @param isConnected true -- device is connected to an AP currently
-     *                    false -- device is not connected to an AP currently
-     * @param isDisconnected true -- WifiStateMachine is at disconnected state
-     *                       false -- WifiStateMachine is not at disconnected state
-     * @param isSupplicantTransient true -- supplicant is in a transient state
-     *                              false -- supplicant is not in a transient state
-     * @return the qualified network candidate found. If no available candidate, return null
+     * @param forceSelectNetwork true -- start a qualified network selection anyway, no matter
+     *                                   the current network is already qualified or not.
+     *                           false -- if current network is already qualified, stay connected
+     *                                    to it.
+     * @param isUntrustedConnectionsAllowed connection to untrusted networks is allowed or not
+     * @param isLinkDebouncing Link layer is under debouncing or not
+     * @param isConnected WifiStateMachine is in the Connected state or not
+     * @param isDisconnected WifiStateMachine is in the Disconnected state or not
+     * @param isSupplicantTransient wpa_supplicant is in a transient state or not
+     * @param scanDetails new connectivity scan results
+     * @return Best network candidate identified. Null if no candidate available or we should
+     *         stay connected to the current network.
      */
-    public WifiConfiguration selectQualifiedNetwork(boolean forceSelectNetwork ,
-            boolean isUntrustedConnectionsAllowed, List<ScanDetail>  scanDetails,
-            boolean isLinkDebouncing, boolean isConnected, boolean isDisconnected,
-            boolean isSupplicantTransient) {
+    public WifiConfiguration selectQualifiedNetwork(boolean forceSelectNetwork,
+            boolean isUntrustedConnectionsAllowed, boolean isLinkDebouncing,
+            boolean isConnected, boolean isDisconnected, boolean isSupplicantTransient,
+            List<ScanDetail>  scanDetails) {
         localLog("==========start qualified Network Selection==========");
-        mScanDetails = scanDetails;
-        List<Pair<ScanDetail, WifiConfiguration>>  filteredScanDetails = new ArrayList<>();
+
+        List<Pair<ScanDetail, WifiConfiguration>> filteredScanDetails = new ArrayList<>();
+
+        if (scanDetails.size() == 0) {
+            localLog("Empty connectivity scan result");
+            mFilteredScanDetails = filteredScanDetails;
+            return null;
+        }
+
         if (mCurrentConnectedNetwork == null) {
             mCurrentConnectedNetwork =
                     mWifiConfigManager.getWifiConfiguration(mWifiInfo.getNetworkId());
@@ -623,8 +631,7 @@
 
         if (!forceSelectNetwork && !needQualifiedNetworkSelection(isLinkDebouncing, isConnected,
                 isDisconnected, isSupplicantTransient)) {
-            localLog("Quit qualified Network Selection since it is not forced and current network"
-                    + " is qualified already");
+            localLog("Stay connected to the current qualified network");
             mFilteredScanDetails = filteredScanDetails;
             return null;
         }
@@ -639,8 +646,8 @@
                 mWifiConfigManager.getWifiConfiguration(lastUserSelectedNetWorkKey);
         if (lastUserSelectedNetwork != null) {
             localLog("Last selection is " + lastUserSelectedNetwork.SSID + " Time to now: "
-                    + ((mClock.elapsedRealtime() - mWifiConfigManager.getLastSelectedTimeStamp())
-                            / 1000 / 60 + " minutes"));
+                    + ((mClock.getElapsedSinceBootMillis()
+                    - mWifiConfigManager.getLastSelectedTimeStamp()) / 1000 / 60 + " minutes"));
         }
 
         updateSavedNetworkSelectionStatus();
@@ -649,51 +656,65 @@
         StringBuffer lowSignalScan = new StringBuffer();
         StringBuffer notSavedScan = new StringBuffer();
         StringBuffer noValidSsid = new StringBuffer();
+        StringBuffer unwantedBand = new StringBuffer();
         StringBuffer scoreHistory =  new StringBuffer();
         ArrayList<NetworkKey> unscoredNetworks = new ArrayList<NetworkKey>();
 
-        //iterate all scan results and find the best candidate with the highest score
-        for (ScanDetail scanDetail : mScanDetails) {
+        // Iterate over all scan results to find the best candidate.
+        for (ScanDetail scanDetail : scanDetails) {
             ScanResult scanResult = scanDetail.getScanResult();
-            //skip bad scan result
+            // Skip bad scan result.
             if (scanResult.SSID == null || TextUtils.isEmpty(scanResult.SSID)) {
                 if (mDbg) {
-                    //We should not see this in ePNO
-                    noValidSsid.append(scanResult.BSSID + " / ");
+                    noValidSsid.append(scanResult.BSSID).append(" / ");
                 }
                 continue;
             }
 
             final String scanId = toScanId(scanResult);
-            //check whether this BSSID is blocked or not
+            // Skip blacklisted BSSID.
             if (mWifiConfigManager.isBssidBlacklisted(scanResult.BSSID)
                     || isBssidDisabled(scanResult.BSSID)) {
-                //We should not see this in ePNO
-                Log.e(TAG, scanId + " is in blacklist.");
+                Log.i(TAG, scanId + " is in the blacklist.");
                 continue;
             }
 
-            //skip scan result with too weak signals
+            // Skip network with too weak signals.
             if ((scanResult.is24GHz() && scanResult.level
                     < mWifiConfigManager.mThresholdMinimumRssi24.get())
                     || (scanResult.is5GHz() && scanResult.level
                     < mWifiConfigManager.mThresholdMinimumRssi5.get())) {
                 if (mDbg) {
-                    lowSignalScan.append(scanId + "(" + (scanResult.is24GHz() ? "2.4GHz" : "5GHz")
-                            + ")" + scanResult.level + " / ");
+                    lowSignalScan.append(scanId).append("(")
+                        .append(scanResult.is24GHz() ? "2.4GHz" : "5GHz")
+                        .append(")").append(scanResult.level).append(" / ");
                 }
                 continue;
             }
 
-            //check if there is already a score for this network
+            // Skip network not matching band preference set by user.
+            // WifiConnectivityManager schedules scan according to the user band prefrence. This is
+            // a check for the ScanResults generated from the old settings.
+            if ((scanResult.is24GHz()
+                    && (mUserPreferedBand == WifiManager.WIFI_FREQUENCY_BAND_5GHZ))
+                    || (scanResult.is5GHz()
+                        && (mUserPreferedBand == WifiManager.WIFI_FREQUENCY_BAND_2GHZ))) {
+                if (mDbg) {
+                    unwantedBand.append(scanId).append("(")
+                        .append(scanResult.is24GHz() ? "2.4GHz" : "5GHz")
+                        .append(")").append(" / ");
+                }
+                continue;
+            }
+
+            // Is there a score for this network? If not, request a score.
             if (mNetworkScoreCache != null && !mNetworkScoreCache.isScoredNetwork(scanResult)) {
-                //no score for this network yet.
                 WifiKey wifiKey;
 
                 try {
                     wifiKey = new WifiKey("\"" + scanResult.SSID + "\"", scanResult.BSSID);
                     NetworkKey ntwkKey = new NetworkKey(wifiKey);
-                    //add to the unscoredNetworks list so we can request score later
+                    // Add to the unscoredNetworks list so we can request score later
                     unscoredNetworks.add(ntwkKey);
                 } catch (IllegalArgumentException e) {
                     Log.w(TAG, "Invalid SSID=" + scanResult.SSID + " BSSID=" + scanResult.BSSID
@@ -701,7 +722,7 @@
                 }
             }
 
-            //check whether this scan result belong to a saved network
+            // Is this scan result from an ephemeral network?
             boolean potentiallyEphemeral = false;
             // Stores WifiConfiguration of potential connection candidates for scan result filtering
             WifiConfiguration potentialEphemeralCandidate = null;
@@ -711,10 +732,10 @@
             if (associatedWifiConfigurations == null) {
                 potentiallyEphemeral =  true;
                 if (mDbg) {
-                    notSavedScan.append(scanId + " / ");
+                    notSavedScan.append(scanId).append(" / ");
                 }
             } else if (associatedWifiConfigurations.size() == 1) {
-                //if there are more than 1 associated network, it must be a passpoint network
+                // If there is more than one associated network, it must be a passpoint network.
                 WifiConfiguration network = associatedWifiConfigurations.get(0);
                 if (network.ephemeral) {
                     potentialEphemeralCandidate = network;
@@ -728,7 +749,7 @@
                 if (isUntrustedConnectionsAllowed) {
                     Integer netScore = getNetworkScore(scanResult, false);
                     if (netScore != null
-                        && !mWifiConfigManager.wasEphemeralNetworkDeleted(scanResult.SSID)) {
+                            && !mWifiConfigManager.wasEphemeralNetworkDeleted(scanResult.SSID)) {
                         externalScoreEvaluator.evalUntrustedCandidate(netScore, scanResult);
                         // scanDetail is for available ephemeral network
                         filteredScanDetails.add(Pair.create(scanDetail,
@@ -738,9 +759,9 @@
                 continue;
             }
 
-            // calculate the score of each scanresult whose associated network is not ephemeral. Due
-            // to one scan result can associated with more than 1 network, we need calculate all
-            // the scores and use the highest one as the scanresults score.
+            // Calculate the score of each ScanResult whose associated network is not ephemeral.
+            // One ScanResult can associated with more than one network, hence we calculate all
+            // the scores and use the highest one as the ScanResult's score
             int highestScore = Integer.MIN_VALUE;
             int score;
             WifiConfiguration configurationCandidateForThisScan = null;
@@ -756,9 +777,9 @@
                     continue;
                 } else if (network.BSSID != null && !network.BSSID.equals("any")
                         && !network.BSSID.equals(scanResult.BSSID)) {
-                    //in such scenario, user (APP) has specified the only BSSID to connect for this
-                    // configuration. So only the matched scan result can be candidate
-                    localLog("Network: " + getNetworkString(network) + " has specified" + "BSSID:"
+                    // App has specified the only BSSID to connect for this
+                    // configuration. So only the matching ScanResult can be a candidate.
+                    localLog("Network " + getNetworkString(network) + " has specified BSSID "
                             + network.BSSID + ". Skip " + scanResult.BSSID);
                     continue;
                 }
@@ -780,13 +801,13 @@
                     configurationCandidateForThisScan = network;
                     potentialCandidate = network;
                 }
-                //update the cached candidate
+                // Update the cached candidate.
                 if (score > status.getCandidateScore()) {
                     status.setCandidate(scanResult);
                     status.setCandidateScore(score);
                 }
             }
-            // Create potential filteredScanDetail entry
+            // Create potential filteredScanDetail entry.
             filteredScanDetails.add(Pair.create(scanDetail, potentialCandidate));
 
             if (highestScore > currentHighestScore || (highestScore == currentHighestScore
@@ -800,7 +821,7 @@
 
         mFilteredScanDetails = filteredScanDetails;
 
-        //kick the score manager if there is any unscored network
+        // Kick the score manager if there is any unscored network.
         if (mScoreManager != null && unscoredNetworks.size() != 0) {
             NetworkKey[] unscoredNetworkKeys =
                     unscoredNetworks.toArray(new NetworkKey[unscoredNetworks.size()]);
@@ -808,13 +829,22 @@
         }
 
         if (mDbg) {
-            localLog(lowSignalScan + " skipped due to low signal\n");
-            localLog(notSavedScan + " skipped due to not saved\n ");
-            localLog(noValidSsid + " skipped due to not valid SSID\n");
+            if (lowSignalScan.length() != 0) {
+                localLog(lowSignalScan + " skipped due to low signal");
+            }
+            if (notSavedScan.length() != 0) {
+                localLog(notSavedScan + " skipped due to not saved");
+            }
+            if (noValidSsid.length() != 0) {
+                localLog(noValidSsid + " skipped due to invalid SSID");
+            }
+            if (unwantedBand.length() != 0) {
+                localLog(unwantedBand + " skipped due to user band preference");
+            }
             localLog(scoreHistory.toString());
         }
 
-        //we need traverse the whole user preference to choose the one user like most now
+        // Traverse the whole user preference to choose the one user likes the most.
         if (scanResultCandidate != null) {
             WifiConfiguration tempConfig = networkCandidate;
 
@@ -830,12 +860,12 @@
                         networkCandidate = tempConfig;
                     }
                 } else {
-                    //we should not come here in theory
+                    // We should not come here in theory.
                     localLoge("Connect choice: " + key + " has no corresponding saved config");
                     break;
                 }
             }
-            localLog("After user choice adjust, the final candidate is:"
+            localLog("After user choice adjustment, the final candidate is:"
                     + getNetworkString(networkCandidate) + " : " + scanResultCandidate.BSSID);
         }
 
@@ -857,27 +887,15 @@
         String currentAssociationId = mCurrentConnectedNetwork == null ? "Disconnected" :
                 getNetworkString(mCurrentConnectedNetwork);
         String targetAssociationId = getNetworkString(networkCandidate);
-        //In passpoint, saved configuration has garbage SSID. We need update it with the SSID of
-        //the scan result.
+        // In passpoint, saved configuration is initialized with a fake SSID. Now update it with
+        // the real SSID from the scan result.
         if (networkCandidate.isPasspoint()) {
-            // This will update the passpoint configuration in WifiConfigManager
             networkCandidate.SSID = "\"" + scanResultCandidate.SSID + "\"";
         }
 
-        //For debug purpose only
-        if (scanResultCandidate.BSSID.equals(mCurrentBssid)) {
-            localLog(currentAssociationId + " is already the best choice!");
-        } else if (mCurrentConnectedNetwork != null
-                && (mCurrentConnectedNetwork.networkId == networkCandidate.networkId
-                || mCurrentConnectedNetwork.isLinked(networkCandidate))) {
-            localLog("Roaming from " + currentAssociationId + " to " + targetAssociationId);
-        } else {
-            localLog("reconnect from " + currentAssociationId + " to " + targetAssociationId);
-        }
-
         mCurrentBssid = scanResultCandidate.BSSID;
         mCurrentConnectedNetwork = networkCandidate;
-        mLastQualifiedNetworkSelectionTimeStamp = mClock.elapsedRealtime();
+        mLastQualifiedNetworkSelectionTimeStamp = mClock.getElapsedSinceBootMillis();
         return networkCandidate;
     }
 
@@ -958,7 +976,7 @@
                                   : String.format("%s:%s", scanResult.SSID, scanResult.BSSID);
     }
 
-    //Dump the logs
+    // Dump the logs.
     void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("Dump of WifiQualifiedNetworkSelector");
         pw.println("WifiQualifiedNetworkSelector - Log Begin ----");
diff --git a/service/java/com/android/server/wifi/WifiScoreReport.java b/service/java/com/android/server/wifi/WifiScoreReport.java
index 50e28bf..dfdf3cd 100644
--- a/service/java/com/android/server/wifi/WifiScoreReport.java
+++ b/service/java/com/android/server/wifi/WifiScoreReport.java
@@ -97,12 +97,8 @@
                                                  WifiConfigManager wifiConfigManager,
                                                  NetworkAgent networkAgent,
                                                  WifiScoreReport lastReport,
-                                                 int aggressiveHandover) {
-        boolean debugLogging = false;
-        if (wifiConfigManager.mEnableVerboseLogging.get() > 0) {
-            debugLogging = true;
-        }
-
+                                                 int aggressiveHandover,
+                                                 boolean verboseLogging) {
         StringBuilder sb = new StringBuilder();
 
         int score = STARTING_SCORE;
@@ -258,7 +254,7 @@
                     currentConfiguration.numTicksAtNotHighRSSI));
         }
 
-        if (debugLogging) {
+        if (verboseLogging) {
             String rssiStatus = "";
             if (isBadRSSI) {
                 rssiStatus += " badRSSI ";
@@ -286,7 +282,7 @@
                 wifiInfo.linkStuckCount += 1;
             }
             sb.append(String.format(" ls+=%d", wifiInfo.linkStuckCount));
-            if (debugLogging) {
+            if (verboseLogging) {
                 Log.d(TAG, " bad link -> stuck count ="
                         + Integer.toString(wifiInfo.linkStuckCount));
             }
@@ -295,7 +291,7 @@
                 wifiInfo.linkStuckCount -= 1;
             }
             sb.append(String.format(" ls-=%d", wifiInfo.linkStuckCount));
-            if (debugLogging) {
+            if (verboseLogging) {
                 Log.d(TAG, " good link -> stuck count ="
                         + Integer.toString(wifiInfo.linkStuckCount));
             }
@@ -311,7 +307,7 @@
 
         if (isBadLinkspeed) {
             score -= BAD_LINKSPEED_PENALTY;
-            if (debugLogging) {
+            if (verboseLogging) {
                 Log.d(TAG, " isBadLinkspeed   ---> count=" + badLinkspeedcount
                         + " score=" + Integer.toString(score));
             }
@@ -338,7 +334,7 @@
         score -= wifiInfo.badRssiCount * BAD_RSSI_COUNT_PENALTY + wifiInfo.lowRssiCount;
         sb.append(String.format(",%d", score));
 
-        if (debugLogging) {
+        if (verboseLogging) {
             Log.d(TAG, " badRSSI count" + Integer.toString(wifiInfo.badRssiCount)
                     + " lowRSSI count" + Integer.toString(wifiInfo.lowRssiCount)
                     + " --> score " + Integer.toString(score));
@@ -346,7 +342,7 @@
 
         if (isHighRSSI) {
             score += 5;
-            if (debugLogging) Log.d(TAG, " isHighRSSI       ---> score=" + Integer.toString(score));
+            if (verboseLogging) Log.d(TAG, " isHighRSSI       ---> score=" + score);
         }
         sb.append(String.format(",%d]", score));
 
@@ -362,7 +358,7 @@
 
         //report score
         if (score != wifiInfo.score) {
-            if (debugLogging) {
+            if (verboseLogging) {
                 Log.d(TAG, "calculateWifiScore() report new score " + Integer.toString(score));
             }
             wifiInfo.score = score;
diff --git a/service/java/com/android/server/wifi/WifiServiceImpl.java b/service/java/com/android/server/wifi/WifiServiceImpl.java
index be24e49..cb107f5 100644
--- a/service/java/com/android/server/wifi/WifiServiceImpl.java
+++ b/service/java/com/android/server/wifi/WifiServiceImpl.java
@@ -73,7 +73,6 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.SystemClock;
-import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.WorkSource;
@@ -82,13 +81,10 @@
 import android.util.Log;
 import android.util.Slog;
 
-import com.android.internal.R;
-import com.android.internal.app.IBatteryStats;
 import com.android.internal.telephony.IccCardConstants;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.util.AsyncChannel;
-import com.android.server.am.BatteryStatsService;
 import com.android.server.wifi.configparse.ConfigBuilder;
 
 import org.xml.sax.SAXException;
@@ -123,19 +119,12 @@
     private static final String TAG = "WifiService";
     private static final boolean DBG = true;
     private static final boolean VDBG = false;
-    private static final String BOOT_DEFAULT_WIFI_COUNTRY_CODE = "ro.boot.wificountrycode";
 
     final WifiStateMachine mWifiStateMachine;
 
     private final Context mContext;
     private final FrameworkFacade mFacade;
 
-    private final List<Multicaster> mMulticasters =
-            new ArrayList<Multicaster>();
-    private int mMulticastEnabled;
-    private int mMulticastDisabled;
-
-    private final IBatteryStats mBatteryStats;
     private final PowerManager mPowerManager;
     private final AppOpsManager mAppOps;
     private final UserManager mUserManager;
@@ -155,6 +144,9 @@
     private final WifiCertManager mCertManager;
 
     private final WifiInjector mWifiInjector;
+    /* Backup/Restore Module */
+    private final WifiBackupRestore mWifiBackupRestore;
+
     /**
      * Asynchronous channel to WifiStateMachine
      */
@@ -254,7 +246,7 @@
         }
 
         private void replyFailed(Message msg, int what, int why) {
-            Message reply = msg.obtain();
+            Message reply = Message.obtain();
             reply.what = what;
             reply.arg1 = why;
             try {
@@ -306,48 +298,38 @@
     }
 
     WifiStateMachineHandler mWifiStateMachineHandler;
-
     private WifiController mWifiController;
     private final WifiLockManager mWifiLockManager;
+    private final WifiMulticastLockManager mWifiMulticastLockManager;
 
     public WifiServiceImpl(Context context) {
         mContext = context;
-        mWifiInjector = WifiInjector.getInstance();
-        mFacade = new FrameworkFacade();
-        HandlerThread wifiThread = new HandlerThread("WifiService");
-        wifiThread.start();
+        mWifiInjector = new WifiInjector(context);
+
+        mFacade = mWifiInjector.getFrameworkFacade();
         mWifiMetrics = mWifiInjector.getWifiMetrics();
-        mTrafficPoller = new WifiTrafficPoller(mContext, wifiThread.getLooper(),
-                WifiNative.getWlanNativeInterface().getInterfaceName());
+        mTrafficPoller = mWifiInjector.getWifiTrafficPoller();
         mUserManager = UserManager.get(mContext);
-        HandlerThread wifiStateMachineThread = new HandlerThread("WifiStateMachine");
-        wifiStateMachineThread.start();
-        mCountryCode = new WifiCountryCode(
-                WifiNative.getWlanNativeInterface(),
-                SystemProperties.get(BOOT_DEFAULT_WIFI_COUNTRY_CODE),
-                mFacade.getStringSetting(mContext, Settings.Global.WIFI_COUNTRY_CODE),
-                mContext.getResources().getBoolean(
-                        R.bool.config_wifi_revert_country_code_on_cellular_loss));
-        mWifiStateMachine = new WifiStateMachine(mContext, mFacade,
-            wifiStateMachineThread.getLooper(), mUserManager, mWifiInjector,
-            new BackupManagerProxy(), mCountryCode);
-        mSettingsStore = new WifiSettingsStore(mContext);
+        mCountryCode = mWifiInjector.getWifiCountryCode();
+        mWifiStateMachine = mWifiInjector.getWifiStateMachine();
         mWifiStateMachine.enableRssiPolling(true);
-        mBatteryStats = BatteryStatsService.getService();
-        mPowerManager = context.getSystemService(PowerManager.class);
-        mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
-        mCertManager = new WifiCertManager(mContext);
+        mSettingsStore = mWifiInjector.getWifiSettingsStore();
+        mPowerManager = mContext.getSystemService(PowerManager.class);
+        mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
+        mCertManager = mWifiInjector.getWifiCertManager();
 
-        mNotificationController = new WifiNotificationController(mContext,
-                wifiThread.getLooper(), mWifiStateMachine, mFacade, null);
+        mNotificationController = mWifiInjector.getWifiNotificationController();
 
-        mWifiLockManager = new WifiLockManager(mContext, mBatteryStats);
-        mClientHandler = new ClientHandler(wifiThread.getLooper());
-        mWifiStateMachineHandler = new WifiStateMachineHandler(wifiThread.getLooper());
-        mWifiController = new WifiController(mContext, mWifiStateMachine,
-                mSettingsStore, mWifiLockManager, wifiThread.getLooper(), mFacade);
-        // Set the WifiController for WifiLastResortWatchdog
-        mWifiInjector.getWifiLastResortWatchdog().setWifiController(mWifiController);
+        mWifiLockManager = mWifiInjector.getWifiLockManager();
+        mWifiMulticastLockManager = mWifiInjector.getWifiMulticastLockManager();
+        HandlerThread wifiServiceHandlerThread = mWifiInjector.getWifiServiceHandlerThread();
+        mClientHandler = new ClientHandler(wifiServiceHandlerThread.getLooper());
+        mWifiStateMachineHandler =
+                new WifiStateMachineHandler(wifiServiceHandlerThread.getLooper());
+        mWifiController = mWifiInjector.getWifiController();
+        mWifiBackupRestore = mWifiInjector.getWifiBackupRestore();
+
+        enableVerboseLoggingInternal(getVerboseLoggingLevel());
     }
 
 
@@ -419,6 +401,7 @@
      * see {@link android.net.wifi.WifiManager#pingSupplicant()}
      * @return {@code true} if the operation succeeds, {@code false} otherwise
      */
+    @Override
     public boolean pingSupplicant() {
         enforceAccessPermission();
         if (mWifiStateMachineChannel != null) {
@@ -436,6 +419,7 @@
      * @param settings If null, use default parameter, i.e. full scan.
      * @param workSource If null, all blame is given to the calling uid.
      */
+    @Override
     public void startScan(ScanSettings settings, WorkSource workSource) {
         enforceChangePermission();
         synchronized (this) {
@@ -475,6 +459,7 @@
                 settings, workSource);
     }
 
+    @Override
     public String getWpsNfcConfigurationToken(int netId) {
         enforceConnectivityInternalPermission();
         return mWifiStateMachine.syncGetWpsNfcConfigurationToken(netId);
@@ -547,6 +532,7 @@
      * @return {@code true} if the enable/disable operation was
      *         started or is already in the queue.
      */
+    @Override
     public synchronized boolean setWifiEnabled(boolean enable) {
         enforceChangePermission();
         Slog.d(TAG, "setWifiEnabled: " + enable + " pid=" + Binder.getCallingPid()
@@ -579,6 +565,7 @@
      *         {@link WifiManager#WIFI_STATE_ENABLING},
      *         {@link WifiManager#WIFI_STATE_UNKNOWN}
      */
+    @Override
     public int getWifiEnabledState() {
         enforceAccessPermission();
         return mWifiStateMachine.syncGetWifiState();
@@ -590,6 +577,7 @@
      *        part of WifiConfiguration
      * @param enabled true to enable and false to disable
      */
+    @Override
     public void setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
         enforceChangePermission();
         ConnectivityManager.enforceTetherChangePermission(mContext);
@@ -612,6 +600,7 @@
      *         {@link WifiManager#WIFI_AP_STATE_ENABLING},
      *         {@link WifiManager#WIFI_AP_STATE_FAILED}
      */
+    @Override
     public int getWifiApEnabledState() {
         enforceAccessPermission();
         return mWifiStateMachine.syncGetWifiApState();
@@ -621,6 +610,7 @@
      * see {@link WifiManager#getWifiApConfiguration()}
      * @return soft access point configuration
      */
+    @Override
     public WifiConfiguration getWifiApConfiguration() {
         enforceAccessPermission();
         return mWifiStateMachine.syncGetWifiApConfiguration();
@@ -630,6 +620,7 @@
      * see {@link WifiManager#buildWifiConfig()}
      * @return a WifiConfiguration.
      */
+    @Override
     public WifiConfiguration buildWifiConfig(String uriString, String mimeType, byte[] data) {
         if (mimeType.equals(ConfigBuilder.WifiConfigType)) {
             try {
@@ -649,6 +640,7 @@
      * see {@link WifiManager#setWifiApConfiguration(WifiConfiguration)}
      * @param wifiConfig WifiConfiguration details for soft access point
      */
+    @Override
     public void setWifiApConfiguration(WifiConfiguration wifiConfig) {
         enforceChangePermission();
         if (wifiConfig == null)
@@ -661,10 +653,9 @@
     }
 
     /**
-     * @param enable {@code true} to enable, {@code false} to disable.
-     * @return {@code true} if the enable/disable operation was
-     *         started or is already in the queue.
+     * see {@link android.net.wifi.WifiManager#isScanAlwaysAvailable()}
      */
+    @Override
     public boolean isScanAlwaysAvailable() {
         enforceAccessPermission();
         return mSettingsStore.isScanAlwaysAvailable();
@@ -673,6 +664,7 @@
     /**
      * see {@link android.net.wifi.WifiManager#disconnect()}
      */
+    @Override
     public void disconnect() {
         enforceChangePermission();
         mWifiStateMachine.disconnectCommand();
@@ -681,6 +673,7 @@
     /**
      * see {@link android.net.wifi.WifiManager#reconnect()}
      */
+    @Override
     public void reconnect() {
         enforceChangePermission();
         mWifiStateMachine.reconnectCommand();
@@ -689,6 +682,7 @@
     /**
      * see {@link android.net.wifi.WifiManager#reassociate()}
      */
+    @Override
     public void reassociate() {
         enforceChangePermission();
         mWifiStateMachine.reassociateCommand();
@@ -697,6 +691,7 @@
     /**
      * see {@link android.net.wifi.WifiManager#getSupportedFeatures}
      */
+    @Override
     public int getSupportedFeatures() {
         enforceAccessPermission();
         if (mWifiStateMachineChannel != null) {
@@ -717,6 +712,7 @@
     /**
      * see {@link android.net.wifi.WifiManager#getControllerActivityEnergyInfo(int)}
      */
+    @Override
     public WifiActivityEnergyInfo reportActivityInfo() {
         enforceAccessPermission();
         if ((getSupportedFeatures() & WifiManager.WIFI_FEATURE_LINK_LAYER_STATS) == 0) {
@@ -788,6 +784,7 @@
      * see {@link android.net.wifi.WifiManager#getConfiguredNetworks()}
      * @return the list of configured networks
      */
+    @Override
     public List<WifiConfiguration> getConfiguredNetworks() {
         enforceAccessPermission();
         if (mWifiStateMachineChannel != null) {
@@ -803,6 +800,7 @@
      * see {@link android.net.wifi.WifiManager#getPrivilegedConfiguredNetworks()}
      * @return the list of configured networks with real preSharedKey
      */
+    @Override
     public List<WifiConfiguration> getPrivilegedConfiguredNetworks() {
         enforceReadCredentialPermission();
         enforceAccessPermission();
@@ -819,6 +817,7 @@
      * @param scanResult scanResult that represents the BSSID
      * @return {@link WifiConfiguration} that matches this BSSID or null
      */
+    @Override
     public WifiConfiguration getMatchingWifiConfig(ScanResult scanResult) {
         enforceAccessPermission();
         return mWifiStateMachine.syncGetMatchingWifiConfig(scanResult, mWifiStateMachineChannel);
@@ -829,6 +828,7 @@
      * @return the supplicant-assigned identifier for the new or updated
      * network if the operation succeeds, or {@code -1} if it fails
      */
+    @Override
     public int addOrUpdateNetwork(WifiConfiguration config) {
         enforceChangePermission();
         if (isValid(config) && isValidPasspoint(config)) {
@@ -899,6 +899,7 @@
      * to the supplicant
      * @return {@code true} if the operation succeeded
      */
+    @Override
     public boolean removeNetwork(int netId) {
         enforceChangePermission();
 
@@ -917,6 +918,7 @@
      * @param disableOthers if true, disable all other networks.
      * @return {@code true} if the operation succeeded
      */
+    @Override
     public boolean enableNetwork(int netId, boolean disableOthers) {
         enforceChangePermission();
         if (mWifiStateMachineChannel != null) {
@@ -934,6 +936,7 @@
      * to the supplicant
      * @return {@code true} if the operation succeeded
      */
+    @Override
     public boolean disableNetwork(int netId) {
         enforceChangePermission();
         if (mWifiStateMachineChannel != null) {
@@ -948,6 +951,7 @@
      * See {@link android.net.wifi.WifiManager#getConnectionInfo()}
      * @return the Wi-Fi information, contained in {@link WifiInfo}.
      */
+    @Override
     public WifiInfo getConnectionInfo() {
         enforceAccessPermission();
         /*
@@ -962,6 +966,7 @@
      * a list of {@link ScanResult} objects.
      * @return the list of results
      */
+    @Override
     public List<ScanResult> getScanResults(String callingPackage) {
         enforceAccessPermission();
         int userId = UserHandle.getCallingUserId();
@@ -998,6 +1003,7 @@
      * @param mo The MO in XML form
      * @return -1 for failure
      */
+    @Override
     public int addPasspointManagementObject(String mo) {
         return mWifiStateMachine.syncAddPasspointManagementObject(mWifiStateMachineChannel, mo);
     }
@@ -1008,6 +1014,7 @@
      * @param mos A List of MO definitions to be updated
      * @return the number of nodes updated, or -1 for failure
      */
+    @Override
     public int modifyPasspointManagementObject(String fqdn, List<PasspointManagementObjectDefinition> mos) {
         return mWifiStateMachine.syncModifyPasspointManagementObject(mWifiStateMachineChannel, fqdn, mos);
     }
@@ -1017,6 +1024,7 @@
      * @param bssid The BSSID of the AP
      * @param fileName Icon file name
      */
+    @Override
     public void queryPasspointIcon(long bssid, String fileName) {
         mWifiStateMachine.syncQueryPasspointIcon(mWifiStateMachineChannel, bssid, fileName);
     }
@@ -1026,6 +1034,7 @@
      * @param fqdn FQDN of the SP
      * @return ordinal [HomeProvider, RoamingProvider, Incomplete, None, Declined]
      */
+    @Override
     public int matchProviderWithCurrentNetwork(String fqdn) {
         return mWifiStateMachine.matchProviderWithCurrentNetwork(mWifiStateMachineChannel, fqdn);
     }
@@ -1035,6 +1044,7 @@
      * @param holdoff hold off time in milliseconds
      * @param ess set if the hold off pertains to an ESS rather than a BSS
      */
+    @Override
     public void deauthenticateNetwork(long holdoff, boolean ess) {
         mWifiStateMachine.deauthenticateNetwork(mWifiStateMachineChannel, holdoff, ess);
     }
@@ -1088,8 +1098,8 @@
      *
      * TODO: deprecate this
      */
+    @Override
     public boolean saveConfiguration() {
-        boolean result = true;
         enforceChangePermission();
         if (mWifiStateMachineChannel != null) {
             return mWifiStateMachine.syncSaveConfig(mWifiStateMachineChannel);
@@ -1108,6 +1118,7 @@
      * persisted country code on a restart, when the locale information is
      * not available from telephony.
      */
+    @Override
     public void setCountryCode(String countryCode, boolean persist) {
         Slog.i(TAG, "WifiService trying to set country code to " + countryCode +
                 " with persist set to " + persist);
@@ -1129,6 +1140,7 @@
      * Get the country code
      * @return ISO 3166 country code.
      */
+    @Override
     public String getCountryCode() {
         enforceConnectivityInternalPermission();
         String country = mCountryCode.getCurrentCountryCode();
@@ -1143,6 +1155,7 @@
      * @param persist {@code true} if the setting should be remembered.
      *
      */
+    @Override
     public void setFrequencyBand(int band, boolean persist) {
         enforceChangePermission();
         if (!isDualBandSupported()) return;
@@ -1160,11 +1173,13 @@
     /**
      * Get the operational frequency band
      */
+    @Override
     public int getFrequencyBand() {
         enforceAccessPermission();
         return mWifiStateMachine.getFrequencyBand();
     }
 
+    @Override
     public boolean isDualBandSupported() {
         //TODO: Should move towards adding a driver API that checks at runtime
         return mContext.getResources().getBoolean(
@@ -1177,6 +1192,8 @@
      * @return the DHCP information
      * @deprecated
      */
+    @Override
+    @Deprecated
     public DhcpInfo getDhcpInfo() {
         enforceAccessPermission();
         DhcpResults dhcpResults = mWifiStateMachine.syncGetDhcpResults();
@@ -1216,6 +1233,7 @@
      * see {@link android.net.wifi.WifiManager#addToBlacklist}
      *
      */
+    @Override
     public void addToBlacklist(String bssid) {
         enforceChangePermission();
 
@@ -1226,6 +1244,7 @@
      * see {@link android.net.wifi.WifiManager#clearBlacklist}
      *
      */
+    @Override
     public void clearBlacklist() {
         enforceChangePermission();
 
@@ -1306,6 +1325,7 @@
         }
     }
 
+    @Override
     public void enableTdls(String remoteAddress, boolean enable) {
         if (remoteAddress == null) {
           throw new IllegalArgumentException("remoteAddress cannot be null");
@@ -1318,6 +1338,7 @@
     }
 
 
+    @Override
     public void enableTdlsWithMacAddress(String remoteMacAddress, boolean enable) {
         if (remoteMacAddress == null) {
           throw new IllegalArgumentException("remoteMacAddress cannot be null");
@@ -1330,6 +1351,7 @@
      * Get a reference to handler. This is used by a client to establish
      * an AsyncChannel communication with WifiService
      */
+    @Override
     public Messenger getWifiServiceMessenger() {
         enforceAccessPermission();
         enforceChangePermission();
@@ -1339,6 +1361,7 @@
     /**
      * Disable an ephemeral network, i.e. network that is created thru a WiFi Scorer
      */
+    @Override
     public void disableEphemeralNetwork(String SSID) {
         enforceAccessPermission();
         enforceChangePermission();
@@ -1348,6 +1371,7 @@
     /**
      * Get the IP and proxy configuration file
      */
+    @Override
     public String getConfigFile() {
         enforceAccessPermission();
         return mWifiStateMachine.getConfigFile();
@@ -1473,8 +1497,6 @@
             pw.println("Stay-awake conditions: " +
                     Settings.Global.getInt(mContext.getContentResolver(),
                                            Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0));
-            pw.println("mMulticastEnabled " + mMulticastEnabled);
-            pw.println("mMulticastDisabled " + mMulticastDisabled);
             pw.println("mInIdleMode " + mInIdleMode);
             pw.println("mScanPending " + mScanPending);
             mWifiController.dump(fd, pw, args);
@@ -1511,15 +1533,12 @@
             pw.println("Locks held:");
             mWifiLockManager.dump(pw);
             pw.println();
-            pw.println("Multicast Locks held:");
-            for (Multicaster l : mMulticasters) {
-                pw.print("    ");
-                pw.println(l);
-            }
-
+            mWifiMulticastLockManager.dump(pw);
             pw.println();
             mWifiStateMachine.dump(fd, pw, args);
             pw.println();
+            mWifiBackupRestore.dump(fd, pw, args);
+            pw.println();
         }
     }
 
@@ -1546,168 +1565,91 @@
         return false;
     }
 
-    private class Multicaster implements IBinder.DeathRecipient {
-        String mTag;
-        int mUid;
-        IBinder mBinder;
-
-        Multicaster(String tag, IBinder binder) {
-            mTag = tag;
-            mUid = Binder.getCallingUid();
-            mBinder = binder;
-            try {
-                mBinder.linkToDeath(this, 0);
-            } catch (RemoteException e) {
-                binderDied();
-            }
-        }
-
-        @Override
-        public void binderDied() {
-            Slog.e(TAG, "Multicaster binderDied");
-            synchronized (mMulticasters) {
-                int i = mMulticasters.indexOf(this);
-                if (i != -1) {
-                    removeMulticasterLocked(i, mUid);
-                }
-            }
-        }
-
-        void unlinkDeathRecipient() {
-            mBinder.unlinkToDeath(this, 0);
-        }
-
-        public int getUid() {
-            return mUid;
-        }
-
-        public String toString() {
-            return "Multicaster{" + mTag + " uid=" + mUid  + "}";
-        }
-    }
-
+    @Override
     public void initializeMulticastFiltering() {
         enforceMulticastChangePermission();
-
-        synchronized (mMulticasters) {
-            // if anybody had requested filters be off, leave off
-            if (mMulticasters.size() != 0) {
-                return;
-            } else {
-                mWifiStateMachine.startFilteringMulticastPackets();
-            }
-        }
+        mWifiMulticastLockManager.initializeFiltering();
     }
 
+    @Override
     public void acquireMulticastLock(IBinder binder, String tag) {
         enforceMulticastChangePermission();
-
-        synchronized (mMulticasters) {
-            mMulticastEnabled++;
-            mMulticasters.add(new Multicaster(tag, binder));
-            // Note that we could call stopFilteringMulticastPackets only when
-            // our new size == 1 (first call), but this function won't
-            // be called often and by making the stopPacket call each
-            // time we're less fragile and self-healing.
-            mWifiStateMachine.stopFilteringMulticastPackets();
-        }
-
-        int uid = Binder.getCallingUid();
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            mBatteryStats.noteWifiMulticastEnabled(uid);
-        } catch (RemoteException e) {
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
+        mWifiMulticastLockManager.acquireLock(binder, tag);
     }
 
+    @Override
     public void releaseMulticastLock() {
         enforceMulticastChangePermission();
-
-        int uid = Binder.getCallingUid();
-        synchronized (mMulticasters) {
-            mMulticastDisabled++;
-            int size = mMulticasters.size();
-            for (int i = size - 1; i >= 0; i--) {
-                Multicaster m = mMulticasters.get(i);
-                if ((m != null) && (m.getUid() == uid)) {
-                    removeMulticasterLocked(i, uid);
-                }
-            }
-        }
+        mWifiMulticastLockManager.releaseLock();
     }
 
-    private void removeMulticasterLocked(int i, int uid)
-    {
-        Multicaster removed = mMulticasters.remove(i);
-
-        if (removed != null) {
-            removed.unlinkDeathRecipient();
-        }
-        if (mMulticasters.size() == 0) {
-            mWifiStateMachine.startFilteringMulticastPackets();
-        }
-
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            mBatteryStats.noteWifiMulticastDisabled(uid);
-        } catch (RemoteException e) {
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
+    @Override
     public boolean isMulticastEnabled() {
         enforceAccessPermission();
-
-        synchronized (mMulticasters) {
-            return (mMulticasters.size() > 0);
-        }
+        return mWifiMulticastLockManager.isMulticastEnabled();
     }
 
+    @Override
     public void enableVerboseLogging(int verbose) {
         enforceAccessPermission();
+        mFacade.setIntegerSetting(
+                mContext, Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED, verbose);
+        enableVerboseLoggingInternal(verbose);
+    }
+
+    private void enableVerboseLoggingInternal(int verbose) {
         mWifiStateMachine.enableVerboseLogging(verbose);
         mWifiLockManager.enableVerboseLogging(verbose);
+        mWifiMulticastLockManager.enableVerboseLogging(verbose);
+        mWifiInjector.getWifiLastResortWatchdog().enableVerboseLogging(verbose);
+        mWifiInjector.getWifiBackupRestore().enableVerboseLogging(verbose);
     }
 
+    @Override
     public int getVerboseLoggingLevel() {
         enforceAccessPermission();
-        return mWifiStateMachine.getVerboseLoggingLevel();
+        return mFacade.getIntegerSetting(
+                mContext, Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED, 0);
     }
 
+    @Override
     public void enableAggressiveHandover(int enabled) {
         enforceAccessPermission();
         mWifiStateMachine.enableAggressiveHandover(enabled);
     }
 
+    @Override
     public int getAggressiveHandover() {
         enforceAccessPermission();
         return mWifiStateMachine.getAggressiveHandover();
     }
 
+    @Override
     public void setAllowScansWithTraffic(int enabled) {
         enforceAccessPermission();
         mWifiStateMachine.setAllowScansWithTraffic(enabled);
     }
 
+    @Override
     public int getAllowScansWithTraffic() {
         enforceAccessPermission();
         return mWifiStateMachine.getAllowScansWithTraffic();
     }
 
+    @Override
     public boolean setEnableAutoJoinWhenAssociated(boolean enabled) {
         enforceChangePermission();
         return mWifiStateMachine.setEnableAutoJoinWhenAssociated(enabled);
     }
 
+    @Override
     public boolean getEnableAutoJoinWhenAssociated() {
         enforceAccessPermission();
         return mWifiStateMachine.getEnableAutoJoinWhenAssociated();
     }
 
     /* Return the Wifi Connection statistics object */
+    @Override
     public WifiConnectionStatistics getConnectionStatistics() {
         enforceAccessPermission();
         enforceReadCredentialPermission();
@@ -1719,6 +1661,7 @@
         }
     }
 
+    @Override
     public void factoryReset() {
         enforceConnectivityInternalPermission();
 
@@ -1807,6 +1750,7 @@
         return null;
     }
 
+    @Override
     public Network getCurrentNetwork() {
         enforceAccessPermission();
         return mWifiStateMachine.getCurrentNetwork();
@@ -1888,8 +1832,96 @@
      *
      * @param enabled true-enable; false-disable
      */
+    @Override
     public void enableWifiConnectivityManager(boolean enabled) {
         enforceConnectivityInternalPermission();
         mWifiStateMachine.enableWifiConnectivityManager(enabled);
     }
+
+    /**
+     * Retrieve the data to be backed to save the current state.
+     *
+     * @return  Raw byte stream of the data to be backed up.
+     */
+    @Override
+    public byte[] retrieveBackupData() {
+        enforceReadCredentialPermission();
+        enforceAccessPermission();
+        if (mWifiStateMachineChannel == null) {
+            Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
+            return null;
+        }
+
+        Slog.d(TAG, "Retrieving backup data");
+        List<WifiConfiguration> wifiConfigurations =
+                mWifiStateMachine.syncGetPrivilegedConfiguredNetwork(mWifiStateMachineChannel);
+        byte[] backupData =
+                mWifiBackupRestore.retrieveBackupDataFromConfigurations(wifiConfigurations);
+        Slog.d(TAG, "Retrieved backup data");
+        return backupData;
+    }
+
+    /**
+     * Helper method to restore networks retrieved from backup data.
+     *
+     * @param configurations list of WifiConfiguration objects parsed from the backup data.
+     */
+    private void restoreNetworks(List<WifiConfiguration> configurations) {
+        if (configurations == null) {
+            Slog.e(TAG, "Backup data parse failed");
+            return;
+        }
+        for (WifiConfiguration configuration : configurations) {
+            int networkId = mWifiStateMachine.syncAddOrUpdateNetwork(
+                    mWifiStateMachineChannel, configuration);
+            if (networkId == WifiConfiguration.INVALID_NETWORK_ID) {
+                Slog.e(TAG, "Restore network failed: " + configuration.configKey());
+                continue;
+            }
+            // Enable all networks restored.
+            mWifiStateMachine.syncEnableNetwork(mWifiStateMachineChannel, networkId, false);
+        }
+    }
+
+    /**
+     * Restore state from the backed up data.
+     *
+     * @param data Raw byte stream of the backed up data.
+     */
+    @Override
+    public void restoreBackupData(byte[] data) {
+        enforceChangePermission();
+        if (mWifiStateMachineChannel == null) {
+            Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
+            return;
+        }
+
+        Slog.d(TAG, "Restoring backup data");
+        List<WifiConfiguration> wifiConfigurations =
+                mWifiBackupRestore.retrieveConfigurationsFromBackupData(data);
+        restoreNetworks(wifiConfigurations);
+        Slog.d(TAG, "Restored backup data");
+    }
+
+    /**
+     * Restore state from the older supplicant back up data.
+     * The old backup data was essentially a backup of wpa_supplicant.conf & ipconfig.txt file.
+     *
+     * @param supplicantData Raw byte stream of wpa_supplicant.conf
+     * @param ipConfigData Raw byte stream of ipconfig.txt
+     */
+    public void restoreSupplicantBackupData(byte[] supplicantData, byte[] ipConfigData) {
+        enforceChangePermission();
+        if (mWifiStateMachineChannel == null) {
+            Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
+            return;
+        }
+
+        Slog.d(TAG, "Restoring supplicant backup data");
+        List<WifiConfiguration> wifiConfigurations =
+                mWifiBackupRestore.retrieveConfigurationsFromSupplicantBackupData(
+                        supplicantData, ipConfigData);
+        restoreNetworks(wifiConfigurations);
+        Slog.d(TAG, "Restored supplicant backup data");
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiStateMachine.java b/service/java/com/android/server/wifi/WifiStateMachine.java
index e3ab3bc..c2e4b93 100644
--- a/service/java/com/android/server/wifi/WifiStateMachine.java
+++ b/service/java/com/android/server/wifi/WifiStateMachine.java
@@ -29,7 +29,6 @@
 
 import android.Manifest;
 import android.app.ActivityManager;
-import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.bluetooth.BluetoothAdapter;
 import android.content.BroadcastReceiver;
@@ -56,6 +55,8 @@
 import android.net.StaticIpConfiguration;
 import android.net.dhcp.DhcpClient;
 import android.net.ip.IpManager;
+import android.net.wifi.IApInterface;
+import android.net.wifi.IWificond;
 import android.net.wifi.PasspointManagementObjectDefinition;
 import android.net.wifi.RssiPacketCountInfo;
 import android.net.wifi.ScanResult;
@@ -73,6 +74,7 @@
 import android.net.wifi.WpsInfo;
 import android.net.wifi.WpsResult;
 import android.net.wifi.WpsResult.Status;
+import android.net.wifi.nan.WifiNanManager;
 import android.net.wifi.p2p.IWifiP2pManager;
 import android.os.BatteryStats;
 import android.os.Binder;
@@ -85,7 +87,6 @@
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.WorkSource;
@@ -120,12 +121,10 @@
 import java.net.InetAddress;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Calendar;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Queue;
-import java.util.Random;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -146,22 +145,22 @@
  *
  * @hide
  */
-public class WifiStateMachine extends StateMachine implements WifiNative.WifiRssiEventHandler {
+public class WifiStateMachine extends StateMachine implements WifiNative.WifiRssiEventHandler,
+        WifiMulticastLockManager.FilterController {
 
     private static final String NETWORKTYPE = "WIFI";
     private static final String NETWORKTYPE_UNTRUSTED = "WIFI_UT";
     @VisibleForTesting public static final short NUM_LOG_RECS_NORMAL = 100;
     @VisibleForTesting public static final short NUM_LOG_RECS_VERBOSE_LOW_MEMORY = 200;
     @VisibleForTesting public static final short NUM_LOG_RECS_VERBOSE = 3000;
-    private static boolean DBG = false;
-    private static boolean USE_PAUSE_SCANS = false;
     private static final String TAG = "WifiStateMachine";
 
     private static final int ONE_HOUR_MILLI = 1000 * 60 * 60;
 
     private static final String GOOGLE_OUI = "DA-A1-19";
 
-    private int mVerboseLoggingLevel = 0;
+    private boolean mVerboseLoggingEnabled = false;
+
     /* debug flag, indicating if handling of ASSOCIATION_REJECT ended up blacklisting
      * the corresponding BSSID.
      */
@@ -172,16 +171,18 @@
      *
      * @param s is string log
      */
+    @Override
     protected void loge(String s) {
         Log.e(getName(), s);
     }
+    @Override
     protected void logd(String s) {
         Log.d(getName(), s);
     }
-    protected void log(String s) {;
+    @Override
+    protected void log(String s) {
         Log.d(getName(), s);
     }
-    private WifiLastResortWatchdog mWifiLastResortWatchdog;
     private WifiMetrics mWifiMetrics;
     private WifiInjector mWifiInjector;
     private WifiMonitor mWifiMonitor;
@@ -190,11 +191,13 @@
     private WifiConnectivityManager mWifiConnectivityManager;
     private WifiQualifiedNetworkSelector mWifiQualifiedNetworkSelector;
     private INetworkManagementService mNwService;
+    private IWificond mWificond;
     private ConnectivityManager mCm;
     private BaseWifiLogger mWifiLogger;
     private WifiApConfigStore mWifiApConfigStore;
     private final boolean mP2pSupported;
     private final AtomicBoolean mP2pConnected = new AtomicBoolean(false);
+    private final boolean mNanSupported;
     private boolean mTemporarilyDisconnectWifi = false;
     private final String mPrimaryDeviceType;
     private final Clock mClock;
@@ -213,21 +216,16 @@
 
     private boolean mScreenOn = false;
 
-    /* Chipset supports background scan */
-    private final boolean mBackgroundScanSupported;
-
     private final String mInterfaceName;
-    /* Tethering interface could be separate from wlan interface */
-    private String mTetherInterfaceName;
 
     private int mLastSignalLevel = -1;
     private String mLastBssid;
     private int mLastNetworkId; // The network Id we successfully joined
-    private boolean linkDebouncing = false;
+    private boolean mIsLinkDebouncing = false;
 
     @Override
     public void onRssiThresholdBreached(byte curRssi) {
-        if (DBG) {
+        if (mVerboseLoggingEnabled) {
             Log.e(TAG, "onRssiThresholdBreach event. Cur Rssi = " + curRssi);
         }
         sendMessage(CMD_RSSI_THRESHOLD_BREACH, curRssi);
@@ -247,7 +245,7 @@
                 // This value of hw has to be believed as this value is averaged and has breached
                 // the rssi thresholds and raised event to host. This would be eggregious if this
                 // value is invalid
-                mWifiInfo.setRssi((int) curRssi);
+                mWifiInfo.setRssi(curRssi);
                 updateCapabilities(getCurrentWifiConfiguration());
                 int ret = startRssiMonitoringOffload(maxRssi, minRssi);
                 Log.d(TAG, "Re-program RSSI thresholds for " + smToString(reason) +
@@ -274,12 +272,8 @@
     private boolean mSendScanResultsBroadcast = false;
 
     private final Queue<Message> mBufferedScanMsg = new LinkedList<Message>();
-    private WorkSource mScanWorkSource = null;
     private static final int UNKNOWN_SCAN_SOURCE = -1;
     private static final int ADD_OR_UPDATE_SOURCE = -3;
-    private static final int SET_ALLOW_UNTRUSTED_SOURCE = -4;
-    private static final int ENABLE_WIFI = -5;
-    public static final int DFS_RESTRICTED_SCAN_REQUEST = -6;
 
     private static final int SCAN_REQUEST_BUFFER_MAX_SIZE = 10;
     private static final String CUSTOMIZED_SCAN_SETTING = "customized_scan_settings";
@@ -322,14 +316,6 @@
     private int mSupplicantStopFailureToken = 0;
 
     /**
-     * Tether state change notification time out
-     */
-    private static final int TETHER_NOTIFICATION_TIME_OUT_MSECS = 5000;
-
-    /* Tracks sequence number on a tether notification time out */
-    private int mTetherToken = 0;
-
-    /**
      * Driver start time out.
      */
     private static final int DRIVER_START_TIME_OUT_MSECS = 10000;
@@ -338,12 +324,6 @@
     private int mDriverStartToken = 0;
 
     /**
-     * Don't select new network when previous network selection is
-     * pending connection for this much time
-     */
-    private static final int CONNECT_TIMEOUT_MSEC = 3000;
-
-    /**
      * The link properties of the wifi interface.
      * Do not modify this directly; use updateLinkProperties instead.
      */
@@ -394,9 +374,6 @@
     // Used as debug to indicate which configuration last was removed
     private WifiConfiguration lastForgetConfigurationAttempt = null;
 
-    //Random used by softAP channel Selection
-    private static Random mRandom = new Random(Calendar.getInstance().getTimeInMillis());
-
     boolean isRoaming() {
         return mAutoRoaming;
     }
@@ -417,18 +394,18 @@
         }
         if (!mTargetRoamBSSID.equals("any") && bssid.equals("any")) {
             // Changing to ANY
-            if (!mWifiConfigManager.ROAM_ON_ANY) {
+            if (!WifiConfigManager.ROAM_ON_ANY) {
                 ret = false; // Nothing to do
             }
         }
         if (config.BSSID != null) {
             bssid = config.BSSID;
-            if (DBG) {
+            if (mVerboseLoggingEnabled) {
                 Log.d(TAG, "force BSSID to " + bssid + "due to config");
             }
         }
 
-        if (DBG) {
+        if (mVerboseLoggingEnabled) {
             logd("autoRoamSetBSSID " + bssid + " key=" + config.configKey());
         }
         mTargetRoamBSSID = bssid;
@@ -450,7 +427,7 @@
 
         if (config.BSSID != null) {
             bssid = config.BSSID;
-            if (DBG) {
+            if (mVerboseLoggingEnabled) {
                 Log.d(TAG, "force BSSID to " + bssid + "due to config");
             }
         }
@@ -462,13 +439,13 @@
         String networkSelectionBSSID = config.getNetworkSelectionStatus()
                 .getNetworkSelectionBSSID();
         if (networkSelectionBSSID != null && networkSelectionBSSID.equals(bssid)) {
-            if (DBG) {
+            if (mVerboseLoggingEnabled) {
                 Log.d(TAG, "Current preferred BSSID is the same as the target one");
             }
             return false;
         }
 
-        if (DBG) {
+        if (mVerboseLoggingEnabled) {
             Log.d(TAG, "target set to " + config.SSID + ":" + bssid);
         }
         mTargetRoamBSSID = bssid;
@@ -528,9 +505,6 @@
 
     private final IpManager mIpManager;
 
-    private AlarmManager mAlarmManager;
-    private PendingIntent mScanIntent;
-
     /* Tracks current frequency mode */
     private AtomicInteger mFrequencyBand = new AtomicInteger(WifiManager.WIFI_FREQUENCY_BAND_AUTO);
 
@@ -538,6 +512,7 @@
     private AsyncChannel mReplyChannel = new AsyncChannel();
 
     private WifiP2pServiceImpl mWifiP2pServiceImpl;
+    private WifiNanManager mWifiNanManager;
 
     // Used to initiate a connection with WifiP2pService
     private AsyncChannel mWifiP2pChannel;
@@ -549,8 +524,6 @@
     private UntrustedWifiNetworkFactory mUntrustedNetworkFactory;
     private WifiNetworkAgent mNetworkAgent;
 
-    private String[] mWhiteListedSsids = null;
-
     private byte[] mRssiRanges;
 
     // Keep track of various statistics, for retrieval by System Apps, i.e. under @SystemApi
@@ -667,8 +640,6 @@
     static final int CMD_SET_FREQUENCY_BAND                             = BASE + 90;
     /* Enable TDLS on a specific MAC address */
     static final int CMD_ENABLE_TDLS                                    = BASE + 92;
-    /* DHCP/IP configuration watchdog */
-    static final int CMD_OBTAINING_IP_ADDRESS_WATCHDOG_TIMER            = BASE + 93;
 
     /**
      * Watchdog for protecting against b/16823537
@@ -708,15 +679,6 @@
     /* try to match a provider with current network */
     static final int CMD_MATCH_PROVIDER_NETWORK                         = BASE + 105;
 
-    /**
-     * Make this timer 40 seconds, which is about the normal DHCP timeout.
-     * In no valid case, the WiFiStateMachine should remain stuck in ObtainingIpAddress
-     * for more than 30 seconds.
-     */
-    static final int OBTAINING_IP_ADDRESS_GUARD_TIMER_MSEC = 40000;
-
-    int obtainingIpWatchdogCount = 0;
-
     /* Commands from/to the SupplicantStateTracker */
     /* Reset the supplicant state tracker */
     static final int CMD_RESET_SUPPLICANT_STATE                         = BASE + 111;
@@ -771,9 +733,6 @@
 
     static final int CMD_ACCEPT_UNVALIDATED                             = BASE + 153;
 
-    /* used to log if PNO was started */
-    static final int CMD_UPDATE_ASSOCIATED_SCAN_PERMISSION              = BASE + 158;
-
     /* used to offload sending IP packet */
     static final int CMD_START_IP_PACKET_OFFLOAD                        = BASE + 160;
 
@@ -846,16 +805,6 @@
     private AtomicBoolean mUserWantsSuspendOpt = new AtomicBoolean(true);
 
     /**
-     * Default framework scan interval in milliseconds. This is used in the scenario in which
-     * wifi chipset does not support background scanning to set up a
-     * periodic wake up scan so that the device can connect to a new access
-     * point on the move. {@link Settings.Global#WIFI_FRAMEWORK_SCAN_INTERVAL_MS} can
-     * override this.
-     */
-    private final int mDefaultFrameworkScanIntervalMs;
-
-
-    /**
      * Scan period for the NO_NETWORKS_PERIIDOC_SCAN_FEATURE
      */
     private final int mNoNetworksPeriodicScan;
@@ -948,8 +897,6 @@
      */
     private final AtomicInteger mWifiApState = new AtomicInteger(WIFI_AP_STATE_DISABLED);
 
-    private static final int SCAN_REQUEST = 0;
-
     /**
      * Work source to use to blame usage on the WiFi service
      */
@@ -982,11 +929,6 @@
     // Used for debug and stats gathering
     private static int sScanAlarmIntentCount = 0;
 
-    private static final int sFrameworkMinScanIntervalSaneValue = 10000;
-
-    private long mGScanStartTimeMilli;
-    private long mGScanPeriodMilli;
-
     private FrameworkFacade mFacade;
 
     private final BackupManagerProxy mBackupManagerProxy;
@@ -1000,7 +942,6 @@
         super("WifiStateMachine", looper);
         mWifiInjector = wifiInjector;
         mWifiMetrics = mWifiInjector.getWifiMetrics();
-        mWifiLastResortWatchdog = wifiInjector.getWifiLastResortWatchdog();
         mClock = wifiInjector.getClock();
         mPropertyService = wifiInjector.getPropertyService();
         mBuildProperties = wifiInjector.getBuildProperties();
@@ -1021,6 +962,8 @@
 
         mP2pSupported = mContext.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_WIFI_DIRECT);
+        mNanSupported = mContext.getPackageManager()
+                .hasSystemFeature(PackageManager.FEATURE_WIFI_NAN);
 
         mWifiConfigManager = mFacade.makeWifiConfigManager(context, mWifiNative, facade,
                 mWifiInjector.getClock(), userManager, mWifiInjector.getKeyStore());
@@ -1047,6 +990,8 @@
         IBinder s1 = mFacade.getService(Context.WIFI_P2P_SERVICE);
         mWifiP2pServiceImpl = (WifiP2pServiceImpl) IWifiP2pManager.Stub.asInterface(s1);
 
+        mWifiNanManager = (WifiNanManager) mContext.getSystemService(Context.WIFI_NAN_SERVICE);
+
         mNetworkInfo.setIsAvailable(false);
         mLastBssid = null;
         mLastNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
@@ -1055,21 +1000,12 @@
         mIpManager = mFacade.makeIpManager(mContext, mInterfaceName, new IpManagerCallback());
         mIpManager.setMulticastFilter(true);
 
-        mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
-
-        // Make sure the interval is not configured less than 10 seconds
-        int period = mContext.getResources().getInteger(
-                R.integer.config_wifi_framework_scan_interval);
-        if (period < sFrameworkMinScanIntervalSaneValue) {
-            period = sFrameworkMinScanIntervalSaneValue;
-        }
-        mDefaultFrameworkScanIntervalMs = period;
-
         mNoNetworksPeriodicScan = mContext.getResources().getInteger(
                 R.integer.config_wifi_no_network_periodic_scan_interval);
 
-        mBackgroundScanSupported = mContext.getResources().getBoolean(
-                R.bool.config_wifi_background_scan_support);
+        // TODO: remove these settings from the config file since we no longer obey them
+        // mContext.getResources().getInteger(R.integer.config_wifi_framework_scan_interval);
+        // mContext.getResources().getBoolean(R.bool.config_wifi_background_scan_support);
 
         mPrimaryDeviceType = mContext.getResources().getString(
                 R.string.config_wifi_p2p_device_type);
@@ -1210,10 +1146,6 @@
         } catch (PackageManager.NameNotFoundException e) {
             loge("Unable to resolve SystemUI's UID.");
         }
-
-        mVerboseLoggingLevel = mFacade.getIntegerSetting(
-                mContext, Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED, 0);
-        updateLoggingLevel();
     }
 
     class IpManagerCallback extends IpManager.Callback {
@@ -1233,8 +1165,8 @@
                 sendMessage(CMD_IPV4_PROVISIONING_SUCCESS, dhcpResults);
             } else {
                 sendMessage(CMD_IPV4_PROVISIONING_FAILURE);
-                mWifiLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(getTargetSsid(),
-                        mTargetRoamBSSID,
+                mWifiInjector.getWifiLastResortWatchdog().noteConnectionFailureAndTriggerIfNeeded(
+                        getTargetSsid(), mTargetRoamBSSID,
                         WifiLastResortWatchdog.FAILURE_CODE_DHCP);
             }
         }
@@ -1289,48 +1221,37 @@
         return mFacade.getBroadcast(mContext, requestCode, intent, 0);
     }
 
-    int getVerboseLoggingLevel() {
-        return mVerboseLoggingLevel;
-    }
-
-    void enableVerboseLogging(int verbose) {
-        mVerboseLoggingLevel = verbose;
-        mFacade.setIntegerSetting(
-                mContext, Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED, verbose);
-        updateLoggingLevel();
-    }
-
     /**
      * Set wpa_supplicant log level using |mVerboseLoggingLevel| flag.
      */
     void setSupplicantLogLevel() {
-        if (mVerboseLoggingLevel > 0) {
+        if (mVerboseLoggingEnabled) {
             mWifiNative.setSupplicantLogLevel("DEBUG");
         } else {
             mWifiNative.setSupplicantLogLevel("INFO");
         }
     }
 
-    void updateLoggingLevel() {
-        if (mVerboseLoggingLevel > 0) {
-            DBG = true;
+    void enableVerboseLogging(int verbose) {
+        if (verbose > 0) {
+            mVerboseLoggingEnabled = true;
             setLogRecSize(ActivityManager.isLowRamDeviceStatic()
                     ? NUM_LOG_RECS_VERBOSE_LOW_MEMORY : NUM_LOG_RECS_VERBOSE);
         } else {
-            DBG = false;
+            mVerboseLoggingEnabled = false;
             setLogRecSize(NUM_LOG_RECS_NORMAL);
         }
-        configureVerboseHalLogging(mVerboseLoggingLevel > 0);
+        configureVerboseHalLogging(mVerboseLoggingEnabled);
         setSupplicantLogLevel();
-        mCountryCode.enableVerboseLogging(mVerboseLoggingLevel);
-        mWifiLogger.startLogging(DBG);
-        mWifiMonitor.enableVerboseLogging(mVerboseLoggingLevel);
-        mWifiNative.enableVerboseLogging(mVerboseLoggingLevel);
-        mWifiConfigManager.enableVerboseLogging(mVerboseLoggingLevel);
-        mSupplicantStateTracker.enableVerboseLogging(mVerboseLoggingLevel);
-        mWifiQualifiedNetworkSelector.enableVerboseLogging(mVerboseLoggingLevel);
+        mCountryCode.enableVerboseLogging(verbose);
+        mWifiLogger.startLogging(mVerboseLoggingEnabled);
+        mWifiMonitor.enableVerboseLogging(verbose);
+        mWifiNative.enableVerboseLogging(verbose);
+        mWifiConfigManager.enableVerboseLogging(verbose);
+        mSupplicantStateTracker.enableVerboseLogging(verbose);
+        mWifiQualifiedNetworkSelector.enableVerboseLogging(verbose);
         if (mWifiConnectivityManager != null) {
-            mWifiConnectivityManager.enableVerboseLogging(mVerboseLoggingLevel);
+            mWifiConnectivityManager.enableVerboseLogging(verbose);
         }
     }
 
@@ -1345,15 +1266,6 @@
                 enableVerbose ? LOGD_LEVEL_VERBOSE : LOGD_LEVEL_DEBUG);
     }
 
-    long mLastScanPermissionUpdate = 0;
-    boolean mConnectedModeGScanOffloadStarted = false;
-    // Don't do a G-scan enable/re-enable cycle more than once within 20seconds
-    // The function updateAssociatedScanPermission() can be called quite frequently, hence
-    // we want to throttle the GScan Stop->Start transition
-    static final long SCAN_PERMISSION_UPDATE_THROTTLE_MILLI = 20000;
-    void updateAssociatedScanPermission() {
-    }
-
     private int mAggressiveHandover = 0;
 
     int getAggressiveHandover() {
@@ -1437,7 +1349,7 @@
         Bundle bundle = new Bundle();
         bundle.putParcelable(CUSTOMIZED_SCAN_SETTING, settings);
         bundle.putParcelable(CUSTOMIZED_SCAN_WORKSOURCE, workSource);
-        bundle.putLong(SCAN_REQUEST_TIME, System.currentTimeMillis());
+        bundle.putLong(SCAN_REQUEST_TIME, mClock.getWallClockMillis());
         sendMessage(CMD_START_SCAN, callingUid, scanCounter, bundle);
     }
 
@@ -1477,7 +1389,7 @@
     public long getDisconnectedTimeMilli() {
         if (getCurrentState() == mDisconnectedState
                 && mDisconnectedTimeStamp != 0) {
-            long now_ms = System.currentTimeMillis();
+            long now_ms = mClock.getWallClockMillis();
             return now_ms - mDisconnectedTimeStamp;
         }
         return 0;
@@ -1506,7 +1418,7 @@
     //TODO: this is used only to track connection attempts, however the link state and packet per
     //TODO: second logic should be folded into that
     private boolean checkOrDeferScanAllowed(Message msg) {
-        long now = System.currentTimeMillis();
+        long now = mClock.getWallClockMillis();
         if (lastConnectAttemptTimestamp != 0 && (now - lastConnectAttemptTimestamp) < 10000) {
             Message dmsg = Message.obtain(msg);
             sendMessageDelayed(dmsg, 11000 - (now - lastConnectAttemptTimestamp));
@@ -1529,7 +1441,7 @@
     private long lastLinkLayerStatsUpdate = 0;
 
     String reportOnTime() {
-        long now = System.currentTimeMillis();
+        long now = mClock.getWallClockMillis();
         StringBuilder sb = new StringBuilder();
         // Report stats since last report
         int on = mOnTime - mOnTimeLastReport;
@@ -1548,7 +1460,7 @@
         return sb.toString();
     }
 
-    WifiLinkLayerStats getWifiLinkLayerStats(boolean dbg) {
+    WifiLinkLayerStats getWifiLinkLayerStats() {
         WifiLinkLayerStats stats = null;
         if (mWifiLinkLayerStatsSupported > 0) {
             String name = "wlan0";
@@ -1556,7 +1468,7 @@
             if (name != null && stats == null && mWifiLinkLayerStatsSupported > 0) {
                 mWifiLinkLayerStatsSupported -= 1;
             } else if (stats != null) {
-                lastLinkLayerStatsUpdate = System.currentTimeMillis();
+                lastLinkLayerStatsUpdate = mClock.getWallClockMillis();
                 mOnTime = stats.on_time;
                 mTxTime = stats.tx_time;
                 mRxTime = stats.rx_time;
@@ -1704,16 +1616,21 @@
         }
         WifiScanner.ScanListener nativeScanListener = new WifiScanner.ScanListener() {
                 // ignore all events since WifiStateMachine is registered for the supplicant events
+                @Override
                 public void onSuccess() {
                 }
+                @Override
                 public void onFailure(int reason, String description) {
                     mIsScanOngoing = false;
                     mIsFullScanOngoing = false;
                 }
+                @Override
                 public void onResults(WifiScanner.ScanData[] results) {
                 }
+                @Override
                 public void onFullResult(ScanResult fullScanResult) {
                 }
+                @Override
                 public void onPeriodChanged(int periodInMs) {
                 }
             };
@@ -1817,19 +1734,19 @@
     }
 
     public boolean isSupplicantTransientState() {
-        SupplicantState SupplicantState = mWifiInfo.getSupplicantState();
-        if (SupplicantState == SupplicantState.ASSOCIATING
-                || SupplicantState == SupplicantState.AUTHENTICATING
-                || SupplicantState == SupplicantState.FOUR_WAY_HANDSHAKE
-                || SupplicantState == SupplicantState.GROUP_HANDSHAKE) {
+        SupplicantState supplicantState = mWifiInfo.getSupplicantState();
+        if (supplicantState == SupplicantState.ASSOCIATING
+                || supplicantState == SupplicantState.AUTHENTICATING
+                || supplicantState == SupplicantState.FOUR_WAY_HANDSHAKE
+                || supplicantState == SupplicantState.GROUP_HANDSHAKE) {
 
-            if (DBG) {
-                Log.d(TAG, "Supplicant is under transient state: " + SupplicantState);
+            if (mVerboseLoggingEnabled) {
+                Log.d(TAG, "Supplicant is under transient state: " + supplicantState);
             }
             return true;
         } else {
-            if (DBG) {
-                Log.d(TAG, "Supplicant is under steady state: " + SupplicantState);
+            if (mVerboseLoggingEnabled) {
+                Log.d(TAG, "Supplicant is under steady state: " + supplicantState);
             }
         }
 
@@ -1837,7 +1754,7 @@
     }
 
     public boolean isLinkDebouncing() {
-        return linkDebouncing;
+        return mIsLinkDebouncing;
     }
 
     /**
@@ -1874,7 +1791,7 @@
      * TODO: doc
      */
     public void setOperationalMode(int mode) {
-        if (DBG) log("setting operational mode to " + String.valueOf(mode));
+        if (mVerboseLoggingEnabled) log("setting operational mode to " + String.valueOf(mode));
         sendMessage(CMD_SET_OPERATIONAL_MODE, mode, 0);
     }
 
@@ -2305,14 +2222,6 @@
         } else {
             pw.println("CurrentCountryCode is not initialized");
         }
-        pw.println("mConnectedModeGScanOffloadStarted " + mConnectedModeGScanOffloadStarted);
-        pw.println("mGScanPeriodMilli " + mGScanPeriodMilli);
-        if (mWhiteListedSsids != null && mWhiteListedSsids.length > 0) {
-            pw.println("SSID whitelist :" );
-            for (int i=0; i < mWhiteListedSsids.length; i++) {
-                pw.println("       " + mWhiteListedSsids[i]);
-            }
-        }
         if (mNetworkFactory != null) {
             mNetworkFactory.dump(fd, pw, args);
         } else {
@@ -2353,7 +2262,7 @@
 
     private void logStateAndMessage(Message message, State state) {
         messageHandlingStatus = 0;
-        if (DBG) {
+        if (mVerboseLoggingEnabled) {
             logd(" " + state.getClass().getSimpleName() + " " + getLogRecString(message));
         }
     }
@@ -2363,8 +2272,8 @@
      */
     String printTime() {
         StringBuilder sb = new StringBuilder();
-        sb.append(" rt=").append(SystemClock.uptimeMillis());
-        sb.append("/").append(SystemClock.elapsedRealtime());
+        sb.append(" rt=").append(mClock.getUptimeSinceBootMillis());
+        sb.append("/").append(mClock.getElapsedSinceBootMillis());
         return sb.toString();
     }
 
@@ -2374,6 +2283,7 @@
      * @param msg that was processed
      * @return information to be logged as a String
      */
+    @Override
     protected String getLogRecString(Message msg) {
         WifiConfiguration config;
         Long now;
@@ -2392,22 +2302,8 @@
         }
         sb.append(" ").append(printTime());
         switch (msg.what) {
-            case CMD_UPDATE_ASSOCIATED_SCAN_PERMISSION:
-                sb.append(" ");
-                sb.append(Integer.toString(msg.arg1));
-                sb.append(" ");
-                sb.append(Integer.toString(msg.arg2));
-                sb.append(" autojoinAllowed=");
-                sb.append(mWifiConfigManager.getEnableAutoJoinWhenAssociated());
-                sb.append(" withTraffic=").append(getAllowScansWithTraffic());
-                sb.append(" tx=").append(mWifiInfo.txSuccessRate);
-                sb.append("/").append(mWifiConfigManager.MAX_TX_PACKET_FOR_FULL_SCANS);
-                sb.append(" rx=").append(mWifiInfo.rxSuccessRate);
-                sb.append("/").append(mWifiConfigManager.MAX_RX_PACKET_FOR_FULL_SCANS);
-                sb.append(" -> ").append(mConnectedModeGScanOffloadStarted);
-                break;
             case CMD_START_SCAN:
-                now = System.currentTimeMillis();
+                now = mClock.getWallClockMillis();
                 sb.append(" ");
                 sb.append(Integer.toString(msg.arg1));
                 sb.append(" ");
@@ -2581,7 +2477,7 @@
                     sb.append(" freq=").append(mWifiInfo.getFrequency());
                     sb.append(" rssi=").append(mWifiInfo.getRssi());
                 }
-                if (linkDebouncing) {
+                if (isLinkDebouncing()) {
                     sb.append(" debounce");
                 }
                 break;
@@ -2606,7 +2502,7 @@
                                 netWorkSelectionStatus.getNetworkDisableReasonString());
                     }
                     if (config.lastConnected != 0) {
-                        now = System.currentTimeMillis();
+                        now = mClock.getWallClockMillis();
                         sb.append(" lastconn=").append(now - config.lastConnected).append("(ms)");
                     }
                     if (mLastBssid != null) {
@@ -2647,11 +2543,6 @@
                 if (mWifiScoreReport != null) {
                     sb.append(mWifiScoreReport.getReport());
                 }
-                if (mConnectedModeGScanOffloadStarted) {
-                    sb.append(" offload-started periodMilli " + mGScanPeriodMilli);
-                } else {
-                    sb.append(" offload-stopped");
-                }
                 break;
             case CMD_AUTO_CONNECT:
             case WifiManager.CONNECT_NETWORK:
@@ -2685,7 +2576,7 @@
                 sb.append(Integer.toString(msg.arg2));
                 ScanResult result = (ScanResult) msg.obj;
                 if (result != null) {
-                    now = System.currentTimeMillis();
+                    now = mClock.getWallClockMillis();
                     sb.append(" bssid=").append(result.BSSID);
                     sb.append(" rssi=").append(result.level);
                     sb.append(" freq=").append(result.frequency);
@@ -2890,7 +2781,7 @@
 
     private void handleScreenStateChanged(boolean screenOn) {
         mScreenOn = screenOn;
-        if (DBG) {
+        if (mVerboseLoggingEnabled) {
             logd(" handleScreenStateChanged Enter: screenOn=" + screenOn
                     + " mUserWantsSuspendOpt=" + mUserWantsSuspendOpt
                     + " state " + getCurrentState().getName()
@@ -2912,7 +2803,7 @@
         }
         mScreenBroadcastReceived.set(true);
 
-        getWifiLinkLayerStats(false);
+        getWifiLinkLayerStats();
         mOnTimeScreenStateChange = mOnTime;
         lastScreenStateChangeTimeStamp = lastLinkLayerStatsUpdate;
 
@@ -2922,7 +2813,7 @@
             mWifiConnectivityManager.handleScreenStateChanged(screenOn);
         }
 
-        if (DBG) log("handleScreenStateChanged Exit: " + screenOn);
+        if (mVerboseLoggingEnabled) log("handleScreenStateChanged Exit: " + screenOn);
     }
 
     private void checkAndSetConnectivityInstance() {
@@ -2943,7 +2834,7 @@
             if (mWifiConnectivityManager != null) {
                 mWifiConnectivityManager.setUserPreferredBand(band);
             }
-            if (DBG) {
+            if (mVerboseLoggingEnabled) {
                 logd("done set frequency band " + band);
             }
         } else {
@@ -2952,7 +2843,7 @@
     }
 
     private void setSuspendOptimizationsNative(int reason, boolean enabled) {
-        if (DBG) {
+        if (mVerboseLoggingEnabled) {
             log("setSuspendOptimizationsNative: " + reason + " " + enabled
                     + " -want " + mUserWantsSuspendOpt.get()
                     + " stack:" + Thread.currentThread().getStackTrace()[2].getMethodName()
@@ -2966,7 +2857,7 @@
             mSuspendOptNeedsDisabled &= ~reason;
             /* None of dhcp, screen or highperf need it disabled and user wants it enabled */
             if (mSuspendOptNeedsDisabled == 0 && mUserWantsSuspendOpt.get()) {
-                if (DBG) {
+                if (mVerboseLoggingEnabled) {
                     log("setSuspendOptimizationsNative do it " + reason + " " + enabled
                             + " stack:" + Thread.currentThread().getStackTrace()[2].getMethodName()
                             + " - " + Thread.currentThread().getStackTrace()[3].getMethodName()
@@ -2982,13 +2873,13 @@
     }
 
     private void setSuspendOptimizations(int reason, boolean enabled) {
-        if (DBG) log("setSuspendOptimizations: " + reason + " " + enabled);
+        if (mVerboseLoggingEnabled) log("setSuspendOptimizations: " + reason + " " + enabled);
         if (enabled) {
             mSuspendOptNeedsDisabled &= ~reason;
         } else {
             mSuspendOptNeedsDisabled |= reason;
         }
-        if (DBG) log("mSuspendOptNeedsDisabled " + mSuspendOptNeedsDisabled);
+        if (mVerboseLoggingEnabled) log("mSuspendOptNeedsDisabled " + mSuspendOptNeedsDisabled);
     }
 
     private void setWifiState(int wifiState) {
@@ -3006,7 +2897,7 @@
 
         mWifiState.set(wifiState);
 
-        if (DBG) log("setWifiState: " + syncGetWifiStateByName());
+        if (mVerboseLoggingEnabled) log("setWifiState: " + syncGetWifiStateByName());
 
         final Intent intent = new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
@@ -3031,7 +2922,7 @@
         // Update state
         mWifiApState.set(wifiApState);
 
-        if (DBG) log("setWifiApState: " + syncGetWifiApStateByName());
+        if (mVerboseLoggingEnabled) log("setWifiApState: " + syncGetWifiApStateByName());
 
         final Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
@@ -3069,17 +2960,9 @@
         }
 
         synchronized (mScanResultsLock) {
-            ScanDetail activeScanDetail = null;
             mScanResults = scanResults;
             mNumScanResultsReturned = mScanResults.size();
             for (ScanDetail resultDetail : mScanResults) {
-                if (connected && resultDetail.getNetworkDetail().getBSSID() == activeBssid) {
-                    if (activeScanDetail == null
-                            || activeScanDetail.getNetworkDetail().getBSSID() != activeBssid
-                            || activeScanDetail.getNetworkDetail().getANQPElements() == null) {
-                        activeScanDetail = resultDetail;
-                    }
-                }
                 // Cache DTIM values parsed from the beacon frame Traffic Indication Map (TIM)
                 // Information Element (IE), into the associated WifiConfigurations. Most of the
                 // time there is no TIM IE in the scan result (Probe Response instead of Beacon
@@ -3098,10 +2981,9 @@
                     }
                 }
             }
-            mWifiConfigManager.setActiveScanDetail(activeScanDetail);
         }
 
-        if (linkDebouncing) {
+        if (isLinkDebouncing()) {
             // If debouncing, we dont re-select a SSID or BSSID hence
             // there is no need to call the network selection code
             // in WifiAutoJoinController, instead,
@@ -3140,7 +3022,7 @@
             }
         }
 
-        if (DBG) {
+        if (mVerboseLoggingEnabled) {
             logd("fetchRssiLinkSpeedAndFrequencyNative rssi=" + newRssi +
                  " linkspeed=" + newLinkSpeed + " freq=" + newFrequency);
         }
@@ -3237,7 +3119,7 @@
     }
 
     private void updateLinkProperties(LinkProperties newLp) {
-        if (DBG) {
+        if (mVerboseLoggingEnabled) {
             log("Link configuration changed for netId: " + mLastNetworkId
                     + " old: " + mLinkProperties + " new: " + newLp);
         }
@@ -3253,7 +3135,7 @@
             sendLinkConfigurationChangedBroadcast();
         }
 
-        if (DBG) {
+        if (mVerboseLoggingEnabled) {
             StringBuilder sb = new StringBuilder();
             sb.append("updateLinkProperties nid: " + mLastNetworkId);
             sb.append(" state: " + getNetworkDetailedState());
@@ -3292,7 +3174,7 @@
             if (route.isDefaultRoute() && route.hasGateway()) {
                 InetAddress gateway = route.getGateway();
                 if (gateway instanceof Inet4Address) {
-                    if (DBG) {
+                    if (mVerboseLoggingEnabled) {
                         logd("updateDefaultRouteMacAddress found Ipv4 default :"
                                 + gateway.getHostAddress());
                     }
@@ -3310,7 +3192,7 @@
                             if (reachable == true) {
 
                                 address = macAddressFromRoute(gateway.getHostAddress());
-                                if (DBG) {
+                                if (mVerboseLoggingEnabled) {
                                     logd("updateDefaultRouteMacAddress reachable (tried again) :"
                                             + gateway.getHostAddress() + " found " + address);
                                 }
@@ -3409,7 +3291,7 @@
     private boolean setNetworkDetailedState(NetworkInfo.DetailedState state) {
         boolean hidden = false;
 
-        if (linkDebouncing || isRoaming()) {
+        if (isLinkDebouncing() || isRoaming()) {
             // There is generally a confusion in the system about colluding
             // WiFi Layer 2 state (as reported by supplicant) and the Network state
             // which leads to multiple confusion.
@@ -3424,7 +3306,7 @@
             //
             hidden = true;
         }
-        if (DBG) {
+        if (mVerboseLoggingEnabled) {
             log("setDetailed state, old ="
                     + mNetworkInfo.getDetailedState() + " and new state=" + state
                     + " hidden=" + hidden);
@@ -3433,7 +3315,7 @@
                 && !mWifiInfo.getSSID().equals(WifiSsid.NONE)) {
             // Always indicate that SSID has changed
             if (!mNetworkInfo.getExtraInfo().equals(mWifiInfo.getSSID())) {
-                if (DBG) {
+                if (mVerboseLoggingEnabled) {
                     log("setDetailed state send new extra info" + mWifiInfo.getSSID());
                 }
                 mNetworkInfo.setExtraInfo(mWifiInfo.getSSID());
@@ -3471,7 +3353,7 @@
         // this implies that wpa_supplicant is already disconnected.
         // We should pretend we are still connected when linkDebouncing is on.
         if ((stateChangeResult.wifiSsid == null
-                || stateChangeResult.wifiSsid.toString().isEmpty()) && linkDebouncing) {
+                || stateChangeResult.wifiSsid.toString().isEmpty()) && isLinkDebouncing()) {
             return state;
         }
         // Network id is only valid when we start connecting
@@ -3483,30 +3365,6 @@
 
         mWifiInfo.setBSSID(stateChangeResult.BSSID);
 
-        if (mWhiteListedSsids != null
-                && mWhiteListedSsids.length > 0
-                && stateChangeResult.wifiSsid != null) {
-            String SSID = stateChangeResult.wifiSsid.toString();
-            String currentSSID = mWifiInfo.getSSID();
-            if (SSID != null && currentSSID != null && !SSID.equals(WifiSsid.NONE)) {
-                // Remove quote before comparing
-                if (SSID.length() >= 2 && SSID.charAt(0) == '"'
-                        && SSID.charAt(SSID.length() - 1) == '"') {
-                    SSID = SSID.substring(1, SSID.length() - 1);
-                }
-                if (currentSSID.length() >= 2 && currentSSID.charAt(0) == '"'
-                        && currentSSID.charAt(currentSSID.length() - 1) == '"') {
-                    currentSSID = currentSSID.substring(1, currentSSID.length() - 1);
-                }
-                if ((!SSID.equals(currentSSID)) && (getCurrentState() == mConnectedState)) {
-                    lastConnectAttemptTimestamp = System.currentTimeMillis();
-                    targetWificonfiguration =
-                            mWifiConfigManager.getWifiConfiguration(mWifiInfo.getNetworkId());
-                    transitionTo(mRoamingState);
-                }
-            }
-        }
-
         mWifiInfo.setSSID(stateChangeResult.wifiSsid);
         mWifiInfo.setEphemeral(mWifiConfigManager.isEphemeral(mWifiInfo.getNetworkId()));
         if (!mWifiInfo.getMeteredHint()) { // don't override the value if already set.
@@ -3523,11 +3381,13 @@
      * using the interface, stopping DHCP & disabling interface
      */
     private void handleNetworkDisconnect() {
-        if (DBG) log("handleNetworkDisconnect: Stopping DHCP and clearing IP"
-                + " stack:" + Thread.currentThread().getStackTrace()[2].getMethodName()
-                + " - " + Thread.currentThread().getStackTrace()[3].getMethodName()
-                + " - " + Thread.currentThread().getStackTrace()[4].getMethodName()
-                + " - " + Thread.currentThread().getStackTrace()[5].getMethodName());
+        if (mVerboseLoggingEnabled) {
+            log("handleNetworkDisconnect: Stopping DHCP and clearing IP"
+                    + " stack:" + Thread.currentThread().getStackTrace()[2].getMethodName()
+                    + " - " + Thread.currentThread().getStackTrace()[3].getMethodName()
+                    + " - " + Thread.currentThread().getStackTrace()[4].getMethodName()
+                    + " - " + Thread.currentThread().getStackTrace()[5].getMethodName());
+        }
 
         stopRssiMonitoringOffload();
 
@@ -3538,7 +3398,7 @@
         /* Reset data structures */
         mWifiScoreReport = null;
         mWifiInfo.reset();
-        linkDebouncing = false;
+        mIsLinkDebouncing = false;
         /* Reset roaming parameters */
         mAutoRoaming = false;
 
@@ -3594,7 +3454,7 @@
              */
             // Disable the coexistence mode
             mWifiNative.setBluetoothCoexistenceMode(
-                    mWifiNative.BLUETOOTH_COEXISTENCE_MODE_DISABLED);
+                    WifiNative.BLUETOOTH_COEXISTENCE_MODE_DISABLED);
         }
 
         // Disable power save and suspend optimizations during DHCP
@@ -3605,15 +3465,17 @@
         mWifiNative.setPowerSave(false);
 
         // Update link layer stats
-        getWifiLinkLayerStats(false);
+        getWifiLinkLayerStats();
 
-        /* P2p discovery breaks dhcp, shut it down in order to get through this */
-        Message msg = new Message();
-        msg.what = WifiP2pServiceImpl.BLOCK_DISCOVERY;
-        msg.arg1 = WifiP2pServiceImpl.ENABLED;
-        msg.arg2 = DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE;
-        msg.obj = WifiStateMachine.this;
-        mWifiP2pChannel.sendMessage(msg);
+        if (mWifiP2pChannel != null) {
+            /* P2p discovery breaks dhcp, shut it down in order to get through this */
+            Message msg = new Message();
+            msg.what = WifiP2pServiceImpl.BLOCK_DISCOVERY;
+            msg.arg1 = WifiP2pServiceImpl.ENABLED;
+            msg.arg2 = DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE;
+            msg.obj = WifiStateMachine.this;
+            mWifiP2pChannel.sendMessage(msg);
+        }
     }
 
     void handlePostDhcpSetup() {
@@ -3621,12 +3483,11 @@
         setSuspendOptimizationsNative(SUSPEND_DUE_TO_DHCP, true);
         mWifiNative.setPowerSave(true);
 
-        mWifiP2pChannel.sendMessage(WifiP2pServiceImpl.BLOCK_DISCOVERY,
-                WifiP2pServiceImpl.DISABLED);
+        p2pSendMessage(WifiP2pServiceImpl.BLOCK_DISCOVERY, WifiP2pServiceImpl.DISABLED);
 
         // Set the coexistence mode back to its default value
         mWifiNative.setBluetoothCoexistenceMode(
-                mWifiNative.BLUETOOTH_COEXISTENCE_MODE_SENSE);
+                WifiNative.BLUETOOTH_COEXISTENCE_MODE_SENSE);
     }
 
     /**
@@ -3638,6 +3499,7 @@
         switch (level2FailureCode) {
             case WifiMetrics.ConnectionEvent.FAILURE_NONE:
             case WifiMetrics.ConnectionEvent.FAILURE_REDUNDANT_CONNECTION_ATTEMPT:
+            case WifiMetrics.ConnectionEvent.FAILURE_CONNECT_NETWORK_FAILED:
                 // WifiLogger doesn't care about success, or pre-empted connections.
                 break;
             default:
@@ -3646,7 +3508,7 @@
     }
 
     private void handleIPv4Success(DhcpResults dhcpResults) {
-        if (DBG) {
+        if (mVerboseLoggingEnabled) {
             logd("handleIPv4Success <" + dhcpResults.toString() + ">");
             logd("link address " + dhcpResults.ipAddress);
         }
@@ -3705,7 +3567,7 @@
         // TODO: Move this to provisioning failure, not DHCP failure.
         // DHCPv4 failure is expected on an IPv6-only network.
         mWifiLogger.captureBugReportData(WifiLogger.REPORT_REASON_DHCP_FAILURE);
-        if (DBG) {
+        if (mVerboseLoggingEnabled) {
             int count = -1;
             WifiConfiguration config = getCurrentWifiConfiguration();
             if (config != null) {
@@ -3722,7 +3584,7 @@
                  mDhcpResults.clear();
              }
         }
-        if (DBG) {
+        if (mVerboseLoggingEnabled) {
             logd("handleIPv4Failure");
         }
     }
@@ -3751,82 +3613,28 @@
         mWifiNative.disconnect();
     }
 
-    private int convertFrequencyToChannelNumber(int frequency) {
-        if (frequency >= 2412 && frequency <= 2484) {
-            return (frequency -2412) / 5 + 1;
-        } else if (frequency >= 5170  &&  frequency <=5825) {
-            //DFS is included
-            return (frequency -5170) / 5 + 34;
-        } else {
-            return 0;
-        }
-    }
-
-    private int chooseApChannel(int apBand) {
-        int apChannel;
-        int[] channel;
-
-        if (apBand == 0)  {
-            ArrayList<Integer> allowed2GChannel =
-                    mWifiApConfigStore.getAllowed2GChannel();
-            if (allowed2GChannel == null || allowed2GChannel.size() == 0) {
-                //most safe channel to use
-                if (DBG) {
-                    Log.d(TAG, "No specified 2G allowed channel list");
-                }
-                apChannel = 6;
-            } else {
-                int index = mRandom.nextInt(allowed2GChannel.size());
-                apChannel = allowed2GChannel.get(index).intValue();
-            }
-        } else {
-            //5G without DFS
-            channel = mWifiNative.getChannelsForBand(2);
-            if (channel != null && channel.length > 0) {
-                apChannel = channel[mRandom.nextInt(channel.length)];
-                apChannel = convertFrequencyToChannelNumber(apChannel);
-            } else {
-                Log.e(TAG, "SoftAp do not get available channel list");
-                apChannel = 0;
-            }
-        }
-
-        if (DBG) {
-            Log.d(TAG, "SoftAp set on channel " + apChannel);
-        }
-
-        return apChannel;
-    }
-
     /* Driver/firmware setup for soft AP. */
-    private boolean setupDriverForSoftAp() {
-        if (!mWifiNative.loadDriver()) {
-            Log.e(TAG, "Failed to load driver for softap");
-            return false;
+    private IApInterface setupDriverForSoftAp() {
+        if (mWificond == null) {
+            Log.e(TAG, "Failed to get reference to wificond");
+            return null;
         }
 
-        int index = mWifiNative.queryInterfaceIndex(mInterfaceName);
-        if (index != -1) {
-            if (!mWifiNative.setInterfaceUp(false)) {
-                Log.e(TAG, "toggleInterface failed");
-                return false;
-            }
-        } else {
-            if (DBG) Log.d(TAG, "No interfaces to bring down");
-        }
-
+        IApInterface apInterface = null;
         try {
-            mNwService.wifiFirmwareReload(mInterfaceName, "AP");
-            if (DBG) Log.d(TAG, "Firmware reloaded in AP mode");
-        } catch (Exception e) {
-            Log.e(TAG, "Failed to reload AP firmware " + e);
+            apInterface = mWificond.createApInterface();
+        } catch (RemoteException e1) { }
+
+        if (apInterface == null) {
+            Log.e(TAG, "Could not get IApInterface instance from wificond");
+            return null;
         }
 
         if (!mWifiNative.startHal()) {
             /* starting HAL is optional */
             Log.e(TAG, "Failed to start HAL");
         }
-        return true;
+        return apInterface;
     }
 
     private byte[] macAddressFromString(String macString) {
@@ -3909,6 +3717,7 @@
             --mConnectionRequests;
         }
 
+        @Override
         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             pw.println("mConnectionRequests " + mConnectionRequests);
         }
@@ -3946,6 +3755,7 @@
             }
         }
 
+        @Override
         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             pw.println("mUntrustedReqCount " + mUntrustedReqCount);
         }
@@ -3975,6 +3785,7 @@
      *******************************************************/
 
     class DefaultState extends State {
+
         @Override
         public boolean processMessage(Message message) {
             logStateAndMessage(message, this);
@@ -3984,7 +3795,7 @@
                     AsyncChannel ac = (AsyncChannel) message.obj;
                     if (ac == mWifiP2pChannel) {
                         if (message.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
-                            mWifiP2pChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
+                            p2pSendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
                         } else {
                             loge("WifiP2pService connection failure, error=" + message.arg1);
                         }
@@ -4081,7 +3892,6 @@
                 case CMD_DISABLE_P2P_RSP:
                 case WifiMonitor.SUP_REQUEST_IDENTITY:
                 case CMD_TEST_NETWORK_DISCONNECT:
-                case CMD_OBTAINING_IP_ADDRESS_WATCHDOG_TIMER:
                 case WifiMonitor.SUP_REQUEST_SIM_AUTH:
                 case CMD_TARGET_BSSID:
                 case CMD_AUTO_CONNECT:
@@ -4092,7 +3902,6 @@
                 case CMD_DISCONNECTING_WATCHDOG_TIMER:
                 case CMD_ROAM_WATCHDOG_TIMER:
                 case CMD_DISABLE_EPHEMERAL_NETWORK:
-                case CMD_UPDATE_ASSOCIATED_SCAN_PERMISSION:
                     messageHandlingStatus = MESSAGE_HANDLING_STATUS_DISCARD;
                     break;
                 case CMD_SET_SUSPEND_OPT_ENABLED:
@@ -4235,9 +4044,18 @@
     class InitialState extends State {
         @Override
         public void enter() {
+            if (mWificond != null) {
+                try {
+                    mWificond.tearDownInterfaces();
+                } catch (RemoteException e) {
+                    // There is very little we can do here
+                    Log.e(TAG, "Failed to tear down interfaces via wificond");
+                }
+                mWificond = null;
+            }
             mWifiNative.stopHal();
             mWifiNative.unloadDriver();
-            if (mWifiP2pChannel == null) {
+            if (mWifiP2pChannel == null && mWifiP2pServiceImpl != null) {
                 mWifiP2pChannel = new AsyncChannel();
                 mWifiP2pChannel.connect(mContext, getHandler(),
                     mWifiP2pServiceImpl.getP2pStateMachineMessenger());
@@ -4299,7 +4117,7 @@
                         if (mWifiNative.startSupplicant(mP2pSupported)) {
                             setSupplicantLogLevel();
                             setWifiState(WIFI_STATE_ENABLING);
-                            if (DBG) log("Supplicant start successful");
+                            if (mVerboseLoggingEnabled) log("Supplicant start successful");
                             mWifiMonitor.startMonitoring(mInterfaceName);
                             transitionTo(mSupplicantStartingState);
                         } else {
@@ -4312,17 +4130,9 @@
                     }
                     break;
                 case CMD_START_AP:
-                    if (setupDriverForSoftAp()) {
-                        transitionTo(mSoftApState);
-                    } else {
-                        setWifiApState(WIFI_AP_STATE_FAILED,
-                                WifiManager.SAP_START_FAILURE_GENERAL);
-                        /**
-                         * Transition to InitialState (current state) to reset the
-                         * driver/HAL back to the initial state.
-                         */
-                        transitionTo(mInitialState);
-                    }
+                    // Refresh our reference to wificond.  This allows us to tolerate restarts.
+                    mWificond = mWifiInjector.makeWificond();
+                    transitionTo(mSoftApState);
                     break;
                 default:
                     return NOT_HANDLED;
@@ -4368,7 +4178,7 @@
 
             switch(message.what) {
                 case WifiMonitor.SUP_CONNECTION_EVENT:
-                    if (DBG) log("Supplicant connection established");
+                    if (mVerboseLoggingEnabled) log("Supplicant connection established");
                     setWifiState(WIFI_STATE_ENABLED);
                     mSupplicantRestartCount = 0;
                     /* Reset the supplicant state to indicate the supplicant
@@ -4384,9 +4194,6 @@
                     setFrequencyBand();
                     mWifiNative.enableSaveConfig();
                     mWifiConfigManager.loadAndEnableAllNetworks();
-                    if (mWifiConfigManager.mEnableVerboseLogging.get() > 0) {
-                        enableVerboseLogging(mWifiConfigManager.mEnableVerboseLogging.get());
-                    }
                     initializeWpsDetails();
 
                     sendSupplicantConnectionChangedBroadcast(true);
@@ -4514,7 +4321,7 @@
                     }
                     break;
                 case CMD_GET_LINK_LAYER_STATS:
-                    WifiLinkLayerStats stats = getWifiLinkLayerStats(DBG);
+                    WifiLinkLayerStats stats = getWifiLinkLayerStats();
                     replyToMessage(message, message.what, stats);
                     break;
                 case CMD_RESET_SIM_NETWORKS:
@@ -4566,7 +4373,7 @@
                     loge("Supplicant connection received while stopping");
                     break;
                 case WifiMonitor.SUP_DISCONNECTION_EVENT:
-                    if (DBG) log("Supplicant connection lost");
+                    if (mVerboseLoggingEnabled) log("Supplicant connection lost");
                     handleSupplicantConnectionLoss(false);
                     transitionTo(mInitialState);
                     break;
@@ -4610,7 +4417,7 @@
             switch(message.what) {
                case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
                     SupplicantState state = handleSupplicantStateChange(message);
-                    /* If suplicant is exiting out of INTERFACE_DISABLED state into
+                    /* If supplicant is exiting out of INTERFACE_DISABLED state into
                      * a state that indicates driver has started, it is ready to
                      * receive driver commands
                      */
@@ -4668,7 +4475,7 @@
     class DriverStartedState extends State {
         @Override
         public void enter() {
-            if (DBG) {
+            if (mVerboseLoggingEnabled) {
                 logd("DriverStartedState enter");
             }
 
@@ -4683,7 +4490,7 @@
                     getHandler().getLooper());
             }
 
-            mWifiLogger.startLogging(DBG);
+            mWifiLogger.startLogging(mVerboseLoggingEnabled);
             mIsRunning = true;
             updateBatteryWorkSource(null);
             /**
@@ -4714,19 +4521,18 @@
                 // Status pulls in the current supplicant state and network connection state
                 // events over the monitor connection. This helps framework sync up with
                 // current supplicant state
-                // TODO: actually check th supplicant status string and make sure the supplicant
+                // TODO: actually check the supplicant status string and make sure the supplicant
                 // is in disconnecte4d state.
                 mWifiNative.status();
                 // Transitioning to Disconnected state will trigger a scan and subsequently AutoJoin
                 transitionTo(mDisconnectedState);
-                transitionTo(mDisconnectedState);
             }
 
             // We may have missed screen update at boot
             if (mScreenBroadcastReceived.get() == false) {
                 PowerManager powerManager = (PowerManager)mContext.getSystemService(
                         Context.POWER_SERVICE);
-                handleScreenStateChanged(powerManager.isScreenOn());
+                handleScreenStateChanged(powerManager.isInteractive());
             } else {
                 // Set the right suspend mode settings
                 mWifiNative.setSuspendOptimizations(mSuspendOptNeedsDisabled == 0
@@ -4741,14 +4547,25 @@
 
             if (mP2pSupported) {
                 if (mOperationalMode == CONNECT_MODE) {
-                    mWifiP2pChannel.sendMessage(WifiStateMachine.CMD_ENABLE_P2P);
+                    p2pSendMessage(WifiStateMachine.CMD_ENABLE_P2P);
                 } else {
-                    // P2P statemachine starts in disabled state, and is not enabled until
+                    // P2P state machine starts in disabled state, and is not enabled until
                     // CMD_ENABLE_P2P is sent from here; so, nothing needs to be done to
                     // keep it disabled.
                 }
             }
 
+            if (mNanSupported && mWifiNanManager != null) {
+                if (mOperationalMode == CONNECT_MODE) {
+                    mWifiNanManager.enableUsage();
+                } else {
+                    /*
+                     * NAN state machine starts in disabled state. Nothing
+                     * needed to keep it disabled.
+                     */
+                }
+            }
+
             final Intent intent = new Intent(WifiManager.WIFI_SCAN_AVAILABLE);
             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
             intent.putExtra(WifiManager.EXTRA_SCAN_AVAILABLE, WIFI_STATE_ENABLED);
@@ -4768,16 +4585,16 @@
                     break;
                 case CMD_SET_FREQUENCY_BAND:
                     int band =  message.arg1;
-                    if (DBG) log("set frequency band " + band);
+                    if (mVerboseLoggingEnabled) log("set frequency band " + band);
                     if (mWifiNative.setBand(band)) {
 
-                        if (DBG)  logd("did set frequency band " + band);
+                        if (mVerboseLoggingEnabled)  logd("did set frequency band " + band);
 
                         mFrequencyBand.set(band);
                         // Flush old data - like scan results
                         mWifiNative.bssFlush();
 
-                        if (DBG)  logd("done set frequency band " + band);
+                        if (mVerboseLoggingEnabled)  logd("done set frequency band " + band);
 
                     } else {
                         loge("Failed to set frequency band " + band);
@@ -4789,8 +4606,6 @@
                     mWifiNative.setBluetoothCoexistenceScanMode(mBluetoothConnectionActive);
                     break;
                 case CMD_STOP_DRIVER:
-                    int mode = message.arg1;
-
                     log("stop driver");
                     mWifiConfigManager.disableAllNetworksNative();
 
@@ -4878,9 +4693,9 @@
             }
             return HANDLED;
         }
+
         @Override
         public void exit() {
-
             mWifiLogger.stopLogging();
 
             mIsRunning = false;
@@ -4892,6 +4707,10 @@
             intent.putExtra(WifiManager.EXTRA_SCAN_AVAILABLE, WIFI_STATE_DISABLED);
             mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
             mBufferedScanMsg.clear();
+
+            if (mNanSupported && mWifiNanManager != null) {
+                mWifiNanManager.disableUsage();
+            }
         }
     }
 
@@ -4913,7 +4732,7 @@
                     mTransitionToState = mDriverStoppingState;
                     break;
             }
-            mWifiP2pChannel.sendMessage(WifiStateMachine.CMD_DISABLE_P2P_REQ);
+            p2pSendMessage(WifiStateMachine.CMD_DISABLE_P2P_REQ);
         }
         @Override
         public boolean processMessage(Message message) {
@@ -5023,7 +4842,7 @@
                             // Load and re-enable networks when going back to enabled state
                             // This is essential for networks to show up after restore
                             mWifiConfigManager.loadAndEnableAllNetworks();
-                            mWifiP2pChannel.sendMessage(CMD_ENABLE_P2P);
+                            p2pSendMessage(CMD_ENABLE_P2P);
                         } else {
                             mWifiConfigManager.enableAllNetworks();
                         }
@@ -5047,7 +4866,7 @@
                     break;
                 case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
                     SupplicantState state = handleSupplicantStateChange(message);
-                    if (DBG) log("SupplicantState= " + state);
+                    if (mVerboseLoggingEnabled) log("SupplicantState= " + state);
                     break;
                 default:
                     return NOT_HANDLED;
@@ -5200,7 +5019,7 @@
             if (config != null) {
                 //Here we will clear all disable counters once a network is connected
                 //records how long this network is connected in future
-                config.lastConnected = System.currentTimeMillis();
+                config.lastConnected = mClock.getWallClockMillis();
                 config.numAssociation++;
                 WifiConfiguration.NetworkSelectionStatus networkSelectionStatus =
                         config.getNetworkSelectionStatus();
@@ -5218,7 +5037,7 @@
             // hence record the time we were connected last
             WifiConfiguration config = mWifiConfigManager.getWifiConfiguration(mLastNetworkId);
             if (config != null) {
-                config.lastDisconnected = System.currentTimeMillis();
+                config.lastDisconnected = mClock.getWallClockMillis();
                 if (config.ephemeral) {
                     // Remove ephemeral WifiConfigurations from file
                     mWifiConfigManager.forgetNetwork(mLastNetworkId);
@@ -5289,7 +5108,7 @@
     }
 
     String getCurrentBSSID() {
-        if (linkDebouncing) {
+        if (isLinkDebouncing()) {
             return null;
         }
         return mLastBssid;
@@ -5305,8 +5124,6 @@
             }
             // Inform metrics that Wifi is Enabled (but not yet connected)
             mWifiMetrics.setWifiState(WifiMetricsProto.WifiLog.WIFI_DISCONNECTED);
-
-
         }
 
         @Override
@@ -5356,9 +5173,10 @@
                     reportConnectionAttemptEnd(
                             WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_REJECTION,
                             WifiMetricsProto.ConnectionEvent.HLF_NONE);
-                    mWifiLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(getTargetSsid(),
-                            bssid,
-                            WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
+                    mWifiInjector.getWifiLastResortWatchdog()
+                            .noteConnectionFailureAndTriggerIfNeeded(
+                                    getTargetSsid(), bssid,
+                                    WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
                     break;
                 case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
                     mWifiLogger.captureBugReportData(WifiLogger.REPORT_REASON_AUTH_FAILURE);
@@ -5372,9 +5190,10 @@
                     reportConnectionAttemptEnd(
                             WifiMetrics.ConnectionEvent.FAILURE_AUTHENTICATION_FAILURE,
                             WifiMetricsProto.ConnectionEvent.HLF_NONE);
-                    mWifiLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(getTargetSsid(),
-                            mTargetRoamBSSID,
-                            WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                    mWifiInjector.getWifiLastResortWatchdog()
+                            .noteConnectionFailureAndTriggerIfNeeded(
+                                    getTargetSsid(), mTargetRoamBSSID,
+                                    WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
                     break;
                 case WifiMonitor.SSID_TEMP_DISABLED:
                     Log.e(TAG, "Supplicant SSID temporary disabled:"
@@ -5386,9 +5205,10 @@
                     reportConnectionAttemptEnd(
                             WifiMetrics.ConnectionEvent.FAILURE_SSID_TEMP_DISABLED,
                             WifiMetricsProto.ConnectionEvent.HLF_NONE);
-                    mWifiLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(getTargetSsid(),
-                            mTargetRoamBSSID,
-                            WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
+                    mWifiInjector.getWifiLastResortWatchdog()
+                            .noteConnectionFailureAndTriggerIfNeeded(
+                                    getTargetSsid(), mTargetRoamBSSID,
+                                    WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
                     break;
                 case WifiMonitor.SSID_REENABLED:
                     Log.d(TAG, "Supplicant SSID reenable:"
@@ -5415,9 +5235,11 @@
                     // we can figure this from the supplicant state. If supplicant
                     // state is DISCONNECTED, but the mNetworkInfo says we are not
                     // disconnected, we need to handle a disconnection
-                    if (!linkDebouncing && state == SupplicantState.DISCONNECTED &&
+                    if (!isLinkDebouncing() && state == SupplicantState.DISCONNECTED &&
                             mNetworkInfo.getState() != NetworkInfo.State.DISCONNECTED) {
-                        if (DBG) log("Missed CTRL-EVENT-DISCONNECTED, disconnect");
+                        if (mVerboseLoggingEnabled) {
+                            log("Missed CTRL-EVENT-DISCONNECTED, disconnect");
+                        }
                         handleNetworkDisconnect();
                         transitionTo(mDisconnectedState);
                     }
@@ -5484,7 +5306,7 @@
                                 }
 
                                 // Remember time of last connection attempt
-                                lastConnectAttemptTimestamp = System.currentTimeMillis();
+                                lastConnectAttemptTimestamp = mClock.getWallClockMillis();
                                 mWifiConnectionStatistics.numWifiManagerJoinAttempt++;
 
                                 // As a courtesy to the caller, trigger a scan now
@@ -5545,7 +5367,7 @@
                     // networks need to be disabled
                     if (disableOthers) {
                         // Remember time of last connection attempt
-                        lastConnectAttemptTimestamp = System.currentTimeMillis();
+                        lastConnectAttemptTimestamp = mClock.getWallClockMillis();
                         mWifiConnectionStatistics.numWifiManagerJoinAttempt++;
                     }
                     // Cancel auto roam requests
@@ -5596,7 +5418,7 @@
                 case CMD_SAVE_CONFIG:
                     ok = mWifiConfigManager.saveConfig();
 
-                    if (DBG) logd("did save config " + ok);
+                    if (mVerboseLoggingEnabled) logd("did save config " + ok);
                     replyToMessage(message, CMD_SAVE_CONFIG, ok ? SUCCESS : FAILURE);
 
                     // Inform the backup manager about a data change
@@ -5679,14 +5501,14 @@
                     }
                     break;
                 case CMD_REASSOCIATE:
-                    lastConnectAttemptTimestamp = System.currentTimeMillis();
+                    lastConnectAttemptTimestamp = mClock.getWallClockMillis();
                     mWifiNative.reassociate();
                     break;
                 case CMD_RELOAD_TLS_AND_RECONNECT:
                     if (mWifiConfigManager.needsUnlockedKeyStore()) {
                         logd("Reconnecting to give a chance to un-connected TLS networks");
                         mWifiNative.disconnect();
-                        lastConnectAttemptTimestamp = System.currentTimeMillis();
+                        lastConnectAttemptTimestamp = mClock.getWallClockMillis();
                         mWifiNative.reconnect();
                     }
                     break;
@@ -5783,7 +5605,7 @@
                     }
                     if (mWifiConfigManager.selectNetwork(config, /* updatePriorities = */ false,
                             lastConnectUid) && mWifiNative.reconnect()) {
-                        lastConnectAttemptTimestamp = System.currentTimeMillis();
+                        lastConnectAttemptTimestamp = mClock.getWallClockMillis();
                         targetWificonfiguration = mWifiConfigManager.getWifiConfiguration(netId);
                         config = mWifiConfigManager.getWifiConfiguration(netId);
                         if (config != null
@@ -5805,7 +5627,7 @@
                                          WifiConfiguration.INVALID_NETWORK_ID);
                         }
                         mAutoRoaming = false;
-                        if (isRoaming() || linkDebouncing) {
+                        if (isRoaming() || isLinkDebouncing()) {
                             transitionTo(mRoamingState);
                         } else if (didDisconnect) {
                             transitionTo(mDisconnectingState);
@@ -5946,7 +5768,7 @@
                             WifiMetricsProto.ConnectionEvent.ROAM_USER_SELECTED);
                     if (mWifiConfigManager.selectNetwork(config, /* updatePriorities = */ true,
                             message.sendingUid) && mWifiNative.reconnect()) {
-                        lastConnectAttemptTimestamp = System.currentTimeMillis();
+                        lastConnectAttemptTimestamp = mClock.getWallClockMillis();
                         targetWificonfiguration = mWifiConfigManager.getWifiConfiguration(netId);
 
                         /* The state tracker handles enabling networks upon completion/failure */
@@ -6045,7 +5867,7 @@
                         replyToMessage(message, WifiManager.SAVE_NETWORK_SUCCEEDED);
                         broadcastWifiCredentialChanged(WifiManager.WIFI_CREDENTIAL_SAVED, config);
 
-                        if (DBG) {
+                        if (mVerboseLoggingEnabled) {
                            logd("Success save network nid="
                                     + Integer.toString(result.getNetworkId()));
                         }
@@ -6167,14 +5989,15 @@
                     }
                     return NOT_HANDLED;
                 case WifiMonitor.NETWORK_CONNECTION_EVENT:
-                    if (DBG) log("Network connection established");
+                    if (mVerboseLoggingEnabled) log("Network connection established");
                     mLastNetworkId = message.arg1;
                     mLastBssid = (String) message.obj;
 
                     mWifiInfo.setBSSID(mLastBssid);
                     mWifiInfo.setNetworkId(mLastNetworkId);
-                    mWifiQualifiedNetworkSelector
-                            .enableBssidForQualityNetworkSelection(mLastBssid, true);
+                    if (mWifiConnectivityManager != null) {
+                        mWifiConnectivityManager.trackBssid(mLastBssid, true);
+                    }
                     sendNetworkStateChangeBroadcast(mLastBssid);
                     transitionTo(mObtainingIpState);
                     break;
@@ -6188,7 +6011,7 @@
                     // The side effect of calling handleNetworkDisconnect twice is that a bunch of
                     // idempotent commands are executed twice (stopping Dhcp, enabling the SPS mode
                     // at the chip etc...
-                    if (DBG) log("ConnectModeState: Network connection lost ");
+                    if (mVerboseLoggingEnabled) log("ConnectModeState: Network connection lost ");
                     handleNetworkDisconnect();
                     transitionTo(mDisconnectedState);
                     break;
@@ -6258,11 +6081,14 @@
                 NetworkCapabilities nc, LinkProperties lp, int score, NetworkMisc misc) {
             super(l, c, TAG, ni, nc, lp, score, misc);
         }
+
+        @Override
         protected void unwanted() {
             // Ignore if we're not the current networkAgent.
             if (this != mNetworkAgent) return;
-            if (DBG) log("WifiNetworkAgent -> Wifi unwanted score "
-                    + Integer.toString(mWifiInfo.score));
+            if (mVerboseLoggingEnabled) {
+                log("WifiNetworkAgent -> Wifi unwanted score " + Integer.toString(mWifiInfo.score));
+            }
             unwantedNetwork(NETWORK_STATUS_UNWANTED_DISCONNECT);
         }
 
@@ -6270,11 +6096,13 @@
         protected void networkStatus(int status, String redirectUrl) {
             if (this != mNetworkAgent) return;
             if (status == NetworkAgent.INVALID_NETWORK) {
-                if (DBG) log("WifiNetworkAgent -> Wifi networkStatus invalid, score="
-                        + Integer.toString(mWifiInfo.score));
+                if (mVerboseLoggingEnabled) {
+                    log("WifiNetworkAgent -> Wifi networkStatus invalid, score="
+                            + Integer.toString(mWifiInfo.score));
+                }
                 unwantedNetwork(NETWORK_STATUS_UNWANTED_VALIDATION_FAILED);
             } else if (status == NetworkAgent.VALID_NETWORK) {
-                if (DBG) {
+                if (mVerboseLoggingEnabled) {
                     log("WifiNetworkAgent -> Wifi networkStatus valid, score= "
                             + Integer.toString(mWifiInfo.score));
                 }
@@ -6412,7 +6240,7 @@
         }
         HashSet<Integer> freqs = mWifiConfigManager.makeChannelList(config, ONE_HOUR_MILLI);
         if (freqs != null && freqs.size() != 0) {
-            //if (DBG) {
+            //if (mVerboseLoggingEnabled) {
             logd("starting scan for " + config.configKey() + " with " + freqs);
             //}
             Set<Integer> hiddenNetworkIds = new HashSet<>();
@@ -6428,7 +6256,7 @@
             }
             return true;
         } else {
-            if (DBG) logd("no channels for " + config.configKey());
+            if (mVerboseLoggingEnabled) logd("no channels for " + config.configKey());
             return false;
         }
     }
@@ -6443,12 +6271,12 @@
     void clearConfigBSSID(WifiConfiguration config, String dbg) {
         if (config == null)
             return;
-        if (DBG) {
+        if (mVerboseLoggingEnabled) {
             logd(dbg + " " + mTargetRoamBSSID + " config " + config.configKey()
                     + " config.NetworkSelectionStatus.mNetworkSelectionBSSID "
                     + config.getNetworkSelectionStatus().getNetworkSelectionBSSID());
         }
-        if (DBG) {
+        if (mVerboseLoggingEnabled) {
            logd(dbg + " " + config.SSID
                     + " nid=" + Integer.toString(config.networkId));
         }
@@ -6489,7 +6317,7 @@
             // For paranoia's sake, call handleNetworkDisconnect
             // only if BSSID is null or last networkId
             // is not invalid.
-            if (DBG) {
+            if (mVerboseLoggingEnabled) {
                 StringBuilder sb = new StringBuilder();
                 sb.append("leaving L2ConnectedState state nid=" + Integer.toString(mLastNetworkId));
                 if (mLastBssid !=null) {
@@ -6543,12 +6371,15 @@
                     break;
                 case CMD_IP_CONFIGURATION_LOST:
                     // Get Link layer stats so that we get fresh tx packet counters.
-                    getWifiLinkLayerStats(true);
+                    getWifiLinkLayerStats();
                     handleIpConfigurationLost();
+                    reportConnectionAttemptEnd(
+                            WifiMetrics.ConnectionEvent.FAILURE_DHCP,
+                            WifiMetricsProto.ConnectionEvent.HLF_NONE);
                     transitionTo(mDisconnectingState);
                     break;
                 case CMD_IP_REACHABILITY_LOST:
-                    if (DBG && message.obj != null) log((String) message.obj);
+                    if (mVerboseLoggingEnabled && message.obj != null) log((String) message.obj);
                     handleIpReachabilityLost();
                     transitionTo(mDisconnectingState);
                     break;
@@ -6586,7 +6417,7 @@
                     mWifiInfo.setBSSID((String) message.obj);
                     mLastNetworkId = message.arg1;
                     mWifiInfo.setNetworkId(mLastNetworkId);
-                    if(!mLastBssid.equals((String) message.obj)) {
+                    if(!mLastBssid.equals(message.obj)) {
                         mLastBssid = (String) message.obj;
                         sendNetworkStateChangeBroadcast(mLastBssid);
                     }
@@ -6594,8 +6425,10 @@
                 case CMD_RSSI_POLL:
                     if (message.arg1 == mRssiPollToken) {
                         if (mWifiConfigManager.mEnableChipWakeUpWhenAssociated.get()) {
-                            if (DBG) log(" get link layer stats " + mWifiLinkLayerStatsSupported);
-                            WifiLinkLayerStats stats = getWifiLinkLayerStats(DBG);
+                            if (mVerboseLoggingEnabled) {
+                                log(" get link layer stats " + mWifiLinkLayerStatsSupported);
+                            }
+                            WifiLinkLayerStats stats = getWifiLinkLayerStats();
                             if (stats != null) {
                                 // Sanity check the results provided by driver
                                 if (mWifiInfo.getRssi() != WifiInfo.INVALID_RSSI
@@ -6612,11 +6445,12 @@
                                                                    mWifiConfigManager,
                                                                    mNetworkAgent,
                                                                    mWifiScoreReport,
-                                                                   mAggressiveHandover);
+                                                                   mAggressiveHandover,
+                                                                   mVerboseLoggingEnabled);
                         }
                         sendMessageDelayed(obtainMessage(CMD_RSSI_POLL,
                                 mRssiPollToken, 0), POLL_RSSI_INTERVAL_MSECS);
-                        if (DBG) sendRssiChangeBroadcast(mWifiInfo.getRssi());
+                        if (mVerboseLoggingEnabled) sendRssiChangeBroadcast(mWifiInfo.getRssi());
                     } else {
                         // Polling has completed
                     }
@@ -6644,7 +6478,7 @@
                     replyToMessage(message, WifiManager.RSSI_PKTCNT_FETCH_SUCCEEDED, info);
                     break;
                 case CMD_DELAYED_NETWORK_DISCONNECT:
-                    if (!linkDebouncing && mWifiConfigManager.mEnableLinkDebouncing) {
+                    if (!isLinkDebouncing()) {
 
                         // Ignore if we are not debouncing
                         logd("CMD_DELAYED_NETWORK_DISCONNECT and not debouncing - ignore "
@@ -6654,7 +6488,7 @@
                         logd("CMD_DELAYED_NETWORK_DISCONNECT and debouncing - disconnect "
                                 + message.arg1);
 
-                        linkDebouncing = false;
+                        mIsLinkDebouncing = false;
                         // If we are still debouncing while this message comes,
                         // it means we were not able to reconnect within the alloted time
                         // = LINK_FLAPPING_DEBOUNCE_MSEC
@@ -6706,7 +6540,7 @@
     class ObtainingIpState extends State {
         @Override
         public void enter() {
-            if (DBG) {
+            if (mVerboseLoggingEnabled) {
                 String key = "";
                 if (getCurrentWifiConfiguration() != null) {
                     key = getCurrentWifiConfiguration().configKey();
@@ -6714,13 +6548,12 @@
                 log("enter ObtainingIpState netId=" + Integer.toString(mLastNetworkId)
                         + " " + key + " "
                         + " roam=" + mAutoRoaming
-                        + " static=" + mWifiConfigManager.isUsingStaticIp(mLastNetworkId)
-                        + " watchdog= " + obtainingIpWatchdogCount);
+                        + " static=" + mWifiConfigManager.isUsingStaticIp(mLastNetworkId));
             }
 
             // Reset link Debouncing, indicating we have successfully re-connected to the AP
             // We might still be roaming
-            linkDebouncing = false;
+            mIsLinkDebouncing = false;
 
             // Send event to CM & network change broadcast
             setNetworkDetailedState(DetailedState.OBTAINING_IPADDR);
@@ -6749,17 +6582,13 @@
 
             if (!mWifiConfigManager.isUsingStaticIp(mLastNetworkId)) {
                 final IpManager.ProvisioningConfiguration prov =
-                        mIpManager.buildProvisioningConfiguration()
+                        IpManager.buildProvisioningConfiguration()
                             .withPreDhcpAction()
                             .withApfCapabilities(mWifiNative.getApfCapabilities())
                             .build();
                 mIpManager.startProvisioning(prov);
-                obtainingIpWatchdogCount++;
-                logd("Start Dhcp Watchdog " + obtainingIpWatchdogCount);
                 // Get Link layer stats so as we get fresh tx packet counters
-                getWifiLinkLayerStats(true);
-                sendMessageDelayed(obtainMessage(CMD_OBTAINING_IP_ADDRESS_WATCHDOG_TIMER,
-                        obtainingIpWatchdogCount, 0), OBTAINING_IP_ADDRESS_GUARD_TIMER_MSEC);
+                getWifiLinkLayerStats();
             } else {
                 StaticIpConfiguration config = mWifiConfigManager.getStaticIpConfiguration(
                         mLastNetworkId);
@@ -6768,7 +6597,7 @@
                     sendMessage(CMD_IPV4_PROVISIONING_FAILURE);
                 } else {
                     final IpManager.ProvisioningConfiguration prov =
-                            mIpManager.buildProvisioningConfiguration()
+                            IpManager.buildProvisioningConfiguration()
                                 .withStaticConfiguration(config)
                                 .withApfCapabilities(mWifiNative.getApfCapabilities())
                                 .build();
@@ -6807,16 +6636,6 @@
                     messageHandlingStatus = MESSAGE_HANDLING_STATUS_DEFERRED;
                     deferMessage(message);
                     break;
-                case CMD_OBTAINING_IP_ADDRESS_WATCHDOG_TIMER:
-                    if (message.arg1 == obtainingIpWatchdogCount) {
-                        logd("ObtainingIpAddress: Watchdog Triggered, count="
-                                + obtainingIpWatchdogCount);
-                        handleIpConfigurationLost();
-                        transitionTo(mDisconnectingState);
-                        break;
-                    }
-                    messageHandlingStatus = MESSAGE_HANDLING_STATUS_DISCARD;
-                    break;
                 default:
                     return NOT_HANDLED;
             }
@@ -6831,14 +6650,14 @@
         if (mWifiConfigManager.isLastSelectedConfiguration(config)) {
             boolean prompt =
                     mWifiConfigManager.checkConfigOverridePermission(config.lastConnectUid);
-            if (DBG) {
+            if (mVerboseLoggingEnabled) {
                 log("Network selected by UID " + config.lastConnectUid + " prompt=" + prompt);
             }
             if (prompt) {
                 // Selected by the user via Settings or QuickSettings. If this network has Internet
                 // access, switch to it. Otherwise, switch to it only if the user confirms that they
                 // really want to switch, or has already confirmed and selected "Don't ask again".
-                if (DBG) {
+                if (mVerboseLoggingEnabled) {
                     log("explictlySelected acceptUnvalidated=" + config.noInternetAccessExpected);
                 }
                 mNetworkAgent.explicitlySelected(config.noInternetAccessExpected);
@@ -6854,7 +6673,7 @@
         boolean mAssociated;
         @Override
         public void enter() {
-            if (DBG) {
+            if (mVerboseLoggingEnabled) {
                 log("RoamingState Enter"
                         + " mScreenOn=" + mScreenOn );
             }
@@ -6880,7 +6699,9 @@
                     }
                     return NOT_HANDLED;
                 case CMD_UNWANTED_NETWORK:
-                    if (DBG) log("Roaming and CS doesnt want the network -> ignore");
+                    if (mVerboseLoggingEnabled) {
+                        log("Roaming and CS doesnt want the network -> ignore");
+                    }
                     return HANDLED;
                 case CMD_SET_OPERATIONAL_MODE:
                     if (message.arg1 != CONNECT_MODE) {
@@ -6899,7 +6720,7 @@
                     if (stateChangeResult.state == SupplicantState.DISCONNECTED
                             || stateChangeResult.state == SupplicantState.INACTIVE
                             || stateChangeResult.state == SupplicantState.INTERFACE_DISABLED) {
-                        if (DBG) {
+                        if (mVerboseLoggingEnabled) {
                             log("STATE_CHANGE_EVENT in roaming state "
                                     + stateChangeResult.toString() );
                         }
@@ -6913,13 +6734,13 @@
                         // We completed the layer2 roaming part
                         mAssociated = true;
                         if (stateChangeResult.BSSID != null) {
-                            mTargetRoamBSSID = (String) stateChangeResult.BSSID;
+                            mTargetRoamBSSID = stateChangeResult.BSSID;
                         }
                     }
                     break;
                 case CMD_ROAM_WATCHDOG_TIMER:
                     if (roamWatchdogCount == message.arg1) {
-                        if (DBG) log("roaming watchdog! -> disconnect");
+                        if (mVerboseLoggingEnabled) log("roaming watchdog! -> disconnect");
                         mWifiMetrics.endConnectionEvent(
                                 WifiMetrics.ConnectionEvent.FAILURE_ROAM_TIMEOUT,
                                 WifiMetricsProto.ConnectionEvent.HLF_NONE);
@@ -6931,7 +6752,9 @@
                     break;
                 case WifiMonitor.NETWORK_CONNECTION_EVENT:
                     if (mAssociated) {
-                        if (DBG) log("roaming and Network connection established");
+                        if (mVerboseLoggingEnabled) {
+                            log("roaming and Network connection established");
+                        }
                         mLastNetworkId = message.arg1;
                         mLastBssid = (String) message.obj;
                         mWifiInfo.setBSSID(mLastBssid);
@@ -7018,9 +6841,8 @@
     class ConnectedState extends State {
         @Override
         public void enter() {
-            String address;
             updateDefaultRouteMacAddress(1000);
-            if (DBG) {
+            if (mVerboseLoggingEnabled) {
                 log("Enter ConnectedState "
                        + " mScreenOn=" + mScreenOn);
             }
@@ -7033,7 +6855,7 @@
             lastConnectAttemptTimestamp = 0;
             targetWificonfiguration = null;
             // Paranoia
-            linkDebouncing = false;
+            mIsLinkDebouncing = false;
 
             // Not roaming anymore
             mAutoRoaming = false;
@@ -7051,7 +6873,7 @@
 
             mLastDriverRoamAttempt = 0;
             mTargetNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
-            mWifiLastResortWatchdog.connectedStateTransition(true);
+            mWifiInjector.getWifiLastResortWatchdog().connectedStateTransition(true);
         }
         @Override
         public boolean processMessage(Message message) {
@@ -7059,9 +6881,6 @@
             logStateAndMessage(message, this);
 
             switch (message.what) {
-                case CMD_UPDATE_ASSOCIATED_SCAN_PERMISSION:
-                    updateAssociatedScanPermission();
-                    break;
                 case CMD_UNWANTED_NETWORK:
                     if (message.arg1 == NETWORK_STATUS_UNWANTED_DISCONNECT) {
                         mWifiConfigManager.handleBadNetworkDisconnectReport(
@@ -7121,7 +6940,7 @@
                 case CMD_ASSOCIATED_BSSID:
                     // ASSOCIATING to a new BSSID while already connected, indicates
                     // that driver is roaming
-                    mLastDriverRoamAttempt = System.currentTimeMillis();
+                    mLastDriverRoamAttempt = mClock.getWallClockMillis();
                     return NOT_HANDLED;
                 case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
                     long lastRoam = 0;
@@ -7130,7 +6949,7 @@
                             WifiMetricsProto.ConnectionEvent.HLF_NONE);
                     if (mLastDriverRoamAttempt != 0) {
                         // Calculate time since last driver roam attempt
-                        lastRoam = System.currentTimeMillis() - mLastDriverRoamAttempt;
+                        lastRoam = mClock.getWallClockMillis() - mLastDriverRoamAttempt;
                         mLastDriverRoamAttempt = 0;
                     }
                     if (unexpectedDisconnectedReason(message.arg2)) {
@@ -7138,8 +6957,9 @@
                                 WifiLogger.REPORT_REASON_UNEXPECTED_DISCONNECT);
                     }
                     config = getCurrentWifiConfiguration();
-                    if (mScreenOn
-                            && !linkDebouncing
+                    if (mWifiConfigManager.mEnableLinkDebouncing
+                            && mScreenOn
+                            && !isLinkDebouncing()
                             && config != null
                             && config.getNetworkSelectionStatus().isNetworkEnabled()
                             && !mWifiConfigManager.isLastSelectedConfiguration(config)
@@ -7158,11 +6978,11 @@
                         // roaming cycle and enter Obtaining IP address
                         // before signalling the disconnect to ConnectivityService and L3
                         startScanForConfiguration(getCurrentWifiConfiguration());
-                        linkDebouncing = true;
+                        mIsLinkDebouncing = true;
 
                         sendMessageDelayed(obtainMessage(CMD_DELAYED_NETWORK_DISCONNECT,
                                 0, mLastNetworkId), LINK_FLAPPING_DEBOUNCE_MSEC);
-                        if (DBG) {
+                        if (mVerboseLoggingEnabled) {
                             log("NETWORK_DISCONNECTION_EVENT in connected state"
                                     + " BSSID=" + mWifiInfo.getBSSID()
                                     + " RSSI=" + mWifiInfo.getRssi()
@@ -7172,12 +6992,12 @@
                         }
                         return HANDLED;
                     } else {
-                        if (DBG) {
+                        if (mVerboseLoggingEnabled) {
                             log("NETWORK_DISCONNECTION_EVENT in connected state"
                                     + " BSSID=" + mWifiInfo.getBSSID()
                                     + " RSSI=" + mWifiInfo.getRssi()
                                     + " freq=" + mWifiInfo.getFrequency()
-                                    + " was debouncing=" + linkDebouncing
+                                    + " was debouncing=" + isLinkDebouncing()
                                     + " reason=" + message.arg2
                                     + " Network Selection Status=" + (config == null ? "Unavailable"
                                     : config.getNetworkSelectionStatus().getNetworkStatusString()));
@@ -7262,7 +7082,7 @@
                         ret = mWifiNative.reassociate();
                     }
                     if (ret) {
-                        lastConnectAttemptTimestamp = System.currentTimeMillis();
+                        lastConnectAttemptTimestamp = mClock.getWallClockMillis();
                         targetWificonfiguration = mWifiConfigManager.getWifiConfiguration(netId);
 
                         // replyToMessage(message, WifiManager.CONNECT_NETWORK_SUCCEEDED);
@@ -7316,8 +7136,7 @@
             }
 
             mLastDriverRoamAttempt = 0;
-            mWhiteListedSsids = null;
-            mWifiLastResortWatchdog.connectedStateTransition(false);
+            mWifiInjector.getWifiLastResortWatchdog().connectedStateTransition(false);
         }
     }
 
@@ -7326,7 +7145,7 @@
         @Override
         public void enter() {
 
-            if (DBG) {
+            if (mVerboseLoggingEnabled) {
                 logd(" Enter DisconnectingState State screenOn=" + mScreenOn);
             }
 
@@ -7356,7 +7175,7 @@
                     return HANDLED;
                 case CMD_DISCONNECTING_WATCHDOG_TIMER:
                     if (disconnectingWatchdogCount == message.arg1) {
-                        if (DBG) log("disconnecting watchdog! -> disconnect");
+                        if (mVerboseLoggingEnabled) log("disconnecting watchdog! -> disconnect");
                         handleNetworkDisconnect();
                         transitionTo(mDisconnectedState);
                     }
@@ -7384,11 +7203,11 @@
             // We dont scan frequently if this is a temporary disconnect
             // due to p2p
             if (mTemporarilyDisconnectWifi) {
-                mWifiP2pChannel.sendMessage(WifiP2pServiceImpl.DISCONNECT_WIFI_RESPONSE);
+                p2pSendMessage(WifiP2pServiceImpl.DISCONNECT_WIFI_RESPONSE);
                 return;
             }
 
-            if (DBG) {
+            if (mVerboseLoggingEnabled) {
                 logd(" Enter DisconnectedState screenOn=" + mScreenOn);
             }
 
@@ -7411,7 +7230,7 @@
                         ++mPeriodicScanToken, 0), mNoNetworksPeriodicScan);
             }
 
-            mDisconnectedTimeStamp = System.currentTimeMillis();
+            mDisconnectedTimeStamp = mClock.getWallClockMillis();
         }
         @Override
         public boolean processMessage(Message message) {
@@ -7445,7 +7264,7 @@
                         mOperationalMode = message.arg1;
                         mWifiConfigManager.disableAllNetworksNative();
                         if (mOperationalMode == SCAN_ONLY_WITH_WIFI_OFF_MODE) {
-                            mWifiP2pChannel.sendMessage(CMD_DISABLE_P2P_REQ);
+                            p2pSendMessage(CMD_DISABLE_P2P_REQ);
                             setWifiState(WIFI_STATE_DISABLED);
                         }
                         transitionTo(mScanModeState);
@@ -7460,10 +7279,10 @@
                     break;
                 case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
                     StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
-                    if (DBG) {
+                    if (mVerboseLoggingEnabled) {
                         logd("SUPPLICANT_STATE_CHANGE_EVENT state=" + stateChangeResult.state +
                                 " -> state= " + WifiInfo.getDetailedStateOf(stateChangeResult.state)
-                                + " debouncing=" + linkDebouncing);
+                                + " debouncing=" + isLinkDebouncing());
                     }
                     setNetworkDetailedState(WifiInfo.getDetailedStateOf(stateChangeResult.state));
                     /* ConnectModeState does the rest of the handling */
@@ -7489,7 +7308,7 @@
                                 defaultInterval);
                         mWifiNative.setScanInterval((int) scanIntervalMs/1000);
                     } else if (mWifiConfigManager.getSavedNetworks().size() == 0) {
-                        if (DBG) log("Turn on scanning after p2p disconnected");
+                        if (mVerboseLoggingEnabled) log("Turn on scanning after p2p disconnected");
                         sendMessageDelayed(obtainMessage(CMD_NO_NETWORKS_PERIODIC_SCAN,
                                     ++mPeriodicScanToken, 0), mNoNetworksPeriodicScan);
                     }
@@ -7560,7 +7379,9 @@
                         mSourceMessage = null;
                         transitionTo(mDisconnectedState);
                     } else {
-                        if (DBG) log("Ignore unspecified fail event during WPS connection");
+                        if (mVerboseLoggingEnabled) {
+                            log("Ignore unspecified fail event during WPS connection");
+                        }
                     }
                     break;
                 case WifiMonitor.WPS_TIMEOUT_EVENT:
@@ -7602,18 +7423,20 @@
                     messageHandlingStatus = MESSAGE_HANDLING_STATUS_DISCARD;
                     return HANDLED;
                 case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
-                    if (DBG) log("Network connection lost");
+                    if (mVerboseLoggingEnabled) log("Network connection lost");
                     handleNetworkDisconnect();
                     break;
                 case WifiMonitor.ASSOCIATION_REJECTION_EVENT:
-                    if (DBG) log("Ignore Assoc reject event during WPS Connection");
+                    if (mVerboseLoggingEnabled) {
+                        log("Ignore Assoc reject event during WPS Connection");
+                    }
                     break;
                 case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
                     // Disregard auth failure events during WPS connection. The
                     // EAP sequence is retried several times, and there might be
                     // failures (especially for wps pin). We will get a WPS_XXX
                     // event at the end of the sequence anyway.
-                    if (DBG) log("Ignore auth failure during WPS connection");
+                    if (mVerboseLoggingEnabled) log("Ignore auth failure during WPS connection");
                     break;
                 case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
                     // Throw away supplicant state changes when WPS is running.
@@ -7652,30 +7475,41 @@
         @Override
         public void enter() {
             final Message message = getCurrentMessage();
-            if (message.what == CMD_START_AP) {
-                WifiConfiguration config = (WifiConfiguration) message.obj;
-
-                if (config == null) {
-                    /**
-                     * Configuration not provided in the command, fallback to use the current
-                     * configuration.
-                     */
-                    config = mWifiApConfigStore.getApConfiguration();
-                } else {
-                    /* Update AP configuration. */
-                    mWifiApConfigStore.setApConfiguration(config);
-                }
-
-                checkAndSetConnectivityInstance();
-                mSoftApManager = mFacade.makeSoftApManager(
-                        mContext, getHandler().getLooper(), mWifiNative, mNwService,
-                        mCm, mCountryCode.getCurrentCountryCode(),
-                        mWifiApConfigStore.getAllowed2GChannel(),
-                        new SoftApListener());
-                mSoftApManager.start(config);
-            } else {
+            if (message.what != CMD_START_AP) {
                 throw new RuntimeException("Illegal transition to SoftApState: " + message);
             }
+
+            IApInterface apInterface = setupDriverForSoftAp();
+            if (apInterface == null) {
+                setWifiApState(WIFI_AP_STATE_FAILED,
+                        WifiManager.SAP_START_FAILURE_GENERAL);
+                /**
+                 * Transition to InitialState to reset the
+                 * driver/HAL back to the initial state.
+                 */
+                transitionTo(mInitialState);
+                return;
+            }
+
+            WifiConfiguration config = (WifiConfiguration) message.obj;
+            if (config == null) {
+                /**
+                 * Configuration not provided in the command, fallback to use the current
+                 * configuration.
+                 */
+                config = mWifiApConfigStore.getApConfiguration();
+            } else {
+                /* Update AP configuration. */
+                mWifiApConfigStore.setApConfiguration(config);
+            }
+
+            checkAndSetConnectivityInstance();
+            mSoftApManager = mWifiInjector.makeSoftApManager(
+                    mWifiNative, mNwService,
+                    mCm, mCountryCode.getCurrentCountryCode(),
+                    mWifiApConfigStore.getAllowed2GChannel(),
+                    new SoftApListener(), apInterface);
+            mSoftApManager.start(config);
         }
 
         @Override
@@ -7745,7 +7579,7 @@
 
     /**
      * @param wifiCredentialEventType WIFI_CREDENTIAL_SAVED or WIFI_CREDENTIAL_FORGOT
-     * @param msg Must have a WifiConfiguration obj to succeed
+     * @param config Must have a WifiConfiguration object to succeed
      */
     private void broadcastWifiCredentialChanged(int wifiCredentialEventType,
             WifiConfiguration config) {
@@ -7808,54 +7642,6 @@
         return sb.toString();
     }
 
-    private static byte[] concat(byte[] array1, byte[] array2, byte[] array3) {
-
-        int len = array1.length + array2.length + array3.length;
-
-        if (array1.length != 0) {
-            len++;                      /* add another byte for size */
-        }
-
-        if (array2.length != 0) {
-            len++;                      /* add another byte for size */
-        }
-
-        if (array3.length != 0) {
-            len++;                      /* add another byte for size */
-        }
-
-        byte[] result = new byte[len];
-
-        int index = 0;
-        if (array1.length != 0) {
-            result[index] = (byte) (array1.length & 0xFF);
-            index++;
-            for (byte b : array1) {
-                result[index] = b;
-                index++;
-            }
-        }
-
-        if (array2.length != 0) {
-            result[index] = (byte) (array2.length & 0xFF);
-            index++;
-            for (byte b : array2) {
-                result[index] = b;
-                index++;
-            }
-        }
-
-        if (array3.length != 0) {
-            result[index] = (byte) (array3.length & 0xFF);
-            index++;
-            for (byte b : array3) {
-                result[index] = b;
-                index++;
-            }
-        }
-        return result;
-    }
-
     private static byte[] concatHex(byte[] array1, byte[] array2) {
 
         int len = array1.length + array2.length;
@@ -7901,13 +7687,13 @@
                     rand, android.util.Base64.NO_WRAP);
 
             // Try USIM first for authentication.
-            String tmResponse = tm.getIccAuthentication(tm.APPTYPE_USIM,
-                    tm.AUTHTYPE_EAP_SIM, base64Challenge);
+            String tmResponse = tm.getIccAuthentication(TelephonyManager.APPTYPE_USIM,
+                    TelephonyManager.AUTHTYPE_EAP_SIM, base64Challenge);
             if (tmResponse == null) {
                 /* Then, in case of failure, issue may be due to sim type, retry as a simple sim
                  */
-                tmResponse = tm.getIccAuthentication(tm.APPTYPE_SIM,
-                        tm.AUTHTYPE_EAP_SIM, base64Challenge);
+                tmResponse = tm.getIccAuthentication(TelephonyManager.APPTYPE_SIM,
+                        TelephonyManager.AUTHTYPE_EAP_SIM, base64Challenge);
             }
             logv("Raw Response - " + tmResponse);
 
@@ -8003,8 +7789,8 @@
             TelephonyManager tm = (TelephonyManager)
                     mContext.getSystemService(Context.TELEPHONY_SERVICE);
             if (tm != null) {
-                tmResponse = tm.getIccAuthentication(tm.APPTYPE_USIM,
-                        tm.AUTHTYPE_EAP_AKA, base64Challenge);
+                tmResponse = tm.getIccAuthentication(TelephonyManager.APPTYPE_USIM,
+                        TelephonyManager.AUTHTYPE_EAP_AKA, base64Challenge);
                 logv("Raw Response - " + tmResponse);
             } else {
                 loge("could not get telephony manager");
@@ -8144,7 +7930,7 @@
     }
 
     private static String getLinkPropertiesSummary(LinkProperties lp) {
-        List<String> attributes = new ArrayList(6);
+        List<String> attributes = new ArrayList<>(6);
         if (lp.hasIPv4Address()) {
             attributes.add("v4");
         }
@@ -8202,4 +7988,16 @@
         }
         return null;
     }
+
+    private void p2pSendMessage(int what) {
+        if (mWifiP2pChannel != null) {
+            mWifiP2pChannel.sendMessage(what);
+        }
+    }
+
+    private void p2pSendMessage(int what, int arg1) {
+        if (mWifiP2pChannel != null) {
+            mWifiP2pChannel.sendMessage(what, arg1);
+        }
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiConfigStore.java b/service/java/com/android/server/wifi/WifiSupplicantControl.java
similarity index 72%
rename from service/java/com/android/server/wifi/WifiConfigStore.java
rename to service/java/com/android/server/wifi/WifiSupplicantControl.java
index b693e23..d05f3ef 100644
--- a/service/java/com/android/server/wifi/WifiConfigStore.java
+++ b/service/java/com/android/server/wifi/WifiSupplicantControl.java
@@ -20,18 +20,12 @@
 import android.net.IpConfiguration.IpAssignment;
 import android.net.IpConfiguration.ProxySettings;
 import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiConfiguration.Status;
 import android.net.wifi.WifiEnterpriseConfig;
 import android.net.wifi.WifiSsid;
 import android.net.wifi.WpsInfo;
 import android.net.wifi.WpsResult;
 import android.os.FileObserver;
-import android.os.Process;
-import android.security.Credentials;
-import android.security.KeyChain;
-import android.security.KeyStore;
 import android.text.TextUtils;
-import android.util.ArraySet;
 import android.util.LocalLog;
 import android.util.Log;
 import android.util.SparseArray;
@@ -49,32 +43,18 @@
 import java.io.IOException;
 import java.net.URLDecoder;
 import java.nio.charset.StandardCharsets;
-import java.security.PrivateKey;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.BitSet;
 import java.util.Collection;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 /**
- * This class provides the API's to save/load/modify network configurations from a persistent
- * config database.
- * We use wpa_supplicant as our config database currently, but will be migrating to a different
- * one sometime in the future.
- * We use keystore for certificate/key management operations.
- *
- * NOTE: This class should only be used from WifiConfigManager!!!
+ * This class provides methods to send control commands to wpa_supplicant from WifiConfigManager.
+ * NOTE: This class should only be used from WifiConfigManager!
  */
-public class WifiConfigStore {
-
-    public static final String TAG = "WifiConfigStore";
+public class WifiSupplicantControl {
     // This is the only variable whose contents will not be interpreted by wpa_supplicant. We use it
     // to store metadata that allows us to correlate a wpa_supplicant.conf entry with additional
     // information about the same network stored in other files. The metadata is stored as a
@@ -85,40 +65,23 @@
     public static final String ID_STRING_KEY_CONFIG_KEY = "configKey";
     public static final String SUPPLICANT_CONFIG_FILE = "/data/misc/wifi/wpa_supplicant.conf";
     public static final String SUPPLICANT_CONFIG_FILE_BACKUP = SUPPLICANT_CONFIG_FILE + ".tmp";
-
     // Value stored by supplicant to requirePMF
     public static final int STORED_VALUE_FOR_REQUIRE_PMF = 2;
-
-    private static final boolean DBG = true;
-    private static boolean VDBG = false;
-
+    private static final String TAG = "WifiSupplicantControl";
     private final LocalLog mLocalLog;
     private final WpaConfigFileObserver mFileObserver;
     private final Context mContext;
     private final WifiNative mWifiNative;
-    private final KeyStore mKeyStore;
-    private final boolean mShowNetworks;
-    private final HashSet<String> mBssidBlacklist = new HashSet<String>();
 
-    private final BackupManagerProxy mBackupManagerProxy;
+    private boolean mVerboseLoggingEnabled = false;
 
-    WifiConfigStore(Context context, WifiNative wifiNative, KeyStore keyStore, LocalLog localLog,
-            boolean showNetworks, boolean verboseDebug) {
+    WifiSupplicantControl(Context context, WifiNative wifiNative, LocalLog localLog) {
         mContext = context;
         mWifiNative = wifiNative;
-        mKeyStore = keyStore;
-        mShowNetworks = showNetworks;
-        mBackupManagerProxy = new BackupManagerProxy();
 
-        if (mShowNetworks) {
-            mLocalLog = localLog;
-            mFileObserver = new WpaConfigFileObserver();
-            mFileObserver.startWatching();
-        } else {
-            mLocalLog = null;
-            mFileObserver = null;
-        }
-        VDBG = verboseDebug;
+        mLocalLog = localLog;
+        mFileObserver = new WpaConfigFileObserver();
+        mFileObserver.startWatching();
     }
 
     private static String removeDoubleQuotes(String string) {
@@ -136,7 +99,9 @@
      * Also transform the internal string format that uses _ (for bewildering
      * reasons) into a wpa_supplicant adjusted value, that uses - as a separator
      * (most of the time at least...).
-     * @param set a bit set with a one for each corresponding string to be included from strings.
+     *
+     * @param set     a bit set with a one for each corresponding string to be included from
+     *                strings.
      * @param strings the set of string literals to concatenate strinfs from.
      * @return A wpa_supplicant formatted value.
      */
@@ -146,8 +111,10 @@
 
     /**
      * Same as makeString with an exclusion parameter.
-     * @param set a bit set with a one for each corresponding string to be included from strings.
-     * @param strings the set of string literals to concatenate strinfs from.
+     *
+     * @param set       a bit set with a one for each corresponding string to be included from
+     *                  strings.
+     * @param strings   the set of string literals to concatenate strinfs from.
      * @param exception literal string to be excluded from the _ to - transformation.
      * @return A wpa_supplicant formatted value.
      */
@@ -159,9 +126,7 @@
         BitSet trimmedSet = set.get(0, strings.length);
 
         List<String> valueSet = new ArrayList<>();
-        for (int bit = trimmedSet.nextSetBit(0);
-             bit >= 0;
-             bit = trimmedSet.nextSetBit(bit+1)) {
+        for (int bit = trimmedSet.nextSetBit(0); bit >= 0; bit = trimmedSet.nextSetBit(bit + 1)) {
             String currentName = strings[bit];
             if (exception != null && currentName.equals(exception)) {
                 valueSet.add(currentName);
@@ -184,34 +149,6 @@
         return Utils.toHex(removeDoubleQuotes(str).getBytes(StandardCharsets.UTF_8));
     }
 
-    // Certificate and private key management for EnterpriseConfig
-    private static boolean needsKeyStore(WifiEnterpriseConfig config) {
-        return (!(config.getClientCertificate() == null && config.getCaCertificate() == null));
-    }
-
-    private static boolean isHardwareBackedKey(PrivateKey key) {
-        return KeyChain.isBoundKeyAlgorithm(key.getAlgorithm());
-    }
-
-    private static boolean hasHardwareBackedKey(Certificate certificate) {
-        return KeyChain.isBoundKeyAlgorithm(certificate.getPublicKey().getAlgorithm());
-    }
-
-    private static boolean needsSoftwareBackedKeyStore(WifiEnterpriseConfig config) {
-        java.lang.String client = config.getClientCertificateAlias();
-        if (!TextUtils.isEmpty(client)) {
-            // a valid client certificate is configured
-
-            // BUGBUG: keyStore.get() never returns certBytes; because it is not
-            // taking WIFI_UID as a parameter. It always looks for certificate
-            // with SYSTEM_UID, and never finds any Wifi certificates. Assuming that
-            // all certificates need software keystore until we get the get() API
-            // fixed.
-            return true;
-        }
-        return false;
-    }
-
     private int lookupString(String string, String[] strings) {
         int size = strings.length;
 
@@ -251,7 +188,7 @@
         if (config == null) {
             return;
         }
-        if (VDBG) localLog("readNetworkVariables: " + config.networkId);
+        if (mVerboseLoggingEnabled) localLog("readNetworkVariables: " + config.networkId);
         int netId = config.networkId;
         if (netId < 0) {
             return;
@@ -376,7 +313,7 @@
                 return lastPriority;
             }
             String[] lines = listStr.split("\n");
-            if (mShowNetworks) {
+            if (mVerboseLoggingEnabled) {
                 localLog("loadNetworks:  ");
                 for (String net : lines) {
                     localLog(net);
@@ -422,7 +359,7 @@
                 config.setIpAssignment(IpAssignment.DHCP);
                 config.setProxySettings(ProxySettings.NONE);
                 if (!WifiServiceImpl.isValid(config)) {
-                    if (mShowNetworks) {
+                    if (mVerboseLoggingEnabled) {
                         localLog("Ignoring network " + config.networkId + " because configuration "
                                 + "loaded from wpa_supplicant.conf is not valid.");
                     }
@@ -442,7 +379,7 @@
                 final WifiConfiguration duplicateConfig = configs.put(configKey, config);
                 if (duplicateConfig != null) {
                     // The network is already known. Overwrite the duplicate entry.
-                    if (mShowNetworks) {
+                    if (mVerboseLoggingEnabled) {
                         localLog("Replacing duplicate network " + duplicateConfig.networkId
                                 + " with " + config.networkId + ".");
                     }
@@ -458,132 +395,8 @@
     }
 
     /**
-     * Install keys for given enterprise network.
-     *
-     * @param existingConfig Existing config corresponding to the network already stored in our
-     *                       database. This maybe null if it's a new network.
-     * @param config         Config corresponding to the network.
-     * @return true if successful, false otherwise.
-     */
-    private boolean installKeys(WifiEnterpriseConfig existingConfig, WifiEnterpriseConfig config,
-            String name) {
-        boolean ret = true;
-        String privKeyName = Credentials.USER_PRIVATE_KEY + name;
-        String userCertName = Credentials.USER_CERTIFICATE + name;
-        if (config.getClientCertificate() != null) {
-            byte[] privKeyData = config.getClientPrivateKey().getEncoded();
-            if (DBG) {
-                if (isHardwareBackedKey(config.getClientPrivateKey())) {
-                    Log.d(TAG, "importing keys " + name + " in hardware backed store");
-                } else {
-                    Log.d(TAG, "importing keys " + name + " in software backed store");
-                }
-            }
-            ret = mKeyStore.importKey(privKeyName, privKeyData, Process.WIFI_UID,
-                    KeyStore.FLAG_NONE);
-
-            if (!ret) {
-                return ret;
-            }
-
-            ret = putCertInKeyStore(userCertName, config.getClientCertificate());
-            if (!ret) {
-                // Remove private key installed
-                mKeyStore.delete(privKeyName, Process.WIFI_UID);
-                return ret;
-            }
-        }
-
-        X509Certificate[] caCertificates = config.getCaCertificates();
-        Set<String> oldCaCertificatesToRemove = new ArraySet<String>();
-        if (existingConfig != null && existingConfig.getCaCertificateAliases() != null) {
-            oldCaCertificatesToRemove.addAll(
-                    Arrays.asList(existingConfig.getCaCertificateAliases()));
-        }
-        List<String> caCertificateAliases = null;
-        if (caCertificates != null) {
-            caCertificateAliases = new ArrayList<String>();
-            for (int i = 0; i < caCertificates.length; i++) {
-                String alias = caCertificates.length == 1 ? name
-                        : String.format("%s_%d", name, i);
-
-                oldCaCertificatesToRemove.remove(alias);
-                ret = putCertInKeyStore(Credentials.CA_CERTIFICATE + alias, caCertificates[i]);
-                if (!ret) {
-                    // Remove client key+cert
-                    if (config.getClientCertificate() != null) {
-                        mKeyStore.delete(privKeyName, Process.WIFI_UID);
-                        mKeyStore.delete(userCertName, Process.WIFI_UID);
-                    }
-                    // Remove added CA certs.
-                    for (String addedAlias : caCertificateAliases) {
-                        mKeyStore.delete(Credentials.CA_CERTIFICATE + addedAlias, Process.WIFI_UID);
-                    }
-                    return ret;
-                } else {
-                    caCertificateAliases.add(alias);
-                }
-            }
-        }
-        // Remove old CA certs.
-        for (String oldAlias : oldCaCertificatesToRemove) {
-            mKeyStore.delete(Credentials.CA_CERTIFICATE + oldAlias, Process.WIFI_UID);
-        }
-        // Set alias names
-        if (config.getClientCertificate() != null) {
-            config.setClientCertificateAlias(name);
-            config.resetClientKeyEntry();
-        }
-
-        if (caCertificates != null) {
-            config.setCaCertificateAliases(
-                    caCertificateAliases.toArray(new String[caCertificateAliases.size()]));
-            config.resetCaCertificate();
-        }
-        return ret;
-    }
-
-    private boolean putCertInKeyStore(String name, Certificate cert) {
-        try {
-            byte[] certData = Credentials.convertToPem(cert);
-            if (DBG) Log.d(TAG, "putting certificate " + name + " in keystore");
-            return mKeyStore.put(name, certData, Process.WIFI_UID, KeyStore.FLAG_NONE);
-
-        } catch (IOException e1) {
-            return false;
-        } catch (CertificateException e2) {
-            return false;
-        }
-    }
-
-    /**
-     * Remove enterprise keys from the network config.
-     *
-     * @param config Config corresponding to the network.
-     */
-    private void removeKeys(WifiEnterpriseConfig config) {
-        String client = config.getClientCertificateAlias();
-        // a valid client certificate is configured
-        if (!TextUtils.isEmpty(client)) {
-            if (DBG) Log.d(TAG, "removing client private key and user cert");
-            mKeyStore.delete(Credentials.USER_PRIVATE_KEY + client, Process.WIFI_UID);
-            mKeyStore.delete(Credentials.USER_CERTIFICATE + client, Process.WIFI_UID);
-        }
-
-        String[] aliases = config.getCaCertificateAliases();
-        // a valid ca certificate is configured
-        if (aliases != null) {
-            for (String ca : aliases) {
-                if (!TextUtils.isEmpty(ca)) {
-                    if (DBG) Log.d(TAG, "removing CA cert: " + ca);
-                    mKeyStore.delete(Credentials.CA_CERTIFICATE + ca, Process.WIFI_UID);
-                }
-            }
-        }
-    }
-
-    /**
      * Update the network metadata info stored in wpa_supplicant network extra field.
+     *
      * @param config Config corresponding to the network.
      * @return true if successful, false otherwise.
      */
@@ -612,7 +425,7 @@
         if (config == null) {
             return false;
         }
-        if (VDBG) localLog("saveNetwork: " + netId);
+        if (mVerboseLoggingEnabled) localLog("saveNetwork: " + netId);
         if (config.SSID != null && !mWifiNative.setNetworkVariable(
                 netId,
                 WifiConfiguration.ssidVarName,
@@ -750,53 +563,31 @@
     }
 
     /**
-     * Update/Install keys for given enterprise network.
+     * Save an enterprise network configuration to wpa_supplicant.
      *
-     * @param config         Config corresponding to the network.
-     * @param existingConfig Existing config corresponding to the network already stored in our
-     *                       database. This maybe null if it's a new network.
+     * @param config Config corresponding to the network.
      * @return true if successful, false otherwise.
      */
-    private boolean updateNetworkKeys(WifiConfiguration config, WifiConfiguration existingConfig) {
-        WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
-        if (needsKeyStore(enterpriseConfig)) {
-            try {
-                /* config passed may include only fields being updated.
-                 * In order to generate the key id, fetch uninitialized
-                 * fields from the currently tracked configuration
-                 */
-                String keyId = config.getKeyIdForCredentials(existingConfig);
-
-                if (!installKeys(existingConfig != null
-                        ? existingConfig.enterpriseConfig : null, enterpriseConfig, keyId)) {
-                    loge(config.SSID + ": failed to install keys");
-                    return false;
-                }
-            } catch (IllegalStateException e) {
-                loge(config.SSID + " invalid config for key installation: " + e.getMessage());
-                return false;
-            }
-        }
-        if (!enterpriseConfig.saveToSupplicant(
-                new SupplicantSaver(config.networkId, config.SSID))) {
-            removeKeys(enterpriseConfig);
+    public boolean saveEnterpriseConfiguration(WifiConfiguration config) {
+        if (config == null || config.enterpriseConfig == null) {
             return false;
         }
-        return true;
+        if (mVerboseLoggingEnabled) localLog("saveEnterpriseConfiguration: " + config.networkId);
+        return config.enterpriseConfig.saveToSupplicant(
+                new WifiSupplicantControl.SupplicantSaver(config.networkId, config.SSID));
     }
 
     /**
      * Add or update a network configuration to wpa_supplicant.
      *
      * @param config         Config corresponding to the network.
-     * @param existingConfig Existing config corresponding to the network saved in our database.
      * @return true if successful, false otherwise.
      */
-    public boolean addOrUpdateNetwork(WifiConfiguration config, WifiConfiguration existingConfig) {
+    public boolean addOrUpdateNetwork(WifiConfiguration config) {
         if (config == null) {
             return false;
         }
-        if (VDBG) localLog("addOrUpdateNetwork: " + config.networkId);
+        if (mVerboseLoggingEnabled) localLog("addOrUpdateNetwork: " + config.networkId);
         int netId = config.networkId;
         boolean newNetwork = false;
         /*
@@ -823,12 +614,6 @@
             }
             return false;
         }
-        if (config.enterpriseConfig != null
-                && config.enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE) {
-            return updateNetworkKeys(config, existingConfig);
-        }
-        // Stage the backup of the SettingsProvider package which backs this up
-        mBackupManagerProxy.notifyDataChanged();
         return true;
     }
 
@@ -842,17 +627,11 @@
         if (config == null) {
             return false;
         }
-        if (VDBG) localLog("removeNetwork: " + config.networkId);
+        if (mVerboseLoggingEnabled) localLog("removeNetwork: " + config.networkId);
         if (!mWifiNative.removeNetwork(config.networkId)) {
             loge("Remove network in wpa_supplicant failed on " + config.networkId);
             return false;
         }
-        // Remove any associated keys
-        if (config.enterpriseConfig != null) {
-            removeKeys(config.enterpriseConfig);
-        }
-        // Stage the backup of the SettingsProvider package which backs this up
-        mBackupManagerProxy.notifyDataChanged();
         return true;
     }
 
@@ -862,17 +641,15 @@
      * @param config Config corresponding to the network.
      * @return true if successful, false otherwise.
      */
-    public boolean selectNetwork(WifiConfiguration config, Collection<WifiConfiguration> configs) {
+    public boolean selectNetwork(WifiConfiguration config) {
         if (config == null) {
             return false;
         }
-        if (VDBG) localLog("selectNetwork: " + config.networkId);
+        if (mVerboseLoggingEnabled) localLog("selectNetwork: " + config.networkId);
         if (!mWifiNative.selectNetwork(config.networkId)) {
             loge("Select network in wpa_supplicant failed on " + config.networkId);
             return false;
         }
-        config.status = Status.ENABLED;
-        markAllNetworksDisabledExcept(config.networkId, configs);
         return true;
     }
 
@@ -886,12 +663,11 @@
         if (config == null) {
             return false;
         }
-        if (VDBG) localLog("disableNetwork: " + config.networkId);
+        if (mVerboseLoggingEnabled) localLog("disableNetwork: " + config.networkId);
         if (!mWifiNative.disableNetwork(config.networkId)) {
             loge("Disable network in wpa_supplicant failed on " + config.networkId);
             return false;
         }
-        config.status = Status.DISABLED;
         return true;
     }
 
@@ -905,7 +681,7 @@
         if (config == null) {
             return false;
         }
-        if (VDBG) localLog("setNetworkPriority: " + config.networkId);
+        if (mVerboseLoggingEnabled) localLog("setNetworkPriority: " + config.networkId);
         if (!mWifiNative.setNetworkVariable(config.networkId,
                 WifiConfiguration.priorityVarName, Integer.toString(priority))) {
             loge("Set priority of network in wpa_supplicant failed on " + config.networkId);
@@ -925,7 +701,7 @@
         if (config == null) {
             return false;
         }
-        if (VDBG) localLog("setNetworkSSID: " + config.networkId);
+        if (mVerboseLoggingEnabled) localLog("setNetworkSSID: " + config.networkId);
         if (!mWifiNative.setNetworkVariable(config.networkId, WifiConfiguration.ssidVarName,
                 encodeSSID(ssid))) {
             loge("Set SSID of network in wpa_supplicant failed on " + config.networkId);
@@ -949,7 +725,7 @@
                 && config.SSID == null)) {
             return false;
         }
-        if (VDBG) localLog("setNetworkBSSID: " + config.networkId);
+        if (mVerboseLoggingEnabled) localLog("setNetworkBSSID: " + config.networkId);
         if (!mWifiNative.setNetworkVariable(config.networkId, WifiConfiguration.bssidVarName,
                 bssid)) {
             loge("Set BSSID of network in wpa_supplicant failed on " + config.networkId);
@@ -960,6 +736,25 @@
     }
 
     /**
+     * Get BSSID for a network in wpa_supplicant.
+     *
+     * @param config Config corresponding to the network.
+     * @return BSSID for the network, if it exists, null otherwise.
+     */
+    public String getNetworkBSSID(WifiConfiguration config) {
+      // Sanity check the config is valid
+        if (config == null
+                || (config.networkId == WifiConfiguration.INVALID_NETWORK_ID
+                && config.SSID == null)) {
+            return null;
+        }
+        if (mVerboseLoggingEnabled) localLog("getNetworkBSSID: " + config.networkId);
+        String bssid =
+                mWifiNative.getNetworkVariable(config.networkId, WifiConfiguration.bssidVarName);
+        return (TextUtils.isEmpty(bssid) ? null : bssid);
+    }
+
+    /**
      * Enable/Disable HS20 parameter in wpa_supplicant.
      *
      * @param enable Enable/Disable the parameter.
@@ -975,7 +770,7 @@
      * @return true if successful, false otherwise.
      */
     public boolean disableAllNetworks(Collection<WifiConfiguration> configs) {
-        if (VDBG) localLog("disableAllNetworks");
+        if (mVerboseLoggingEnabled) localLog("disableAllNetworks");
         boolean networkDisabled = false;
         for (WifiConfiguration enabled : configs) {
             if (disableNetwork(enabled)) {
@@ -1006,16 +801,16 @@
             reader = new BufferedReader(new FileReader(SUPPLICANT_CONFIG_FILE));
             result = readNetworkVariablesFromReader(reader, key);
         } catch (FileNotFoundException e) {
-            if (VDBG) loge("Could not open " + SUPPLICANT_CONFIG_FILE + ", " + e);
+            if (mVerboseLoggingEnabled) loge("Could not open " + SUPPLICANT_CONFIG_FILE + ", " + e);
         } catch (IOException e) {
-            if (VDBG) loge("Could not read " + SUPPLICANT_CONFIG_FILE + ", " + e);
+            if (mVerboseLoggingEnabled) loge("Could not read " + SUPPLICANT_CONFIG_FILE + ", " + e);
         } finally {
             try {
                 if (reader != null) {
                     reader.close();
                 }
             } catch (IOException e) {
-                if (VDBG) {
+                if (mVerboseLoggingEnabled) {
                     loge("Could not close reader for " + SUPPLICANT_CONFIG_FILE + ", " + e);
                 }
             }
@@ -1028,13 +823,13 @@
      * readNetworkVariablesFromSupplicantFile() for testing.
      *
      * @param reader The reader to read the network variables from.
-     * @param key The parameter to be parsed.
+     * @param key    The parameter to be parsed.
      * @return Map of corresponding configKey to the value of the param requested.
      */
     public Map<String, String> readNetworkVariablesFromReader(BufferedReader reader, String key)
             throws IOException {
         Map<String, String> result = new HashMap<>();
-        if (VDBG) localLog("readNetworkVariablesFromReader key=" + key);
+        if (mVerboseLoggingEnabled) localLog("readNetworkVariablesFromReader key=" + key);
         boolean found = false;
         String configKey = null;
         String value = null;
@@ -1054,20 +849,19 @@
                     try {
                         // Trim the quotes wrapping the id_str value.
                         final String encodedExtras = trimmedLine.substring(
-                                8, trimmedLine.length() -1);
+                                8, trimmedLine.length() - 1);
                         final JSONObject json =
                                 new JSONObject(URLDecoder.decode(encodedExtras, "UTF-8"));
-                        if (json.has(WifiConfigStore.ID_STRING_KEY_CONFIG_KEY)) {
+                        if (json.has(ID_STRING_KEY_CONFIG_KEY)) {
                             final Object configKeyFromJson =
-                                    json.get(WifiConfigStore.ID_STRING_KEY_CONFIG_KEY);
+                                    json.get(ID_STRING_KEY_CONFIG_KEY);
                             if (configKeyFromJson instanceof String) {
                                 configKey = (String) configKeyFromJson;
                             }
                         }
                     } catch (JSONException e) {
-                        if (VDBG) {
-                            loge("Could not get "+ WifiConfigStore.ID_STRING_KEY_CONFIG_KEY
-                                    + ", " + e);
+                        if (mVerboseLoggingEnabled) {
+                            loge("Could not get " + ID_STRING_KEY_CONFIG_KEY + ", " + e);
                         }
                     }
                 }
@@ -1088,7 +882,7 @@
      * @param configs List of all the networks.
      */
     public void resetSimNetworks(Collection<WifiConfiguration> configs) {
-        if (VDBG) localLog("resetSimNetworks");
+        if (mVerboseLoggingEnabled) localLog("resetSimNetworks");
         for (WifiConfiguration config : configs) {
             if (TelephonyUtil.isSimConfig(config)) {
                 String currentIdentity = TelephonyUtil.getSimIdentity(mContext,
@@ -1115,11 +909,10 @@
     }
 
     /**
-     * Clear BSSID blacklist in wpa_supplicant.
+     * Clear BSSID blacklist in wpa_supplicant & HAL.
      */
     public void clearBssidBlacklist() {
-        if (VDBG) localLog("clearBlacklist");
-        mBssidBlacklist.clear();
+        if (mVerboseLoggingEnabled) localLog("clearBlacklist");
         mWifiNative.clearBlacklist();
         mWifiNative.setBssidBlacklist(null);
     }
@@ -1127,44 +920,15 @@
     /**
      * Add a BSSID to the blacklist.
      *
-     * @param bssid bssid to be added.
+     * @param bssid     to be added.
+     * @param bssidList entire BSSID list.
      */
-    public void blackListBssid(String bssid) {
-        if (bssid == null) {
-            return;
-        }
-        if (VDBG) localLog("blackListBssid: " + bssid);
-        mBssidBlacklist.add(bssid);
+    public void blackListBssid(String bssid, String[] bssidList) {
+        if (mVerboseLoggingEnabled) localLog("blackListBssid: " + bssid);
         // Blacklist at wpa_supplicant
         mWifiNative.addToBlacklist(bssid);
         // Blacklist at firmware
-        String[] list = mBssidBlacklist.toArray(new String[mBssidBlacklist.size()]);
-        mWifiNative.setBssidBlacklist(list);
-    }
-
-    /**
-     * Checks if the provided bssid is blacklisted or not.
-     *
-     * @param bssid bssid to be checked.
-     * @return true if present, false otherwise.
-     */
-    public boolean isBssidBlacklisted(String bssid) {
-        return mBssidBlacklist.contains(bssid);
-    }
-
-    /* Mark all networks except specified netId as disabled */
-    private void markAllNetworksDisabledExcept(int netId, Collection<WifiConfiguration> configs) {
-        for (WifiConfiguration config : configs) {
-            if (config != null && config.networkId != netId) {
-                if (config.status != Status.DISABLED) {
-                    config.status = Status.DISABLED;
-                }
-            }
-        }
-    }
-
-    private void markAllNetworksDisabled(Collection<WifiConfiguration> configs) {
-        markAllNetworksDisabledExcept(WifiConfiguration.INVALID_NETWORK_ID, configs);
+        mWifiNative.setBssidBlacklist(bssidList);
     }
 
     /**
@@ -1174,12 +938,9 @@
      * @param config WPS configuration
      * @return Wps result containing status and pin
      */
-    public WpsResult startWpsWithPinFromAccessPoint(WpsInfo config,
-            Collection<WifiConfiguration> configs) {
+    public WpsResult startWpsWithPinFromAccessPoint(WpsInfo config) {
         WpsResult result = new WpsResult();
         if (mWifiNative.startWpsRegistrar(config.BSSID, config.pin)) {
-            /* WPS leaves all networks disabled */
-            markAllNetworksDisabled(configs);
             result.status = WpsResult.Status.SUCCESS;
         } else {
             loge("Failed to start WPS pin method configuration");
@@ -1194,13 +955,10 @@
      *
      * @return WpsResult indicating status and pin
      */
-    public WpsResult startWpsWithPinFromDevice(WpsInfo config,
-            Collection<WifiConfiguration> configs) {
+    public WpsResult startWpsWithPinFromDevice(WpsInfo config) {
         WpsResult result = new WpsResult();
         result.pin = mWifiNative.startWpsPinDisplay(config.BSSID);
-        /* WPS leaves all networks disabled */
         if (!TextUtils.isEmpty(result.pin)) {
-            markAllNetworksDisabled(configs);
             result.status = WpsResult.Status.SUCCESS;
         } else {
             loge("Failed to start WPS pin method configuration");
@@ -1215,12 +973,9 @@
      * @param config WPS configuration
      * @return WpsResult indicating status and pin
      */
-    public WpsResult startWpsPbc(WpsInfo config,
-            Collection<WifiConfiguration> configs) {
+    public WpsResult startWpsPbc(WpsInfo config) {
         WpsResult result = new WpsResult();
         if (mWifiNative.startWpsPbc(config.BSSID)) {
-            /* WPS leaves all networks disabled */
-            markAllNetworksDisabled(configs);
             result.status = WpsResult.Status.SUCCESS;
         } else {
             loge("Failed to start WPS push button configuration");
@@ -1229,19 +984,19 @@
         return result;
     }
 
-    protected void logd(String s) {
+    private void logd(String s) {
         Log.d(TAG, s);
     }
 
-    protected void logi(String s) {
+    private void logi(String s) {
         Log.i(TAG, s);
     }
 
-    protected void loge(String s) {
+    private void loge(String s) {
         loge(s, false);
     }
 
-    protected void loge(String s, boolean stack) {
+    private void loge(String s, boolean stack) {
         if (stack) {
             Log.e(TAG, s + " stack:" + Thread.currentThread().getStackTrace()[2].getMethodName()
                     + " - " + Thread.currentThread().getStackTrace()[3].getMethodName()
@@ -1252,7 +1007,7 @@
         }
     }
 
-    protected void log(String s) {
+    private void log(String s) {
         Log.d(TAG, s);
     }
 
@@ -1267,6 +1022,13 @@
         Log.d(TAG, s);
     }
 
+    /**
+     * Enable verbose logging.
+     */
+    public void enableVerboseLogging(boolean verbose) {
+        mVerboseLoggingEnabled = verbose;
+    }
+
     private class SupplicantSaver implements WifiEnterpriseConfig.SupplicantSaver {
         private final int mNetId;
         private final String mSetterSSID;
@@ -1335,7 +1097,7 @@
         }
     }
 
-    // TODO(rpius): Remove this.
+    // TODO(rpius): Remove this (see b/27377614).
     private class WpaConfigFileObserver extends FileObserver {
 
         WpaConfigFileObserver() {
@@ -1346,7 +1108,9 @@
         public void onEvent(int event, String path) {
             if (event == CLOSE_WRITE) {
                 File file = new File(SUPPLICANT_CONFIG_FILE);
-                if (VDBG) localLog("wpa_supplicant.conf changed; new size = " + file.length());
+                if (mVerboseLoggingEnabled) {
+                    localLog("wpa_supplicant.conf changed; new size = " + file.length());
+                }
             }
         }
     }
diff --git a/service/java/com/android/server/wifi/WifiTrafficPoller.java b/service/java/com/android/server/wifi/WifiTrafficPoller.java
index 336e0d7..d05353b 100644
--- a/service/java/com/android/server/wifi/WifiTrafficPoller.java
+++ b/service/java/com/android/server/wifi/WifiTrafficPoller.java
@@ -41,9 +41,7 @@
 /* Polls for traffic stats and notifies the clients */
 final class WifiTrafficPoller {
 
-    private boolean DBG = false;
-    private boolean VDBG = false;
-
+    private static final boolean DBG = false;
     private static final String TAG = "WifiTrafficPoller";
     /**
      * Interval in milliseconds between polling for traffic
@@ -71,6 +69,8 @@
     private NetworkInfo mNetworkInfo;
     private final String mInterface;
 
+    private boolean mVerboseLoggingEnabled = false;
+
     WifiTrafficPoller(Context context, Looper looper, String iface) {
         mInterface = iface;
         mTrafficHandler = new TrafficHandler(looper);
@@ -110,10 +110,10 @@
     }
 
     void enableVerboseLogging(int verbose) {
-        if (verbose > 0 ) {
-            DBG = true;
+        if (verbose > 0) {
+            mVerboseLoggingEnabled = true;
         } else {
-            DBG = false;
+            mVerboseLoggingEnabled = false;
         }
     }
 
@@ -126,8 +126,8 @@
             switch (msg.what) {
                 case ENABLE_TRAFFIC_STATS_POLL:
                     mEnableTrafficStatsPoll = (msg.arg1 == 1);
-                    if (DBG) {
-                        Log.e(TAG, "ENABLE_TRAFFIC_STATS_POLL "
+                    if (mVerboseLoggingEnabled) {
+                        Log.d(TAG, "ENABLE_TRAFFIC_STATS_POLL "
                                 + mEnableTrafficStatsPoll + " Token "
                                 + Integer.toString(mTrafficStatsPollToken));
                     }
@@ -139,8 +139,8 @@
                     }
                     break;
                 case TRAFFIC_STATS_POLL:
-                    if (VDBG) {
-                        Log.e(TAG, "TRAFFIC_STATS_POLL "
+                    if (DBG) {
+                        Log.d(TAG, "TRAFFIC_STATS_POLL "
                                 + mEnableTrafficStatsPoll + " Token "
                                 + Integer.toString(mTrafficStatsPollToken)
                                 + " num clients " + mClients.size());
@@ -153,8 +153,8 @@
                     break;
                 case ADD_CLIENT:
                     mClients.add((Messenger) msg.obj);
-                    if (DBG) {
-                        Log.e(TAG, "ADD_CLIENT: "
+                    if (mVerboseLoggingEnabled) {
+                        Log.d(TAG, "ADD_CLIENT: "
                                 + Integer.toString(mClients.size()));
                     }
                     break;
@@ -187,8 +187,8 @@
         mTxPkts = TrafficStats.getTxPackets(mInterface);
         mRxPkts = TrafficStats.getRxPackets(mInterface);
 
-        if (VDBG) {
-            Log.e(TAG, " packet count Tx="
+        if (DBG) {
+            Log.d(TAG, " packet count Tx="
                     + Long.toString(mTxPkts)
                     + " Rx="
                     + Long.toString(mRxPkts));
@@ -206,7 +206,7 @@
 
             if (dataActivity != mDataActivity && mScreenOn.get()) {
                 mDataActivity = dataActivity;
-                if (DBG) {
+                if (mVerboseLoggingEnabled) {
                     Log.e(TAG, "notifying of data activity "
                             + Integer.toString(mDataActivity));
                 }
diff --git a/service/java/com/android/server/wifi/hotspot2/ANQPData.java b/service/java/com/android/server/wifi/hotspot2/ANQPData.java
index 164ea20..f3d67cc 100644
--- a/service/java/com/android/server/wifi/hotspot2/ANQPData.java
+++ b/service/java/com/android/server/wifi/hotspot2/ANQPData.java
@@ -46,7 +46,7 @@
         mClock = clock;
         mNetwork = network;
         mANQPElements = anqpElements != null ? new HashMap<>(anqpElements) : null;
-        mCtime = mClock.currentTimeMillis();
+        mCtime = mClock.getWallClockMillis();
         mRetry = 0;
         if (anqpElements == null) {
             mExpiry = mCtime + ANQP_HOLDOFF_TIME;
@@ -63,7 +63,7 @@
         mClock = clock;
         mNetwork = network;
         mANQPElements = null;
-        mCtime = mClock.currentTimeMillis();
+        mCtime = mClock.getWallClockMillis();
         if (existing == null) {
             mRetry = 0;
             mExpiry = mCtime + ANQP_HOLDOFF_TIME;
@@ -100,7 +100,7 @@
     }
 
     public boolean expired() {
-        return expired(mClock.currentTimeMillis());
+        return expired(mClock.getWallClockMillis());
     }
 
     public boolean expired(long at) {
@@ -120,7 +120,7 @@
     protected boolean isValid(NetworkDetail nwk) {
         return mANQPElements != null &&
                 nwk.getAnqpDomainID() == mNetwork.getAnqpDomainID() &&
-                mExpiry > mClock.currentTimeMillis();
+                mExpiry > mClock.getWallClockMillis();
     }
 
     private int getRetry() {
@@ -136,7 +136,7 @@
         else {
             sb.append(", ").append(mANQPElements.size()).append(" elements, ");
         }
-        long now = mClock.currentTimeMillis();
+        long now = mClock.getWallClockMillis();
         sb.append(Utils.toHMS(now-mCtime)).append(" old, expires in ").
                 append(Utils.toHMS(mExpiry-now)).append(' ');
         if (brief) {
diff --git a/service/java/com/android/server/wifi/hotspot2/AnqpCache.java b/service/java/com/android/server/wifi/hotspot2/AnqpCache.java
index a6cd42e..c655427 100644
--- a/service/java/com/android/server/wifi/hotspot2/AnqpCache.java
+++ b/service/java/com/android/server/wifi/hotspot2/AnqpCache.java
@@ -25,7 +25,7 @@
     public AnqpCache(Clock clock) {
         mClock = clock;
         mANQPCache = new HashMap<>();
-        mLastSweep = mClock.currentTimeMillis();
+        mLastSweep = mClock.getWallClockMillis();
     }
 
     private static class CacheKey {
@@ -172,7 +172,7 @@
 
     public void clear(boolean all, boolean debug) {
         if (DBG) Log.d(Utils.hs2LogTag(getClass()), "Clearing ANQP cache: all: " + all);
-        long now = mClock.currentTimeMillis();
+        long now = mClock.getWallClockMillis();
         synchronized (mANQPCache) {
             if (all) {
                 mANQPCache.clear();
@@ -197,7 +197,7 @@
     }
 
     public void dump(PrintWriter out) {
-        out.println("Last sweep " + Utils.toHMS(mClock.currentTimeMillis() - mLastSweep) + " ago.");
+        out.println("Last sweep " + Utils.toHMS(mClock.getWallClockMillis() - mLastSweep) + " ago.");
         for (ANQPData anqpData : mANQPCache.values()) {
             out.println(anqpData.toString(false));
         }
diff --git a/service/java/com/android/server/wifi/hotspot2/NetworkDetail.java b/service/java/com/android/server/wifi/hotspot2/NetworkDetail.java
index 402c0a8..5abf164 100644
--- a/service/java/com/android/server/wifi/hotspot2/NetworkDetail.java
+++ b/service/java/com/android/server/wifi/hotspot2/NetworkDetail.java
@@ -24,9 +24,7 @@
 
 public class NetworkDetail {
 
-    //turn off when SHIP
-    private static final boolean DBG = true;
-    private static final boolean VDBG = false;
+    private static final boolean DBG = false;
 
     private static final String TAG = "NetworkDetail:";
 
@@ -290,7 +288,7 @@
             mMaxRate = 0;
             Log.w("WifiMode", mSSID + ", Invalid SupportedRates!!!");
         }
-        if (VDBG) {
+        if (DBG) {
             Log.d(TAG, mSSID + "ChannelWidth is: " + mChannelWidth + " PrimaryFreq: " + mPrimaryFreq
                     + " mCenterfreq0: " + mCenterfreq0 + " mCenterfreq1: " + mCenterfreq1
                     + (extendedCapabilities.is80211McRTTResponder ? "Support RTT reponder"
diff --git a/service/java/com/android/server/wifi/hotspot2/SupplicantBridge.java b/service/java/com/android/server/wifi/hotspot2/SupplicantBridge.java
index af2befb..9c35645 100644
--- a/service/java/com/android/server/wifi/hotspot2/SupplicantBridge.java
+++ b/service/java/com/android/server/wifi/hotspot2/SupplicantBridge.java
@@ -179,18 +179,25 @@
         }
 
         if (scanDetail == null) {
+            // Icon queries are not held on the request map, so a null scanDetail is very likely
+            // the result of an Icon query. Notify the OSU app if the query was unsuccessful,
+            // else bail out.
             if (!success) {
                 mCallbacks.notifyIconFailed(bssid);
             }
             return;
+        } else if (!success) {
+            // If there is an associated ScanDetail, notify of a failed regular ANQP query.
+            mCallbacks.notifyANQPResponse(scanDetail, null);
+            return;
         }
 
         String bssData = mSupplicantHook.scanResult(scanDetail.getBSSIDString());
+        Map<Constants.ANQPElementType, ANQPElement> elements = null;
         try {
-            Map<Constants.ANQPElementType, ANQPElement> elements = parseWPSData(bssData);
-            Log.d(Utils.hs2LogTag(getClass()), String.format("%s ANQP response for %012x: %s",
-                    success ? "successful" : "failed", bssid, elements));
-            mCallbacks.notifyANQPResponse(scanDetail, success ? elements : null);
+            elements = parseWPSData(bssData);
+            Log.d(Utils.hs2LogTag(getClass()),
+                    String.format("Successful ANQP response for %012x: %s", bssid, elements));
         }
         catch (IOException ioe) {
             Log.e(Utils.hs2LogTag(getClass()), "Failed to parse ANQP: " +
@@ -200,7 +207,7 @@
             Log.e(Utils.hs2LogTag(getClass()), "Failed to parse ANQP: " +
                     rte.toString() + ": " + bssData, rte);
         }
-        mCallbacks.notifyANQPResponse(scanDetail, null);
+        mCallbacks.notifyANQPResponse(scanDetail, elements);
     }
 
     private static String escapeSSID(NetworkDetail networkDetail) {
diff --git a/service/java/com/android/server/wifi/nan/WifiNanClientState.java b/service/java/com/android/server/wifi/nan/WifiNanClientState.java
index 0707270..74fa71d 100644
--- a/service/java/com/android/server/wifi/nan/WifiNanClientState.java
+++ b/service/java/com/android/server/wifi/nan/WifiNanClientState.java
@@ -16,17 +16,26 @@
 
 package com.android.server.wifi.nan;
 
+import android.net.wifi.RttManager;
 import android.net.wifi.nan.ConfigRequest;
-import android.net.wifi.nan.IWifiNanEventListener;
-import android.net.wifi.nan.IWifiNanSessionListener;
-import android.net.wifi.nan.WifiNanEventListener;
+import android.net.wifi.nan.IWifiNanEventCallback;
 import android.os.RemoteException;
 import android.util.Log;
 import android.util.SparseArray;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.Arrays;
 
+/**
+ * Manages the service-side NAN state of an individual "client". A client
+ * corresponds to a single instantiation of the WifiNanManager - there could be
+ * multiple ones per UID/process (each of which is a separate client with its
+ * own session namespace). The client state is primarily: (1) callback (a
+ * singleton per client) through which NAN-wide events are called, and (2) a set
+ * of discovery sessions (publish and/or subscribe) which are created through
+ * this client and whose lifetime is tied to the lifetime of the client.
+ */
 public class WifiNanClientState {
     private static final String TAG = "WifiNanClientState";
     private static final boolean DBG = false;
@@ -35,40 +44,56 @@
     /* package */ static final int CLUSTER_CHANGE_EVENT_STARTED = 0;
     /* package */ static final int CLUSTER_CHANGE_EVENT_JOINED = 1;
 
-    private IWifiNanEventListener mListener;
-    private int mEvents;
+    private IWifiNanEventCallback mCallback;
     private final SparseArray<WifiNanSessionState> mSessions = new SparseArray<>();
 
-    private int mUid;
+    private int mClientId;
     private ConfigRequest mConfigRequest;
+    private int mUid;
 
-    public WifiNanClientState(int uid, IWifiNanEventListener listener, int events) {
+    private static final byte[] ALL_ZERO_MAC = new byte[] {0, 0, 0, 0, 0, 0};
+    private byte[] mLastDiscoveryInterfaceMac = ALL_ZERO_MAC;
+
+    public WifiNanClientState(int clientId, int uid, IWifiNanEventCallback callback,
+            ConfigRequest configRequest) {
+        mClientId = clientId;
         mUid = uid;
-        mListener = listener;
-        mEvents = events;
+        mCallback = callback;
+        mConfigRequest = configRequest;
     }
 
+    /**
+     * Destroy the current client - corresponds to a disconnect() request from
+     * the client. Destroys all discovery sessions belonging to this client.
+     */
     public void destroy() {
-        mListener = null;
         for (int i = 0; i < mSessions.size(); ++i) {
-            mSessions.valueAt(i).destroy();
+            mSessions.valueAt(i).terminate();
         }
         mSessions.clear();
         mConfigRequest = null;
     }
 
-    public void setConfigRequest(ConfigRequest configRequest) {
-        mConfigRequest = configRequest;
-    }
-
     public ConfigRequest getConfigRequest() {
         return mConfigRequest;
     }
 
+    public int getClientId() {
+        return mClientId;
+    }
+
     public int getUid() {
         return mUid;
     }
 
+    /**
+     * Searches the discovery sessions of this client and returns the one
+     * corresponding to the publish/subscribe ID. Used on callbacks from HAL to
+     * map callbacks to the correct discovery session.
+     *
+     * @param pubSubId The publish/subscribe match session ID.
+     * @return NAN session corresponding to the requested ID.
+     */
     public WifiNanSessionState getNanSessionStateForPubSubId(int pubSubId) {
         for (int i = 0; i < mSessions.size(); ++i) {
             WifiNanSessionState session = mSessions.valueAt(i);
@@ -80,98 +105,159 @@
         return null;
     }
 
-    public void createSession(int sessionId, IWifiNanSessionListener listener, int events) {
-        WifiNanSessionState session = mSessions.get(sessionId);
-        if (session != null) {
-            Log.e(TAG, "createSession: sessionId already exists (replaced) - " + sessionId);
+    /**
+     * Add the session to the client database.
+     *
+     * @param session Session to be added.
+     */
+    public void addSession(WifiNanSessionState session) {
+        int sessionId = session.getSessionId();
+        if (mSessions.get(sessionId) != null) {
+            Log.w(TAG, "createSession: sessionId already exists (replaced) - " + sessionId);
         }
 
-        mSessions.put(sessionId, new WifiNanSessionState(sessionId, listener, events));
+        mSessions.put(sessionId, session);
     }
 
-    public void destroySession(int sessionId) {
-        WifiNanSessionState session = mSessions.get(sessionId);
-        if (session == null) {
-            Log.e(TAG, "destroySession: sessionId doesn't exist - " + sessionId);
+    /**
+     * Remove the specified session from the client database - without doing a
+     * terminate on the session. The assumption is that it is already
+     * terminated.
+     *
+     * @param sessionId The session ID of the session to be removed.
+     */
+    public void removeSession(int sessionId) {
+        if (mSessions.get(sessionId) == null) {
+            Log.e(TAG, "removeSession: sessionId doesn't exist - " + sessionId);
             return;
         }
 
         mSessions.delete(sessionId);
-        session.destroy();
     }
 
+    /**
+     * Destroy the discovery session: terminates discovery and frees up
+     * resources.
+     *
+     * @param sessionId The session ID of the session to be destroyed.
+     */
+    public void terminateSession(int sessionId) {
+        WifiNanSessionState session = mSessions.get(sessionId);
+        if (session == null) {
+            Log.e(TAG, "terminateSession: sessionId doesn't exist - " + sessionId);
+            return;
+        }
+
+        session.terminate();
+        mSessions.delete(sessionId);
+    }
+
+    /**
+     * Retrieve a session.
+     *
+     * @param sessionId Session ID of the session to be retrieved.
+     * @return Session or null if there's no session corresponding to the
+     *         sessionId.
+     */
     public WifiNanSessionState getSession(int sessionId) {
         return mSessions.get(sessionId);
     }
 
-    public void onConfigCompleted(ConfigRequest completedConfig) {
-        if (mListener != null && (mEvents & WifiNanEventListener.LISTEN_CONFIG_COMPLETED) != 0) {
+    /**
+     * Called to dispatch the NAN interface address change to the client - as an
+     * identity change (interface address information not propagated to client -
+     * privacy concerns).
+     *
+     * @param mac The new MAC address of the discovery interface - optionally propagated to the
+     *            client.
+     */
+    public void onInterfaceAddressChange(byte[] mac) {
+        if (mConfigRequest.mEnableIdentityChangeCallback && !Arrays.equals(mac,
+                mLastDiscoveryInterfaceMac)) {
             try {
-                mListener.onConfigCompleted(completedConfig);
-            } catch (RemoteException e) {
-                Log.w(TAG, "onConfigCompleted: RemoteException - ignored: " + e);
-            }
-        }
-    }
-
-    public void onConfigFailed(ConfigRequest failedConfig, int reason) {
-        if (mListener != null && (mEvents & WifiNanEventListener.LISTEN_CONFIG_FAILED) != 0) {
-            try {
-                mListener.onConfigFailed(failedConfig, reason);
-            } catch (RemoteException e) {
-                Log.w(TAG, "onConfigFailed: RemoteException - ignored: " + e);
-            }
-        }
-    }
-
-    public int onNanDown(int reason) {
-        if (mListener != null && (mEvents & WifiNanEventListener.LISTEN_NAN_DOWN) != 0) {
-            try {
-                mListener.onNanDown(reason);
-            } catch (RemoteException e) {
-                Log.w(TAG, "onNanDown: RemoteException - ignored: " + e);
-            }
-
-            return 1;
-        }
-
-        return 0;
-    }
-
-    public int onInterfaceAddressChange(byte[] mac) {
-        if (mListener != null && (mEvents & WifiNanEventListener.LISTEN_IDENTITY_CHANGED) != 0) {
-            try {
-                mListener.onIdentityChanged();
+                // TODO: b/30000323 - resolve privacy concerns
+                mCallback.onIdentityChanged(mac);
             } catch (RemoteException e) {
                 Log.w(TAG, "onIdentityChanged: RemoteException - ignored: " + e);
             }
-
-            return 1;
         }
 
-        return 0;
+        mLastDiscoveryInterfaceMac = mac;
     }
 
-    public int onClusterChange(int flag, byte[] mac) {
-        if (mListener != null && (mEvents & WifiNanEventListener.LISTEN_IDENTITY_CHANGED) != 0) {
+    /**
+     * Called to dispatch the NAN cluster change (due to joining of a new
+     * cluster or starting a cluster) to the client - as an identity change
+     * (interface address information not propagated to client - privacy
+     * concerns). Dispatched if the client registered for the identity changed
+     * event.
+     *
+     * @param mac The cluster ID of the cluster started or joined.
+     * @param currentDiscoveryInterfaceMac The MAC address of the discovery interface.
+     */
+    public void onClusterChange(int flag, byte[] mac, byte[] currentDiscoveryInterfaceMac) {
+        if (mConfigRequest.mEnableIdentityChangeCallback && !Arrays.equals(
+                currentDiscoveryInterfaceMac, mLastDiscoveryInterfaceMac)) {
             try {
-                mListener.onIdentityChanged();
+                // TODO: b/30000323 - resolve privacy concerns
+                mCallback.onIdentityChanged(currentDiscoveryInterfaceMac);
             } catch (RemoteException e) {
                 Log.w(TAG, "onIdentityChanged: RemoteException - ignored: " + e);
             }
-
-            return 1;
         }
 
-        return 0;
+        mLastDiscoveryInterfaceMac = currentDiscoveryInterfaceMac;
     }
 
+    /**
+     * Called on RTT success - forwards call to client.
+     */
+    public void onRangingSuccess(int rangingId, RttManager.ParcelableRttResults results) {
+        if (VDBG) {
+            Log.v(TAG, "onRangingSuccess: rangingId=" + rangingId + ", results=" + results);
+        }
+        try {
+            mCallback.onRangingSuccess(rangingId, results);
+        } catch (RemoteException e) {
+            Log.w(TAG, "onRangingSuccess: RemoteException - ignored: " + e);
+        }
+    }
+
+    /**
+     * Called on RTT failure - forwards call to client.
+     */
+    public void onRangingFailure(int rangingId, int reason, String description) {
+        if (VDBG) {
+            Log.v(TAG, "onRangingSuccess: rangingId=" + rangingId + ", reason=" + reason
+                    + ", description=" + description);
+        }
+        try {
+            mCallback.onRangingFailure(rangingId, reason, description);
+        } catch (RemoteException e) {
+            Log.w(TAG, "onRangingFailure: RemoteException - ignored: " + e);
+        }
+    }
+
+    /**
+     * Called on RTT operation aborted - forwards call to client.
+     */
+    public void onRangingAborted(int rangingId) {
+        if (VDBG) Log.v(TAG, "onRangingSuccess: rangingId=" + rangingId);
+        try {
+            mCallback.onRangingAborted(rangingId);
+        } catch (RemoteException e) {
+            Log.w(TAG, "onRangingAborted: RemoteException - ignored: " + e);
+        }
+    }
+
+    /**
+     * Dump the internal state of the class.
+     */
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("NanClientState:");
-        pw.println("  mUid: " + mUid);
+        pw.println("  mClientId: " + mClientId);
         pw.println("  mConfigRequest: " + mConfigRequest);
-        pw.println("  mListener: " + mListener);
-        pw.println("  mEvents: " + mEvents);
+        pw.println("  mCallback: " + mCallback);
         pw.println("  mSessions: [" + mSessions + "]");
         for (int i = 0; i < mSessions.size(); ++i) {
             mSessions.valueAt(i).dump(fd, pw, args);
diff --git a/service/java/com/android/server/wifi/nan/WifiNanDataPathStateManager.java b/service/java/com/android/server/wifi/nan/WifiNanDataPathStateManager.java
new file mode 100644
index 0000000..c3a9b9b
--- /dev/null
+++ b/service/java/com/android/server/wifi/nan/WifiNanDataPathStateManager.java
@@ -0,0 +1,1002 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.nan;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.NetworkAgent;
+import android.net.NetworkCapabilities;
+import android.net.NetworkFactory;
+import android.net.NetworkInfo;
+import android.net.NetworkRequest;
+import android.net.RouteInfo;
+import android.net.UidRange;
+import android.net.wifi.nan.WifiNanManager;
+import android.os.IBinder;
+import android.os.INetworkManagementService;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import libcore.util.HexEncoding;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Manages NAN data-path lifetime: interface creation/deletion, data-path setup and tear-down.
+ * The NAN network configuration is:
+ * - transport = TRANSPORT_WIFI_NAN
+ * - capabilities = NET_CAPABILITY_NOT_VPN
+ * - network specifier generated by WifiNanSession.createNetworkSpecifier(...) or
+ *   WifiNanManager.createNetworkSpecifier(...).
+ *   The network specifier is encoded as a JSON string with the following key combos:
+ *     TYPE_1A: type, role, client_id, session_id, peer_id, token
+ *     TYPE_1B: type, role, client_id, session_id, peer_id [only permitted for RESPONDER]
+ *     TYPE_2A: type, role, client_id, peer_mac, token
+ *     TYPE_2B: type, role, client_id, peer_mac [only permitted for RESPONDER]
+ *     TYPE_2C: type, role, client_id, token [only permitted for RESPONDER]
+ *     TYPE_2D: type, role, client_id [only permitted for RESPONDER]
+ */
+public class WifiNanDataPathStateManager {
+    private static final String TAG = "WifiNanDataPathStateMgr";
+
+    private static final boolean DBG = false;
+    private static final boolean VDBG = false; // STOPSHIP if true
+
+    private static final String NAN_INTERFACE_PREFIX = "nan";
+    private static final String NETWORK_TAG = "WIFI_NAN_FACTORY";
+    private static final String AGENT_TAG_PREFIX = "WIFI_NAN_AGENT_";
+    private static final int NETWORK_FACTORY_SCORE_AVAIL = 1;
+    private static final int NETWORK_FACTORY_BANDWIDTH_AVAIL = 1;
+    private static final int NETWORK_FACTORY_SIGNAL_STRENGTH_AVAIL = 1;
+
+    private final WifiNanStateManager mMgr;
+    private final NetworkInterfaceWrapper mNiWrapper = new NetworkInterfaceWrapper();
+    private final NetworkCapabilities mNetworkCapabilitiesFilter = new NetworkCapabilities();
+    private final Set<String> mInterfaces = new HashSet<>();
+    private final Map<String, NanNetworkRequestInformation> mNetworkRequestsCache = new HashMap<>();
+    private Context mContext;
+    private Looper mLooper;
+    private WifiNanNetworkFactory mNetworkFactory;
+    private INetworkManagementService mNwService;
+
+    public WifiNanDataPathStateManager(WifiNanStateManager mgr) {
+        mMgr = mgr;
+    }
+
+    /**
+     * Initialize the NAN data-path state manager. Specifically register the network factory with
+     * connectivity service.
+     */
+    public void start(Context context, Looper looper) {
+        if (VDBG) Log.v(TAG, "start");
+
+        mContext = context;
+        mLooper = looper;
+
+        mNetworkCapabilitiesFilter.clearAll();
+        mNetworkCapabilitiesFilter.addTransportType(NetworkCapabilities.TRANSPORT_WIFI_NAN);
+        mNetworkCapabilitiesFilter
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED);
+        mNetworkCapabilitiesFilter
+                .setNetworkSpecifier(NetworkCapabilities.MATCH_ALL_REQUESTS_NETWORK_SPECIFIER);
+        mNetworkCapabilitiesFilter.setLinkUpstreamBandwidthKbps(NETWORK_FACTORY_BANDWIDTH_AVAIL);
+        mNetworkCapabilitiesFilter.setLinkDownstreamBandwidthKbps(NETWORK_FACTORY_BANDWIDTH_AVAIL);
+        mNetworkCapabilitiesFilter.setSignalStrength(NETWORK_FACTORY_SIGNAL_STRENGTH_AVAIL);
+
+        mNetworkFactory = new WifiNanNetworkFactory(looper, context, mNetworkCapabilitiesFilter);
+        mNetworkFactory.setScoreFilter(NETWORK_FACTORY_SCORE_AVAIL);
+        mNetworkFactory.register();
+
+        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+        mNwService = INetworkManagementService.Stub.asInterface(b);
+    }
+
+    private Map.Entry<String, NanNetworkRequestInformation> getNetworkRequestByNdpId(int ndpId) {
+        for (Map.Entry<String, NanNetworkRequestInformation> entry : mNetworkRequestsCache
+                .entrySet()) {
+            if (entry.getValue().ndpId == ndpId) {
+                return entry;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Create all NAN data-path interfaces which are possible on the device - based on the
+     * capabilities of the firmware.
+     */
+    public void createAllInterfaces() {
+        if (VDBG) Log.v(TAG, "createAllInterfaces");
+
+        if (mMgr.mCapabilities == null) {
+            Log.e(TAG, "createAllInterfaces: capabilities aren't initialized yet!");
+            return;
+        }
+
+        for (int i = 0; i < mMgr.mCapabilities.maxNdiInterfaces; ++i) {
+            String name = NAN_INTERFACE_PREFIX + i;
+            if (mInterfaces.contains(name)) {
+                Log.e(TAG, "createAllInterfaces(): interface already up, " + name
+                        + ", possibly failed to delete - deleting/creating again to be safe");
+                mMgr.deleteDataPathInterface(name);
+
+                // critical to remove so that don't get infinite loop if the delete fails again
+                mInterfaces.remove(name);
+            }
+
+            mMgr.createDataPathInterface(name);
+        }
+    }
+
+    /**
+     * Delete all NAN data-path interfaces which are currently up.
+     */
+    public void deleteAllInterfaces() {
+        if (VDBG) Log.v(TAG, "deleteAllInterfaces");
+
+        for (String name : mInterfaces) {
+            mMgr.deleteDataPathInterface(name);
+        }
+    }
+
+    /**
+     * Called when firmware indicates the an interface was created.
+     */
+    public void onInterfaceCreated(String interfaceName) {
+        if (VDBG) Log.v(TAG, "onInterfaceCreated: interfaceName=" + interfaceName);
+
+        if (mInterfaces.contains(interfaceName)) {
+            Log.w(TAG, "onInterfaceCreated: already contains interface -- " + interfaceName);
+        }
+
+        mInterfaces.add(interfaceName);
+    }
+
+    /**
+     * Called when firmware indicates the an interface was deleted.
+     */
+    public void onInterfaceDeleted(String interfaceName) {
+        if (VDBG) Log.v(TAG, "onInterfaceDeleted: interfaceName=" + interfaceName);
+
+        if (!mInterfaces.contains(interfaceName)) {
+            Log.w(TAG, "onInterfaceDeleted: interface not on list -- " + interfaceName);
+        }
+
+        mInterfaces.remove(interfaceName);
+    }
+
+    /**
+     * Response to initiating data-path request. Indicates that request is successful (not
+     * complete!) and is now in progress.
+     *
+     * @param networkSpecifier The network specifier provided as part of the initiate request.
+     * @param ndpId            The ID assigned to the data-path.
+     */
+    public void onDataPathInitiateSuccess(String networkSpecifier, int ndpId) {
+        if (VDBG) {
+            Log.v(TAG,
+                    "onDataPathInitiateSuccess: networkSpecifier=" + networkSpecifier + ", ndpId="
+                            + ndpId);
+        }
+
+        NanNetworkRequestInformation nnri = mNetworkRequestsCache.get(networkSpecifier);
+        if (nnri == null) {
+            Log.w(TAG, "onDataPathInitiateSuccess: network request not found for networkSpecifier="
+                    + networkSpecifier);
+            mMgr.endDataPath(ndpId);
+            return;
+        }
+
+        if (nnri.state != NanNetworkRequestInformation.STATE_INITIATOR_WAIT_FOR_REQUEST_RESPONSE) {
+            Log.w(TAG, "onDataPathInitiateSuccess: network request in incorrect state: state="
+                    + nnri.state);
+            mNetworkRequestsCache.remove(networkSpecifier);
+            mMgr.endDataPath(ndpId);
+            return;
+        }
+
+        nnri.state = NanNetworkRequestInformation.STATE_INITIATOR_WAIT_FOR_CONFIRM;
+        nnri.ndpId = ndpId;
+    }
+
+    /**
+     * Response to an attempt to set up a data-path (on the initiator side).
+     *
+     * @param networkSpecifier The network specifier provided as part of the initiate request.
+     * @param reason           Failure reason.
+     */
+    public void onDataPathInitiateFail(String networkSpecifier, int reason) {
+        if (VDBG) {
+            Log.v(TAG,
+                    "onDataPathInitiateFail: networkSpecifier=" + networkSpecifier + ", reason="
+                            + reason);
+        }
+
+        NanNetworkRequestInformation nnri = mNetworkRequestsCache.remove(networkSpecifier);
+        if (nnri == null) {
+            Log.w(TAG, "onDataPathInitiateFail: network request not found for networkSpecifier="
+                    + networkSpecifier);
+            return;
+        }
+
+        if (nnri.state != NanNetworkRequestInformation.STATE_INITIATOR_WAIT_FOR_REQUEST_RESPONSE) {
+            Log.w(TAG, "onDataPathInitiateFail: network request in incorrect state: state="
+                    + nnri.state);
+        }
+
+        mNetworkRequestsCache.remove(networkSpecifier);
+    }
+
+
+    /**
+     * Notification (unsolicited/asynchronous) that a peer has requested to set up a data-path
+     * connection with us.
+     *
+     * @param pubSubId      The ID of the discovery session context for the data-path - or 0 if not
+     *                      related to a discovery session.
+     * @param mac           The discovery MAC address of the peer.
+     * @param ndpId         The locally assigned ID for the data-path.
+     * @param message       A message sent from the peer as part of the data-path setup process.
+     * @param messageLength The length of the {@code message}.
+     * @return The network specifier of the data-path (or null if none/error)
+     */
+    public String onDataPathRequest(int pubSubId, byte[] mac, int ndpId, byte[] message, int
+            messageLength) {
+        if (VDBG) {
+            Log.v(TAG,
+                    "onDataPathRequest: pubSubId=" + pubSubId + ", mac=" + String.valueOf(
+                            HexEncoding.encode(mac)) + ", ndpId=" + ndpId + ", messageLength="
+                            + messageLength);
+        }
+
+        String networkSpecifier = null;
+        NanNetworkRequestInformation nnri = null;
+        for (Map.Entry<String, NanNetworkRequestInformation> entry : mNetworkRequestsCache
+                .entrySet()) {
+            /*
+             * Checking that the incoming request (from the Initiator) matches the request
+             * we (the Responder) already have set up. The rules are:
+             * - The discovery session (pub/sub ID) must match.
+             * - The token (if specified - i.e. non-null) must match. A null token == accept any
+             *   Initiator token.
+             * - The peer MAC address (if specified - i.e. non-null) must match. A null peer MAC ==
+             *   accept (otherwise matching) requests from any peer MAC.
+             */
+            if (entry.getValue().pubSubId != 0 && entry.getValue().pubSubId != pubSubId) {
+                continue;
+            }
+
+            if (entry.getValue().token != null && !Arrays.equals(entry.getValue().token,
+                    Arrays.copyOfRange(message, 0, messageLength))) {
+                continue;
+            }
+
+            if (entry.getValue().peerDiscoveryMac != null && !Arrays.equals(
+                    entry.getValue().peerDiscoveryMac, mac)) {
+                continue;
+            }
+
+            networkSpecifier = entry.getKey();
+            nnri = entry.getValue();
+            break;
+        }
+
+        if (nnri == null) {
+            Log.w(TAG, "onDataPathRequest: can't find a request with specified pubSubId=" + pubSubId
+                    + ", mac=" + String.valueOf(HexEncoding.encode(mac)) + ", message=" + message
+                    + ", messageLength=" + messageLength);
+            if (DBG) {
+                Log.d(TAG, "onDataPathRequest: network request cache = " + mNetworkRequestsCache);
+            }
+            mMgr.respondToDataPathRequest(false, ndpId, "", "");
+            // TODO: validate respond with REJECT (when respond has a meaning)
+            return null;
+        }
+
+        if (nnri.state != NanNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_REQUEST) {
+            Log.w(TAG, "onDataPathRequest: request " + networkSpecifier + " is incorrect state="
+                    + nnri.state);
+            mMgr.respondToDataPathRequest(false, ndpId, "", "");
+            // TODO: validate respond with REJECT (when respond has a meaning)
+            mNetworkRequestsCache.remove(networkSpecifier);
+            return null;
+        }
+
+        nnri.state = NanNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_CONFIRM;
+        nnri.ndpId = ndpId;
+        nnri.interfaceName = selectInterfaceForRequest(nnri);
+        mMgr.respondToDataPathRequest(true, ndpId, nnri.interfaceName, "");
+
+        return networkSpecifier;
+        // TODO: validate respond with ACCEPT (when respond has a meaning)
+    }
+
+    /**
+     * Notification (unsolicited/asynchronous) that the data-path (which we've been setting up)
+     * is possibly (if {@code accept} is {@code true}) ready for use from the firmware's
+     * perspective - now can do L3 configuration.
+     *
+     * @param ndpId         Id of the data-path
+     * @param mac           The MAC address of the peer's data-path (not discovery interface). Only
+     *                      valid
+     *                      if {@code accept} is {@code true}.
+     * @param accept        Indicates whether the data-path setup has succeeded (been accepted) or
+     *                      failed (been rejected).
+     * @param reason        If {@code accept} is {@code false} provides a reason code for the
+     *                      rejection/failure.
+     * @param message       The message provided by the peer as part of the data-path setup
+     *                      process.
+     * @param messageLength Length of the {@code message}.
+     * @return The network specifier of the data-path or a null if none/error.
+     */
+    public String onDataPathConfirm(int ndpId, byte[] mac, boolean accept, int reason,
+            byte[] message, int messageLength) {
+        if (VDBG) {
+            Log.v(TAG, "onDataPathConfirm: ndpId=" + ndpId + ", mac=" + String.valueOf(
+                    HexEncoding.encode(mac)) + ", accept=" + accept + ", reason=" + reason
+                    + ", messageLength=" + messageLength);
+        }
+
+        Map.Entry<String, NanNetworkRequestInformation> nnriE = getNetworkRequestByNdpId(ndpId);
+        if (nnriE == null) {
+            Log.w(TAG, "onDataPathConfirm: network request not found for ndpId=" + ndpId);
+            if (accept) {
+                mMgr.endDataPath(ndpId);
+            }
+            return null;
+        }
+
+        String networkSpecifier = nnriE.getKey();
+        NanNetworkRequestInformation nnri = nnriE.getValue();
+
+        // validate state
+        if (nnri.role == WifiNanManager.WIFI_NAN_DATA_PATH_ROLE_INITIATOR
+                && nnri.state != NanNetworkRequestInformation.STATE_INITIATOR_WAIT_FOR_CONFIRM) {
+            Log.w(TAG, "onDataPathConfirm: INITIATOR in invalid state=" + nnri.state);
+            mNetworkRequestsCache.remove(networkSpecifier);
+            if (accept) {
+                mMgr.endDataPath(ndpId);
+            }
+            return networkSpecifier;
+        }
+        if (nnri.role == WifiNanManager.WIFI_NAN_DATA_PATH_ROLE_RESPONDER
+                && nnri.state != NanNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_CONFIRM) {
+            Log.w(TAG, "onDataPathConfirm: RESPONDER in invalid state=" + nnri.state);
+            mNetworkRequestsCache.remove(networkSpecifier);
+            if (accept) {
+                mMgr.endDataPath(ndpId);
+            }
+            return networkSpecifier;
+        }
+
+        if (accept) {
+            nnri.state = (nnri.role == WifiNanManager.WIFI_NAN_DATA_PATH_ROLE_INITIATOR)
+                    ? NanNetworkRequestInformation.STATE_INITIATOR_CONFIRMED
+                    : NanNetworkRequestInformation.STATE_RESPONDER_CONFIRMED;
+            nnri.peerDataMac = mac;
+
+            NetworkInfo networkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI_P2P, 0,
+                    NETWORK_TAG, "");
+            NetworkCapabilities networkCapabilities = new NetworkCapabilities(
+                    mNetworkCapabilitiesFilter);
+            LinkProperties linkProperties = new LinkProperties();
+
+            try {
+                // TODO: b/29608448 to track other IPv6 related optimizations and move to IpManager
+                mNwService.setInterfaceUp(nnri.interfaceName);
+                mNwService.enableIpv6(nnri.interfaceName);
+            } catch (RemoteException e) {
+                Log.e(TAG, "onDataPathConfirm: ACCEPT nnri=" + nnri + ": can't configure network - "
+                        + e);
+                mMgr.endDataPath(ndpId);
+                return networkSpecifier;
+            }
+
+            if (!mNiWrapper.configureAgentProperties(nnri, networkSpecifier, ndpId, networkInfo,
+                    networkCapabilities, linkProperties)) {
+                return networkSpecifier;
+            }
+
+            nnri.networkAgent = new WifiNanNetworkAgent(mLooper, mContext,
+                    AGENT_TAG_PREFIX + nnri.ndpId, networkInfo, networkCapabilities, linkProperties,
+                    NETWORK_FACTORY_SCORE_AVAIL, networkSpecifier, ndpId);
+        } else {
+            if (DBG) {
+                Log.d(TAG, "onDataPathConfirm: data-path for networkSpecifier=" + networkSpecifier
+                        + " rejected - reason=" + reason);
+            }
+            mNetworkRequestsCache.remove(networkSpecifier);
+        }
+
+        return networkSpecifier;
+    }
+
+    /**
+     * Notification (unsolicited/asynchronous) from the firmware that the specified data-path has
+     * been terminated.
+     *
+     * @param ndpId The ID of the terminated data-path.
+     */
+    public void onDataPathEnd(int ndpId) {
+        if (VDBG) Log.v(TAG, "onDataPathEnd: ndpId=" + ndpId);
+
+        Map.Entry<String, NanNetworkRequestInformation> nnriE = getNetworkRequestByNdpId(ndpId);
+        if (nnriE == null) {
+            if (DBG) {
+                Log.d(TAG, "onDataPathEnd: network request not found for ndpId=" + ndpId);
+            }
+            return;
+        }
+
+        tearDownInterface(nnriE.getValue());
+        mNetworkRequestsCache.remove(nnriE.getKey());
+    }
+
+    /**
+     * Called whenever NAN comes down. Clean up all pending and up network requeests and agents.
+     */
+    public void onNanDownCleanupDataPaths() {
+        if (VDBG) Log.v(TAG, "onNanDownCleanupDataPaths");
+
+        for (NanNetworkRequestInformation nnri : mNetworkRequestsCache.values()) {
+            tearDownInterface(nnri);
+        }
+        mNetworkRequestsCache.clear();
+    }
+
+    /**
+     * Called when timed-out waiting for confirmation of the data-path setup (i.e.
+     * onDataPathConfirm). Started on the initiator when executing the request for the data-path
+     * and on the responder when received a request for data-path (in both cases only on success
+     * - i.e. when we're proceeding with data-path setup).
+     */
+    public void handleDataPathTimeout(String networkSpecifier) {
+        if (VDBG) Log.v(TAG, "handleDataPathTimeout: networkSpecifier=" + networkSpecifier);
+
+        NanNetworkRequestInformation nnri = mNetworkRequestsCache.remove(networkSpecifier);
+        if (nnri == null) {
+            if (DBG) {
+                Log.d(TAG,
+                        "handleDataPathTimeout: network request not found for networkSpecifier="
+                                + networkSpecifier);
+            }
+            return;
+        }
+
+        mMgr.endDataPath(nnri.ndpId);
+    }
+
+    private class WifiNanNetworkFactory extends NetworkFactory {
+        WifiNanNetworkFactory(Looper looper, Context context, NetworkCapabilities filter) {
+            super(looper, context, NETWORK_TAG, filter);
+        }
+
+        @Override
+        public boolean acceptRequest(NetworkRequest request, int score) {
+            if (VDBG) {
+                Log.v(TAG, "WifiNanNetworkFactory.acceptRequest: request=" + request + ", score="
+                        + score);
+            }
+
+            if (!mMgr.isUsageEnabled()) {
+                if (VDBG) {
+                    Log.v(TAG, "WifiNanNetworkFactory.acceptRequest: request=" + request
+                            + " -- NAN disabled");
+                }
+                return false;
+            }
+
+            if (mInterfaces.isEmpty()) {
+                Log.w(TAG, "WifiNanNetworkFactory.acceptRequest: request=" + request
+                        + " -- No NAN interfaces are up");
+                return false;
+            }
+
+            String networkSpecifier = request.networkCapabilities.getNetworkSpecifier();
+            if (TextUtils.isEmpty(networkSpecifier)) {
+                Log.w(TAG, "WifiNanNetworkFactory.acceptRequest: request=" + request
+                        + " - empty (or null) NetworkSpecifier");
+                return false;
+            }
+
+            // look up specifier - are we being called again?
+            NanNetworkRequestInformation nnri = mNetworkRequestsCache.get(networkSpecifier);
+            if (nnri != null) {
+                if (DBG) {
+                    Log.d(TAG, "WifiNanNetworkFactory.acceptRequest: request=" + request
+                            + " - already in cache!?");
+                }
+
+                // seems to happen after a network agent is created - trying to rematch all
+                // requests again!?
+                return true;
+            }
+
+            // parse network specifier (JSON) & cache
+            // TODO: validate that the client ID actually comes from the correct process and is
+            // not faked?
+            nnri = NanNetworkRequestInformation.parseNetworkSpecifier(networkSpecifier, mMgr);
+            if (nnri == null) {
+                Log.e(TAG, "WifiNanNetworkFactory.acceptRequest: request=" + request
+                        + " - can't parse network specifier");
+                return false;
+            }
+            mNetworkRequestsCache.put(networkSpecifier, nnri);
+
+            return true;
+        }
+
+        @Override
+        protected void needNetworkFor(NetworkRequest networkRequest, int score) {
+            if (VDBG) {
+                Log.v(TAG, "WifiNanNetworkFactory.needNetworkFor: networkRequest=" + networkRequest
+                        + ", score=" + score);
+            }
+
+            String networkSpecifier = networkRequest.networkCapabilities.getNetworkSpecifier();
+            NanNetworkRequestInformation nnri = mNetworkRequestsCache.get(networkSpecifier);
+            if (nnri == null) {
+                Log.e(TAG, "WifiNanNetworkFactory.needNetworkFor: networkRequest=" + networkRequest
+                        + " not in cache!?");
+                return;
+            }
+
+            if (nnri.role == WifiNanManager.WIFI_NAN_DATA_PATH_ROLE_INITIATOR) {
+                if (nnri.state != NanNetworkRequestInformation.STATE_INITIATOR_IDLE) {
+                    if (DBG) {
+                        Log.d(TAG, "WifiNanNetworkFactory.needNetworkFor: networkRequest="
+                                + networkRequest + " - already in progress");
+                        // TODO: understand how/when can be called again/while in progress (seems
+                        // to be related to score re-calculation after a network agent is created)
+                    }
+                    return;
+                }
+
+                nnri.interfaceName = selectInterfaceForRequest(nnri);
+                mMgr.initiateDataPathSetup(networkSpecifier, nnri.peerId,
+                        WifiNanNative.CHANNEL_REQUEST_TYPE_REQUESTED, selectChannelForRequest(nnri),
+                        nnri.peerDiscoveryMac, nnri.interfaceName, nnri.token);
+                nnri.state = NanNetworkRequestInformation.STATE_INITIATOR_WAIT_FOR_REQUEST_RESPONSE;
+            } else {
+                if (nnri.state != NanNetworkRequestInformation.STATE_RESPONDER_IDLE) {
+                    if (DBG) {
+                        Log.d(TAG, "WifiNanNetworkFactory.needNetworkFor: networkRequest="
+                                + networkRequest + " - already in progress");
+                        // TODO: understand how/when can be called again/while in progress (seems
+                        // to be related to score re-calculation after a network agent is created)
+                    }
+                    return;
+                }
+
+                nnri.state = NanNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_REQUEST;
+            }
+        }
+
+        @Override
+        protected void releaseNetworkFor(NetworkRequest networkRequest) {
+            if (VDBG) {
+                Log.v(TAG, "WifiNanNetworkFactory.releaseNetworkFor: networkRequest="
+                        + networkRequest);
+            }
+
+            String networkSpecifier = networkRequest.networkCapabilities.getNetworkSpecifier();
+            NanNetworkRequestInformation nnri = mNetworkRequestsCache.get(networkSpecifier);
+            if (nnri == null) {
+                Log.e(TAG,
+                        "WifiNanNetworkFactory.releaseNetworkFor: networkRequest=" + networkRequest
+                                + " not in cache!?");
+                return;
+            }
+
+            if (nnri.networkAgent != null) {
+                if (VDBG) {
+                    Log.v(TAG, "WifiNanNetworkFactory.releaseNetworkFor: networkRequest="
+                            + networkRequest + ", nnri=" + nnri
+                            + ": agent already created - deferring ending data-path to agent"
+                            + ".unwanted()");
+                }
+                return;
+            }
+
+            if (nnri.role == WifiNanManager.WIFI_NAN_DATA_PATH_ROLE_INITIATOR && nnri.state
+                    > NanNetworkRequestInformation.STATE_INITIATOR_WAIT_FOR_REQUEST_RESPONSE) {
+                mMgr.endDataPath(nnri.ndpId);
+            }
+            if (nnri.role == WifiNanManager.WIFI_NAN_DATA_PATH_ROLE_RESPONDER
+                    && nnri.state > NanNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_REQUEST) {
+                mMgr.endDataPath(nnri.ndpId);
+            }
+
+            // Will get a callback (on both initiator and responder) when data-path actually
+            // terminated. At that point will inform the agent and will clear the cache.
+        }
+    }
+
+    private class WifiNanNetworkAgent extends NetworkAgent {
+        private NetworkInfo mNetworkInfo;
+        private String mNetworkSpecifier;
+        private int mNdpId;
+
+        WifiNanNetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
+                NetworkCapabilities nc, LinkProperties lp, int score, String networkSpecifier,
+                int ndpId) {
+            super(looper, context, logTag, ni, nc, lp, score);
+
+            mNetworkInfo = ni;
+            mNetworkSpecifier = networkSpecifier;
+            mNdpId = ndpId;
+        }
+
+        @Override
+        protected void unwanted() {
+            if (VDBG) {
+                Log.v(TAG, "WifiNanNetworkAgent.unwanted: networkSpecifier=" + mNetworkSpecifier
+                        + ", ndpId=" + mNdpId);
+            }
+
+            mMgr.endDataPath(mNdpId);
+
+            // Will get a callback (on both initiator and responder) when data-path actually
+            // terminated. At that point will inform the agent and will clear the cache.
+        }
+
+        void reconfigureAgentAsDisconnected() {
+            if (VDBG) {
+                Log.v(TAG, "WifiNanNetworkAgent.reconfigureAgentAsDisconnected: networkSpecifier="
+                        + mNetworkSpecifier + ", ndpId=" + mNdpId);
+            }
+
+            mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null, "");
+            sendNetworkInfo(mNetworkInfo);
+        }
+    }
+
+    private void tearDownInterface(NanNetworkRequestInformation nnri) {
+        if (VDBG) Log.v(TAG, "tearDownInterface: nnri=" + nnri);
+
+        if (nnri.interfaceName != null && !nnri.interfaceName.isEmpty()) {
+            try {
+                // TODO: b/29608448 replace with IpManager
+                mNwService.setInterfaceDown(nnri.interfaceName);
+            } catch (RemoteException e) {
+                Log.e(TAG,
+                        "tearDownInterface: nnri=" + nnri + ": can't bring interface down - " + e);
+            }
+        }
+
+        if (nnri.networkAgent != null) {
+            nnri.networkAgent.reconfigureAgentAsDisconnected();
+        }
+    }
+
+    /**
+     * Select one of the existing interfaces for the new network request.
+     *
+     * TODO: for now there is only a single interface - simply pick it.
+     */
+    private String selectInterfaceForRequest(NanNetworkRequestInformation req) {
+        Iterator<String> it = mInterfaces.iterator();
+        if (it.hasNext()) {
+            return it.next();
+        }
+
+        Log.e(TAG, "selectInterfaceForRequest: req=" + req + " - but no interfaces available!");
+
+        return "";
+    }
+
+    /**
+     * Select a channel for the network request.
+     *
+     * TODO: for now simply select channel 6
+     */
+    private int selectChannelForRequest(NanNetworkRequestInformation req) {
+        return 2437;
+    }
+
+    /**
+     * NAN network request. State object: contains network request information/state through its
+     * lifetime.
+     */
+    @VisibleForTesting
+    public static class NanNetworkRequestInformation {
+        static final int STATE_INITIATOR_IDLE = 100;
+        static final int STATE_INITIATOR_WAIT_FOR_REQUEST_RESPONSE = 101;
+        static final int STATE_INITIATOR_WAIT_FOR_CONFIRM = 102;
+        static final int STATE_INITIATOR_CONFIRMED = 103;
+
+        static final int STATE_RESPONDER_IDLE = 200;
+        static final int STATE_RESPONDER_WAIT_FOR_REQUEST = 201;
+        static final int STATE_RESPONDER_WAIT_FOR_RESPOND_RESPONSE = 202;
+        static final int STATE_RESPONDER_WAIT_FOR_CONFIRM = 203;
+        static final int STATE_RESPONDER_CONFIRMED = 204;
+
+        public int state;
+        public int role;
+
+        public int uid;
+        public String interfaceName;
+        public int pubSubId = 0;
+        public int peerId = 0;
+        public byte[] peerDiscoveryMac = null;
+        public byte[] token = null;
+        public int ndpId;
+        public byte[] peerDataMac;
+
+        public WifiNanNetworkAgent networkAgent;
+
+        static NanNetworkRequestInformation parseNetworkSpecifier(String networkSpecifier,
+                WifiNanStateManager mgr) {
+            int type, role, uid, clientId, sessionId = 0, peerId = 0, pubSubId = 0;
+            byte[] peerMac = null;
+            byte[] token = null;
+
+            if (VDBG) {
+                Log.v(TAG, "parseNetworkSpecifier: networkSpecifier=" + networkSpecifier);
+            }
+
+            try {
+                JSONObject jsonObject = new JSONObject(networkSpecifier);
+
+                // type: always present
+                type = jsonObject.getInt(WifiNanManager.NETWORK_SPECIFIER_KEY_TYPE);
+                if (type < 0 || type > WifiNanManager.NETWORK_SPECIFIER_TYPE_MAX_VALID) {
+                    Log.e(TAG, "parseNetworkSpecifier: networkSpecifier=" + networkSpecifier
+                            + ", invalid 'type' value");
+                    return null;
+                }
+
+                // role: always present
+                role = jsonObject.getInt(WifiNanManager.NETWORK_SPECIFIER_KEY_ROLE);
+                if (role != WifiNanManager.WIFI_NAN_DATA_PATH_ROLE_INITIATOR
+                        && role != WifiNanManager.WIFI_NAN_DATA_PATH_ROLE_RESPONDER) {
+                    Log.e(TAG, "parseNetworkSpecifier: networkSpecifier=" + networkSpecifier
+                            + " -- invalid 'role' value");
+                    return null;
+                }
+
+                if (role == WifiNanManager.WIFI_NAN_DATA_PATH_ROLE_INITIATOR
+                        && type != WifiNanManager.NETWORK_SPECIFIER_TYPE_1A
+                        && type != WifiNanManager.NETWORK_SPECIFIER_TYPE_2A) {
+                    Log.e(TAG, "parseNetworkSpecifier: networkSpecifier=" + networkSpecifier
+                            + " -- invalid 'type' value for INITIATOR (only 1A and 2A are "
+                            + "permitted)");
+                    return null;
+                }
+
+                // clientId: always present
+                clientId = jsonObject.getInt(WifiNanManager.NETWORK_SPECIFIER_KEY_CLIENT_ID);
+
+                // sessionId: present for types 1A, 1B, 1C, 1D
+                if (type == WifiNanManager.NETWORK_SPECIFIER_TYPE_1A
+                        || type == WifiNanManager.NETWORK_SPECIFIER_TYPE_1B
+                        || type == WifiNanManager.NETWORK_SPECIFIER_TYPE_1C
+                        || type == WifiNanManager.NETWORK_SPECIFIER_TYPE_1D) {
+                    sessionId = jsonObject.getInt(WifiNanManager.NETWORK_SPECIFIER_KEY_SESSION_ID);
+                }
+
+                // peer Id: present for types 1A, 1B
+                if (type == WifiNanManager.NETWORK_SPECIFIER_TYPE_1A
+                        || type == WifiNanManager.NETWORK_SPECIFIER_TYPE_1B) {
+                    peerId = jsonObject.getInt(WifiNanManager.NETWORK_SPECIFIER_KEY_PEER_ID);
+                }
+
+                // peerMac: present for types 2A, 2B
+                if (type == WifiNanManager.NETWORK_SPECIFIER_TYPE_2A
+                        || type == WifiNanManager.NETWORK_SPECIFIER_TYPE_2B) {
+                    try {
+                        peerMac = HexEncoding.decode(jsonObject.getString(
+                                WifiNanManager.NETWORK_SPECIFIER_KEY_PEER_MAC).toCharArray(),
+                                false);
+                        if (peerMac == null || peerMac.length != 6) {
+                            Log.e(TAG, "parseNetworkSpecifier: networkSpecifier=" + networkSpecifier
+                                    + " -- invalid peer MAC address - null or not 6 bytes");
+                            return null;
+                        }
+                    } catch (IllegalArgumentException e) {
+                        Log.e(TAG, "parseNetworkSpecifier: networkSpecifier="
+                                + networkSpecifier + " -- invalid peer MAC address -- e=" + e);
+                        return null;
+                    }
+                }
+
+                // token: present for types 1A, 1C, 2A, 2C
+                if (type == WifiNanManager.NETWORK_SPECIFIER_TYPE_1A
+                        || type == WifiNanManager.NETWORK_SPECIFIER_TYPE_1C
+                        || type == WifiNanManager.NETWORK_SPECIFIER_TYPE_2A
+                        || type == WifiNanManager.NETWORK_SPECIFIER_TYPE_2C) {
+                    token = Base64.decode(
+                            jsonObject.getString(WifiNanManager.NETWORK_SPECIFIER_KEY_TOKEN),
+                            Base64.DEFAULT);
+                }
+            } catch (JSONException e) {
+                Log.e(TAG, "parseNetworkSpecifier: networkSpecifier=" + networkSpecifier
+                        + " -- invalid JSON format -- e=" + e);
+                return null;
+            }
+
+            // look up network specifier information in NAN state manager
+            WifiNanClientState client = mgr.getClient(clientId);
+            if (client == null) {
+                Log.e(TAG, "parseNetworkSpecifier: networkSpecifier=" + networkSpecifier
+                        + " -- not client with this id -- clientId=" + clientId);
+                return null;
+            }
+            uid = client.getUid();
+
+            if (type == WifiNanManager.NETWORK_SPECIFIER_TYPE_1A
+                    || type == WifiNanManager.NETWORK_SPECIFIER_TYPE_1B) {
+                WifiNanSessionState session = client.getSession(sessionId);
+                if (session == null) {
+                    Log.e(TAG,
+                            "parseNetworkSpecifier: networkSpecifier=" + networkSpecifier
+                                    + " -- not session with this id -- sessionId=" + sessionId);
+                    return null;
+                }
+                pubSubId = session.getPubSubId();
+                String peerMacStr = session.getMac(peerId, null);
+                if (peerMacStr == null) {
+                    Log.e(TAG,
+                            "parseNetworkSpecifier: networkSpecifier=" + networkSpecifier
+                                    + " -- no MAC address associated with this peer id -- peerId="
+                                    + peerId);
+                    return null;
+                }
+                try {
+                    peerMac = HexEncoding.decode(peerMacStr.toCharArray(), false);
+                    if (peerMac == null || peerMac.length != 6) {
+                        Log.e(TAG, "parseNetworkSpecifier: networkSpecifier="
+                                + networkSpecifier + " -- invalid peer MAC address");
+                        return null;
+                    }
+                } catch (IllegalArgumentException e) {
+                    Log.e(TAG,
+                            "parseNetworkSpecifier: networkSpecifier=" + networkSpecifier
+                                    + " -- invalid peer MAC address -- e=" + e);
+                    return null;
+                }
+            }
+
+            // create container and populate
+            NanNetworkRequestInformation nnri = new NanNetworkRequestInformation();
+            nnri.state = (role == WifiNanManager.WIFI_NAN_DATA_PATH_ROLE_INITIATOR)
+                    ? NanNetworkRequestInformation.STATE_INITIATOR_IDLE
+                    : NanNetworkRequestInformation.STATE_RESPONDER_IDLE;
+            nnri.role = role;
+            nnri.uid = uid;
+            nnri.pubSubId = pubSubId;
+            nnri.peerId = peerId;
+            nnri.peerDiscoveryMac = peerMac;
+            nnri.token = token;
+
+            return nnri;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder("NanNetworkRequestInformation: ");
+            sb.append("state=").append(state).append(", role=").append(role).append(
+                    ", uid=").append(uid).append(", interfaceName=").append(interfaceName).append(
+                    ", pubSubId=").append(pubSubId).append(", peerDiscoveryMac=").append(
+                    peerDiscoveryMac == null ? ""
+                            : String.valueOf(HexEncoding.encode(peerDiscoveryMac))).append(
+                    ", token=").append(token).append(", ndpId=").append(ndpId).append(
+                    ", peerDataMac=").append(
+                    peerDataMac == null ? "" : String.valueOf(HexEncoding.encode(peerDataMac)));
+            return sb.toString();
+        }
+    }
+
+    /**
+     * Enables mocking.
+     */
+    @VisibleForTesting
+    public class NetworkInterfaceWrapper {
+        /**
+         * Configures network agent properties: link-local address, connected status, interface
+         * name. Delegated to enable mocking.
+         */
+        public boolean configureAgentProperties(NanNetworkRequestInformation nnri,
+                String networkSpecifier, int ndpId, NetworkInfo networkInfo,
+                NetworkCapabilities networkCapabilities, LinkProperties linkProperties) {
+            // find link-local address
+            InetAddress linkLocal = null;
+            NetworkInterface ni;
+            try {
+                ni = NetworkInterface.getByName(nnri.interfaceName);
+            } catch (SocketException e) {
+                Log.e(TAG, "onDataPathConfirm: ACCEPT nnri=" + nnri
+                        + ": can't get network interface - " + e);
+                mMgr.endDataPath(ndpId);
+                return false;
+            }
+            if (ni == null) {
+                Log.e(TAG, "onDataPathConfirm: ACCEPT nnri=" + nnri
+                        + ": can't get network interface (null)");
+                mMgr.endDataPath(ndpId);
+                return false;
+            }
+            Enumeration<InetAddress> addresses = ni.getInetAddresses();
+            while (addresses.hasMoreElements()) {
+                InetAddress ip = addresses.nextElement();
+                if (ip instanceof Inet6Address && ip.isLinkLocalAddress()) {
+                    linkLocal = ip;
+                    break;
+                }
+            }
+
+            if (linkLocal == null) {
+                Log.e(TAG, "onDataPathConfirm: ACCEPT nnri=" + nnri + ": no link local addresses");
+                mMgr.endDataPath(ndpId);
+                return false;
+            }
+
+            // configure agent
+            networkInfo.setIsAvailable(true);
+            networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null, null);
+
+            networkCapabilities.setNetworkSpecifier(networkSpecifier);
+
+            linkProperties.setInterfaceName(nnri.interfaceName);
+            linkProperties.addLinkAddress(new LinkAddress(linkLocal, 64));
+            linkProperties.addRoute(
+                    new RouteInfo(new IpPrefix("fe80::/64"), null, nnri.interfaceName));
+
+            return true;
+        }
+    }
+
+    /**
+     * Dump the internal state of the class.
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("WifiNanDataPathStateManager:");
+        pw.println("  mInterfaces: " + mInterfaces);
+        pw.println("  mNetworkCapabilitiesFilter: " + mNetworkCapabilitiesFilter);
+        pw.println("  mNetworkRequestsCache: " + mNetworkRequestsCache);
+        pw.println("  mNetworkFactory:");
+        mNetworkFactory.dump(fd, pw, args);
+    }
+}
diff --git a/service/java/com/android/server/wifi/nan/WifiNanNative.java b/service/java/com/android/server/wifi/nan/WifiNanNative.java
index 8715719..8ecba96 100644
--- a/service/java/com/android/server/wifi/nan/WifiNanNative.java
+++ b/service/java/com/android/server/wifi/nan/WifiNanNative.java
@@ -17,23 +17,22 @@
 package com.android.server.wifi.nan;
 
 import android.net.wifi.nan.ConfigRequest;
-import android.net.wifi.nan.PublishData;
-import android.net.wifi.nan.PublishSettings;
-import android.net.wifi.nan.SubscribeData;
-import android.net.wifi.nan.SubscribeSettings;
-import android.net.wifi.nan.WifiNanSessionListener;
+import android.net.wifi.nan.PublishConfig;
+import android.net.wifi.nan.SubscribeConfig;
+import android.net.wifi.nan.WifiNanEventCallback;
+import android.net.wifi.nan.WifiNanSessionCallback;
 import android.util.Log;
 
 import com.android.server.wifi.WifiNative;
 
 import libcore.util.HexEncoding;
 
+import java.util.Arrays;
+
 /**
  * Native calls to access the Wi-Fi NAN HAL.
  *
  * Relies on WifiNative to perform the actual HAL registration.
- *
- * {@hide}
  */
 public class WifiNanNative {
     private static final String TAG = "WifiNanNative";
@@ -42,12 +41,18 @@
 
     private static final int WIFI_SUCCESS = 0;
 
-    private static boolean sNanNativeInit = false;
-
     private static WifiNanNative sWifiNanNativeSingleton;
 
+    private boolean mNativeHandlersIsInitialized = false;
+
     private static native int registerNanNatives();
 
+    /**
+     * Returns the singleton WifiNanNative used to manage the actual NAN HAL
+     * interface.
+     *
+     * @return Singleton object.
+     */
     public static WifiNanNative getInstance() {
         // dummy reference - used to make sure that WifiNative is loaded before
         // us since it is the one to load the shared library and starts its
@@ -66,6 +71,10 @@
         return sWifiNanNativeSingleton;
     }
 
+    /**
+     * A container class for NAN (vendor) implementation capabilities (or
+     * limitations). Filled-in by the firmware.
+     */
     public static class Capabilities {
         public int maxConcurrentNanClusters;
         public int maxPublishes;
@@ -79,6 +88,7 @@
         public int maxNdiInterfaces;
         public int maxNdpSessions;
         public int maxAppInfoLen;
+        public int maxQueuedTransmitMessages;
 
         @Override
         public String toString() {
@@ -89,233 +99,513 @@
                     + ", maxServiceSpecificInfoLen=" + maxServiceSpecificInfoLen
                     + ", maxVsaDataLen=" + maxVsaDataLen + ", maxMeshDataLen=" + maxMeshDataLen
                     + ", maxNdiInterfaces=" + maxNdiInterfaces + ", maxNdpSessions="
-                    + maxNdpSessions + ", maxAppInfoLen=" + maxAppInfoLen + "]";
+                    + maxNdpSessions + ", maxAppInfoLen=" + maxAppInfoLen
+                    + ", maxQueuedTransmitMessages=" + maxQueuedTransmitMessages + "]";
         }
     }
 
-    /* package */ static native int initNanHandlersNative(Object cls, int iface);
+    /* package */ static native int initNanHandlersNative(Class<WifiNative> cls, int iface);
 
-    private static native int getCapabilitiesNative(short transactionId, Object cls, int iface);
-
-    private boolean isNanInit(boolean tryToInit) {
-        if (!tryToInit || sNanNativeInit) {
-            return sNanNativeInit;
-        }
-
-        if (DBG) Log.d(TAG, "isNanInit: trying to init");
+    private boolean isNanInit() {
         synchronized (WifiNative.sLock) {
-            boolean halStarted = WifiNative.getWlanNativeInterface().isHalStarted();
-            if (!halStarted) {
-                halStarted = WifiNative.getWlanNativeInterface().startHal();
-            }
-            if (halStarted) {
+            if (!WifiNative.getWlanNativeInterface().isHalStarted()) {
+                /*
+                 * We should never start the HAL - that's done at a higher level
+                 * by the Wi-Fi state machine.
+                 */
+                mNativeHandlersIsInitialized = false;
+                return false;
+            } else if (!mNativeHandlersIsInitialized) {
                 int ret = initNanHandlersNative(WifiNative.class, WifiNative.sWlan0Index);
                 if (DBG) Log.d(TAG, "initNanHandlersNative: res=" + ret);
-                sNanNativeInit = ret == WIFI_SUCCESS;
+                mNativeHandlersIsInitialized = ret == WIFI_SUCCESS;
 
-                if (sNanNativeInit) {
-                    ret = getCapabilitiesNative(
-                            WifiNanStateManager.getInstance().createNextTransactionId(),
-                            WifiNative.class,
-                            WifiNative.sWlan0Index);
-                    if (DBG) Log.d(TAG, "getCapabilitiesNative: res=" + ret);
-                }
-
-                return sNanNativeInit;
+                return mNativeHandlersIsInitialized;
             } else {
-                Log.w(TAG, "isNanInit: HAL not initialized");
-                return false;
+                return true;
             }
         }
     }
 
+    /**
+     * Tell the NAN JNI to re-initialize the NAN callback pointers next time it starts up.
+     */
+    public void deInitNan() {
+        mNativeHandlersIsInitialized = false;
+    }
+
     private WifiNanNative() {
         // do nothing
     }
 
-    private static native int enableAndConfigureNative(short transactionId, Object cls, int iface,
-            ConfigRequest configRequest);
+    private static native int getCapabilitiesNative(short transactionId, Class<WifiNative> cls,
+            int iface);
 
-    public void enableAndConfigure(short transactionId, ConfigRequest configRequest) {
-        boolean success;
-
-        if (VDBG) Log.d(TAG, "enableAndConfigure: configRequest=" + configRequest);
-        if (isNanInit(true)) {
+    /**
+     * Query the NAN firmware's capabilities.
+     *
+     * @param transactionId Transaction ID for the transaction - used in the async callback to
+     *                      match with the original request.
+     */
+    public boolean getCapabilities(short transactionId) {
+        if (VDBG) Log.d(TAG, "getCapabilities");
+        if (isNanInit()) {
             int ret;
             synchronized (WifiNative.sLock) {
-                ret = enableAndConfigureNative(transactionId, WifiNative.class,
-                        WifiNative.sWlan0Index, configRequest);
+                ret = getCapabilitiesNative(transactionId, WifiNative.class,
+                        WifiNative.sWlan0Index);
             }
-            if (DBG) Log.d(TAG, "enableAndConfigureNative: ret=" + ret);
-            success = ret == WIFI_SUCCESS;
+            if (ret != WIFI_SUCCESS) {
+                Log.w(TAG, "getCapabilities: HAL API returned non-success -- " + ret);
+            }
+            return ret == WIFI_SUCCESS;
         } else {
-            Log.w(TAG, "enableAndConfigure: NanInit fails");
-            success = false;
+            Log.w(TAG, "getCapabilities: cannot initialize NAN");
+            return false;
         }
-
-
-        // TODO: do something on !success - send failure message back
     }
 
-    private static native int disableNative(short transactionId, Object cls, int iface);
+    private static native int enableAndConfigureNative(short transactionId, Class<WifiNative> cls,
+            int iface, ConfigRequest configRequest);
 
-    public void disable(short transactionId) {
-        boolean success;
+    private static native int updateConfigurationNative(short transactionId, Class<WifiNative> cls,
+            int iface, ConfigRequest configRequest);
 
+    /**
+     * Enable and configure NAN.
+     *
+     * @param transactionId Transaction ID for the transaction - used in the
+     *            async callback to match with the original request.
+     * @param configRequest Requested NAN configuration.
+     * @param initialConfiguration Specifies whether initial configuration
+     *            (true) or an update (false) to the configuration.
+     */
+    public boolean enableAndConfigure(short transactionId, ConfigRequest configRequest,
+            boolean initialConfiguration) {
+        if (VDBG) Log.d(TAG, "enableAndConfigure: configRequest=" + configRequest);
+        if (isNanInit()) {
+            int ret;
+            if (initialConfiguration) {
+                synchronized (WifiNative.sLock) {
+                    ret = enableAndConfigureNative(transactionId, WifiNative.class,
+                            WifiNative.sWlan0Index, configRequest);
+                }
+                if (ret != WIFI_SUCCESS) {
+                    Log.w(TAG, "enableAndConfigureNative: HAL API returned non-success -- " + ret);
+                }
+            } else {
+                synchronized (WifiNative.sLock) {
+                    ret = updateConfigurationNative(transactionId, WifiNative.class,
+                            WifiNative.sWlan0Index, configRequest);
+                }
+                if (ret != WIFI_SUCCESS) {
+                    Log.w(TAG, "updateConfigurationNative: HAL API returned non-success -- " + ret);
+                }
+            }
+            return ret == WIFI_SUCCESS;
+        } else {
+            Log.w(TAG, "enableAndConfigure: NanInit fails");
+            return false;
+        }
+    }
+
+    private static native int disableNative(short transactionId, Class<WifiNative> cls, int iface);
+
+    /**
+     * Disable NAN.
+     *
+     * @param transactionId transactionId Transaction ID for the transaction -
+     *            used in the async callback to match with the original request.
+     */
+    public boolean disable(short transactionId) {
         if (VDBG) Log.d(TAG, "disableNan");
-        if (isNanInit(true)) {
+        if (isNanInit()) {
             int ret;
             synchronized (WifiNative.sLock) {
                 ret = disableNative(transactionId, WifiNative.class, WifiNative.sWlan0Index);
             }
-            if (DBG) Log.d(TAG, "disableNative: ret=" + ret);
-            success = ret == WIFI_SUCCESS;
+            if (ret != WIFI_SUCCESS) {
+                Log.w(TAG, "disableNative: HAL API returned non-success -- " + ret);
+            }
+            return ret == WIFI_SUCCESS;
         } else {
             Log.w(TAG, "disable: cannot initialize NAN");
-            success = false;
+            return false;
         }
-
-        // TODO: do something on !success - send failure message back
     }
 
-    private static native int publishNative(short transactionId, int publishId, Object cls,
-            int iface,
-            PublishData publishData, PublishSettings publishSettings);
+    private static native int publishNative(short transactionId, int publishId,
+            Class<WifiNative> cls, int iface, PublishConfig publishConfig);
 
-    public void publish(short transactionId, int publishId, PublishData publishData,
-            PublishSettings publishSettings) {
-        boolean success;
-
+    /**
+     * Start or modify a service publish session.
+     *
+     * @param transactionId transactionId Transaction ID for the transaction -
+     *            used in the async callback to match with the original request.
+     * @param publishId ID of the requested session - 0 to request a new publish
+     *            session.
+     * @param publishConfig Configuration of the discovery session.
+     */
+    public boolean publish(short transactionId, int publishId, PublishConfig publishConfig) {
         if (VDBG) {
-            Log.d(TAG, "publish: transactionId=" + transactionId + ",data='" + publishData
-                    + "', settings=" + publishSettings);
+            Log.d(TAG, "publish: transactionId=" + transactionId + ", config=" + publishConfig);
         }
 
-        if (isNanInit(true)) {
+        if (isNanInit()) {
             int ret;
             synchronized (WifiNative.sLock) {
                 ret = publishNative(transactionId, publishId, WifiNative.class,
-                        WifiNative.sWlan0Index, publishData, publishSettings);
+                        WifiNative.sWlan0Index, publishConfig);
             }
-            if (DBG) Log.d(TAG, "publishNative: ret=" + ret);
-            success = ret == WIFI_SUCCESS;
+            if (ret != WIFI_SUCCESS) {
+                Log.w(TAG, "publishNative: HAL API returned non-success -- " + ret);
+            }
+            return ret == WIFI_SUCCESS;
         } else {
             Log.w(TAG, "publish: cannot initialize NAN");
-            success = false;
+            return false;
         }
-
-        // TODO: do something on !success - send failure message back
     }
 
-    private static native int subscribeNative(short transactionId, int subscribeId, Object cls,
-            int iface, SubscribeData subscribeData, SubscribeSettings subscribeSettings);
+    private static native int subscribeNative(short transactionId, int subscribeId,
+            Class<WifiNative> cls, int iface, SubscribeConfig subscribeConfig);
 
-    public void subscribe(short transactionId, int subscribeId, SubscribeData subscribeData,
-            SubscribeSettings subscribeSettings) {
-        boolean success;
-
+    /**
+     * Start or modify a service subscription session.
+     *
+     * @param transactionId transactionId Transaction ID for the transaction -
+     *            used in the async callback to match with the original request.
+     * @param subscribeId ID of the requested session - 0 to request a new
+     *            subscribe session.
+     * @param subscribeConfig Configuration of the discovery session.
+     */
+    public boolean subscribe(short transactionId, int subscribeId,
+            SubscribeConfig subscribeConfig) {
         if (VDBG) {
-            Log.d(TAG, "subscribe: transactionId=" + transactionId + ", data='" + subscribeData
-                    + "', settings=" + subscribeSettings);
+            Log.d(TAG, "subscribe: transactionId=" + transactionId + ", config=" + subscribeConfig);
         }
 
-        if (isNanInit(true)) {
+        if (isNanInit()) {
             int ret;
             synchronized (WifiNative.sLock) {
                 ret = subscribeNative(transactionId, subscribeId, WifiNative.class,
-                        WifiNative.sWlan0Index, subscribeData, subscribeSettings);
+                        WifiNative.sWlan0Index, subscribeConfig);
             }
-            if (DBG) Log.d(TAG, "subscribeNative: ret=" + ret);
-            success = ret == WIFI_SUCCESS;
+            if (ret != WIFI_SUCCESS) {
+                Log.w(TAG, "subscribeNative: HAL API returned non-success -- " + ret);
+            }
+            return ret == WIFI_SUCCESS;
         } else {
             Log.w(TAG, "subscribe: cannot initialize NAN");
-            success = false;
+            return false;
         }
-
-        // TODO: do something on !success - send failure message back
     }
 
-    private static native int sendMessageNative(short transactionId, Object cls, int iface,
-            int pubSubId, int requestorInstanceId, byte[] dest, byte[] message, int messageLength);
+    private static native int sendMessageNative(short transactionId, Class<WifiNative> cls,
+            int iface, int pubSubId, int requestorInstanceId, byte[] dest, byte[] message,
+            int messageLength);
 
-    public void sendMessage(short transactionId, int pubSubId, int requestorInstanceId, byte[] dest,
-            byte[] message, int messageLength) {
-        boolean success;
-
+    /**
+     * Send a message through an existing discovery session.
+     *
+     * @param transactionId transactionId Transaction ID for the transaction -
+     *            used in the async callback to match with the original request.
+     * @param pubSubId ID of the existing publish/subscribe session.
+     * @param requestorInstanceId ID of the peer to communicate with - obtained
+     *            through a previous discovery (match) operation with that peer.
+     * @param dest MAC address of the peer to communicate with - obtained
+     *            together with requestorInstanceId.
+     * @param message Message.
+     * @param messageLength Message byte array length.
+     * @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,
+            byte[] dest, byte[] message, int messageLength, int messageId) {
         if (VDBG) {
             Log.d(TAG,
                     "sendMessage: transactionId=" + transactionId + ", pubSubId=" + pubSubId
                             + ", requestorInstanceId=" + requestorInstanceId + ", dest="
                             + String.valueOf(HexEncoding.encode(dest)) + ", messageLength="
-                            + messageLength);
+                            + messageLength + ", messageId=" + messageId);
         }
 
-        if (isNanInit(true)) {
+        if (isNanInit()) {
             int ret;
             synchronized (WifiNative.sLock) {
                 ret = sendMessageNative(transactionId, WifiNative.class, WifiNative.sWlan0Index,
                         pubSubId, requestorInstanceId, dest, message, messageLength);
             }
-            if (DBG) Log.d(TAG, "sendMessageNative: ret=" + ret);
-            success = ret == WIFI_SUCCESS;
+            if (ret != WIFI_SUCCESS) {
+                Log.w(TAG, "sendMessageNative: HAL API returned non-success -- " + ret);
+            }
+            return ret == WIFI_SUCCESS;
         } else {
             Log.w(TAG, "sendMessage: cannot initialize NAN");
-            success = false;
+            return false;
         }
-
-        // TODO: do something on !success - send failure message back
     }
 
-    private static native int stopPublishNative(short transactionId, Object cls, int iface,
-            int pubSubId);
+    private static native int stopPublishNative(short transactionId, Class<WifiNative> cls,
+            int iface, int pubSubId);
 
-    public void stopPublish(short transactionId, int pubSubId) {
-        boolean success;
-
+    /**
+     * Terminate a publish discovery session.
+     *
+     * @param transactionId transactionId Transaction ID for the transaction -
+     *            used in the async callback to match with the original request.
+     * @param pubSubId ID of the publish/subscribe session - obtained when
+     *            creating a session.
+     */
+    public boolean stopPublish(short transactionId, int pubSubId) {
         if (VDBG) {
             Log.d(TAG, "stopPublish: transactionId=" + transactionId + ", pubSubId=" + pubSubId);
         }
 
-        if (isNanInit(true)) {
+        if (isNanInit()) {
             int ret;
             synchronized (WifiNative.sLock) {
                 ret = stopPublishNative(transactionId, WifiNative.class, WifiNative.sWlan0Index,
                         pubSubId);
             }
-            if (DBG) Log.d(TAG, "stopPublishNative: ret=" + ret);
-            success = ret == WIFI_SUCCESS;
+            if (ret != WIFI_SUCCESS) {
+                Log.w(TAG, "stopPublishNative: HAL API returned non-success -- " + ret);
+            }
+            return ret == WIFI_SUCCESS;
         } else {
             Log.w(TAG, "stopPublish: cannot initialize NAN");
-            success = false;
+            return false;
         }
-
-        // TODO: do something on !success - send failure message back
     }
 
-    private static native int stopSubscribeNative(short transactionId, Object cls, int iface,
-            int pubSubId);
+    private static native int stopSubscribeNative(short transactionId, Class<WifiNative> cls,
+            int iface, int pubSubId);
 
-    public void stopSubscribe(short transactionId, int pubSubId) {
-        boolean success;
-
+    /**
+     * Terminate a subscribe discovery session.
+     *
+     * @param transactionId transactionId Transaction ID for the transaction -
+     *            used in the async callback to match with the original request.
+     * @param pubSubId ID of the publish/subscribe session - obtained when
+     *            creating a session.
+     */
+    public boolean stopSubscribe(short transactionId, int pubSubId) {
         if (VDBG) {
             Log.d(TAG, "stopSubscribe: transactionId=" + transactionId + ", pubSubId=" + pubSubId);
         }
 
-        if (isNanInit(true)) {
+        if (isNanInit()) {
             int ret;
             synchronized (WifiNative.sLock) {
                 ret = stopSubscribeNative(transactionId, WifiNative.class, WifiNative.sWlan0Index,
                         pubSubId);
             }
-            if (DBG) Log.d(TAG, "stopSubscribeNative: ret=" + ret);
-            success = ret == WIFI_SUCCESS;
+            if (ret != WIFI_SUCCESS) {
+                Log.w(TAG, "stopSubscribeNative: HAL API returned non-success -- " + ret);
+            }
+            return ret == WIFI_SUCCESS;
         } else {
             Log.w(TAG, "stopSubscribe: cannot initialize NAN");
-            success = false;
+            return false;
+        }
+    }
+
+    private static native int createNanNetworkInterfaceNative(short transactionId,
+                                                              Class<WifiNative> cls, int iface,
+                                                              String interfaceName);
+
+    /**
+     * Create a NAN network interface. This only creates the Linux interface - it doesn't actually
+     * create the data connection.
+     *
+     * @param transactionId Transaction ID for the transaction - used in the async callback to
+     *                      match with the original request.
+     * @param interfaceName The name of the interface, e.g. "nan0".
+     */
+    public boolean createNanNetworkInterface(short transactionId, String interfaceName) {
+        if (VDBG) {
+            Log.v(TAG, "createNanNetworkInterface: transactionId=" + transactionId + ", "
+                    + "interfaceName=" + interfaceName);
         }
 
-        // TODO: do something on !success - send failure message back
+        if (isNanInit()) {
+            int ret;
+            synchronized (WifiNative.sLock) {
+                ret = createNanNetworkInterfaceNative(transactionId, WifiNative.class, WifiNative
+                        .sWlan0Index, interfaceName);
+            }
+            if (ret != WIFI_SUCCESS) {
+                Log.w(TAG,
+                        "createNanNetworkInterfaceNative: HAL API returned non-success -- " + ret);
+            }
+            return ret == WIFI_SUCCESS;
+        } else {
+            Log.w(TAG, "createNanNetworkInterface: cannot initialize NAN");
+            return false;
+        }
+    }
+
+    private static native int deleteNanNetworkInterfaceNative(short transactionId,
+                                                              Class<WifiNative> cls, int iface,
+                                                              String interfaceName);
+
+    /**
+     * Deletes a NAN network interface. The data connection can (should?) be torn down previously.
+     *
+     * @param transactionId Transaction ID for the transaction - used in the async callback to
+     *                      match with the original request.
+     * @param interfaceName The name of the interface, e.g. "nan0".
+     */
+    public boolean deleteNanNetworkInterface(short transactionId, String interfaceName) {
+        if (VDBG) {
+            Log.v(TAG, "deleteNanNetworkInterface: transactionId=" + transactionId + ", "
+                    + "interfaceName=" + interfaceName);
+        }
+
+        if (isNanInit()) {
+            int ret;
+            synchronized (WifiNative.sLock) {
+                ret = deleteNanNetworkInterfaceNative(transactionId, WifiNative.class, WifiNative
+                        .sWlan0Index, interfaceName);
+            }
+            if (ret != WIFI_SUCCESS) {
+                Log.w(TAG,
+                        "deleteNanNetworkInterfaceNative: HAL API returned non-success -- " + ret);
+            }
+            return ret == WIFI_SUCCESS;
+        } else {
+            Log.w(TAG, "deleteNanNetworkInterface: cannot initialize NAN");
+            return false;
+        }
+    }
+
+    private static native int initiateDataPathNative(short transactionId, Class<WifiNative> cls,
+                                                     int iface, int peerId, int
+                                                             channelRequestType, int channel,
+                                                     byte[] peer, String interfaceName, byte[]
+                                                             message, int messageLength);
+
+    public static final int CHANNEL_REQUEST_TYPE_NONE = 0;
+    public static final int CHANNEL_REQUEST_TYPE_REQUESTED = 1;
+    public static final int CHANNEL_REQUEST_TYPE_REQUIRED = 2;
+
+    /**
+     * Initiates setting up a data-path between device and peer.
+     *
+     * @param transactionId      Transaction ID for the transaction - used in the async callback to
+     *                           match with the original request.
+     * @param peerId             ID of the peer ID to associate the data path with. A value of 0
+     *                           indicates that not associated with an existing session.
+     * @param channelRequestType Indicates whether the specified channel is available, if available
+     *                           requested or forced (resulting in failure if cannot be
+     *                           accommodated).
+     * @param channel            The channel on which to set up the data-path.
+     * @param peer               The MAC address of the peer to create a connection with.
+     * @param interfaceName      The interface on which to create the data connection.
+     * @param message An arbitrary byte array to forward to the peer as part of the data path
+     *                request.
+     * @param messageLength The size of the message.
+     */
+    public boolean initiateDataPath(short transactionId, int peerId, int channelRequestType,
+                                    int channel, byte[] peer, String interfaceName, byte[]
+                                            message, int messageLength) {
+        if (VDBG) {
+            Log.v(TAG, "initiateDataPath: transactionId=" + transactionId + ", peerId=" + peerId
+                    + ", channelRequestType=" + channelRequestType + ", channel=" + channel
+                    + ", peer=" + String.valueOf(HexEncoding.encode(peer)) + ", interfaceName="
+                    + interfaceName + ", " + "messageLength=" + messageLength);
+        }
+
+        if (isNanInit()) {
+            int ret;
+            synchronized (WifiNative.sLock) {
+                ret = initiateDataPathNative(transactionId, WifiNative.class, WifiNative
+                        .sWlan0Index, peerId, channelRequestType, channel, peer, interfaceName,
+                        message, messageLength);
+            }
+            if (ret != WIFI_SUCCESS) {
+                Log.w(TAG, "initiateDataPathNative: HAL API returned non-success -- " + ret);
+            }
+            return ret == WIFI_SUCCESS;
+        } else {
+            Log.w(TAG, "initiateDataPath: cannot initialize NAN");
+            return false;
+        }
+    }
+
+    private static native int respondToDataPathRequestNative(short transactionId,
+                                                             Class<WifiNative> cls, int iface,
+                                                             boolean accept, int ndpId, String
+                                                                     interfaceName, byte[]
+                                                                     message, int messageLength);
+
+    /**
+     * Responds to a data request from a peer.
+     *
+     * @param transactionId Transaction ID for the transaction - used in the async callback to
+     *                      match with the original request.
+     * @param accept Accept (true) or reject (false) the original call.
+     * @param ndpId The NDP (NAN data path) ID. Obtained from the request callback.
+     * @param interfaceName The interface on which the data path will be setup. Obtained from the
+     *                      request callback.
+     * @param message An arbitrary byte array to forward to the peer in the respond message.
+     * @param messageLength The length of the message array.
+     */
+    public boolean respondToDataPathRequest(short transactionId, boolean accept, int ndpId,
+                                            String interfaceName, byte[] message, int
+                                                    messageLength) {
+        if (VDBG) {
+            Log.v(TAG, "respondToDataPathRequest: transactionId=" + transactionId + ", accept="
+                    + accept + ", int ndpId=" + ndpId + ", interfaceName=" + interfaceName + ", "
+                    + "messageLength=" + messageLength);
+        }
+
+        if (isNanInit()) {
+            int ret;
+            synchronized (WifiNative.sLock) {
+                ret = respondToDataPathRequestNative(transactionId, WifiNative.class, WifiNative
+                        .sWlan0Index, accept, ndpId, interfaceName, message, messageLength);
+            }
+            if (ret != WIFI_SUCCESS) {
+                Log.w(TAG,
+                        "respondToDataPathRequestNative: HAL API returned non-success -- " + ret);
+            }
+            return ret == WIFI_SUCCESS;
+        } else {
+            Log.w(TAG, "respondToDataPathRequest: cannot initialize NAN");
+            return false;
+        }
+    }
+
+    private static native int endDataPathNative(short transactionId, Class<WifiNative> cls,
+            int iface, int ndpId);
+
+    /**
+     * Terminate an existing data-path (does not delete the interface).
+     *
+     * @param transactionId Transaction ID for the transaction - used in the async callback to
+     *                      match with the original request.
+     * @param ndpId The NDP (NAN data path) ID to be terminated.
+     */
+    public boolean endDataPath(short transactionId, int ndpId) {
+        if (VDBG) {
+            Log.v(TAG, "endDataPath: transactionId=" + transactionId + ", ndpId=" + ndpId);
+        }
+
+        if (isNanInit()) {
+            int ret;
+            synchronized (WifiNative.sLock) {
+                ret = endDataPathNative(transactionId, WifiNative.class, WifiNative.sWlan0Index,
+                        ndpId);
+            }
+            if (ret != WIFI_SUCCESS) {
+                Log.w(TAG, "endDataPathNative: HAL API returned non-success -- " + ret);
+            }
+            return ret == WIFI_SUCCESS;
+        } else {
+            Log.w(TAG, "endDataPath: cannot initialize NAN");
+            return false;
+        }
     }
 
     // EVENTS
@@ -327,9 +617,16 @@
     public static final int NAN_RESPONSE_TRANSMIT_FOLLOWUP = 4;
     public static final int NAN_RESPONSE_SUBSCRIBE = 5;
     public static final int NAN_RESPONSE_SUBSCRIBE_CANCEL = 6;
+    public static final int NAN_RESPONSE_CONFIG = 8;
     public static final int NAN_RESPONSE_GET_CAPABILITIES = 12;
+    public static final int NAN_RESPONSE_DP_INTERFACE_CREATE = 13;
+    public static final int NAN_RESPONSE_DP_INTERFACE_DELETE = 14;
+    public static final int NAN_RESPONSE_DP_INITIATOR_RESPONSE = 15;
+    public static final int NAN_RESPONSE_DP_RESPONDER_RESPONSE = 16;
+    public static final int NAN_RESPONSE_DP_END = 17;
 
     // direct copy from wifi_nan.h: need to keep in sync
+    /* NAN Protocol Response Codes */
     public static final int NAN_STATUS_SUCCESS = 0;
     public static final int NAN_STATUS_TIMEOUT = 1;
     public static final int NAN_STATUS_DE_FAILURE = 2;
@@ -350,8 +647,14 @@
     public static final int NAN_STATUS_INVALID_TLV_VALUE = 17;
     public static final int NAN_STATUS_INVALID_TX_PRIORITY = 18;
     public static final int NAN_STATUS_INVALID_CONNECTION_MAP = 19;
+    public static final int NAN_STATUS_INVALID_TCA_ID = 20;
+    public static final int NAN_STATUS_INVALID_STATS_ID = 21;
+    public static final int NAN_STATUS_NAN_NOT_ALLOWED = 22;
+    public static final int NAN_STATUS_NO_OTA_ACK = 23;
+    public static final int NAN_STATUS_TX_FAIL = 24;
+    public static final int NAN_STATUS_ALREADY_ENABLED = 25;
 
-    // NAN Configuration Response codes
+    /* NAN Configuration Response codes */
     public static final int NAN_STATUS_INVALID_RSSI_CLOSE_VALUE = 4096;
     public static final int NAN_STATUS_INVALID_RSSI_MIDDLE_VALUE = 4097;
     public static final int NAN_STATUS_INVALID_HOP_COUNT_LIMIT = 4098;
@@ -374,8 +677,12 @@
     public static final int NAN_STATUS_INVALID_POST_NAN_DISCOVERY_BITMAP_VALUE = 4115;
     public static final int NAN_STATUS_MISSING_FUTHER_AVAILABILITY_MAP = 4116;
     public static final int NAN_STATUS_INVALID_BAND_CONFIG_FLAGS = 4117;
+    public static final int NAN_STATUS_INVALID_RANDOM_FACTOR_UPDATE_TIME_VALUE = 4118;
+    public static final int NAN_STATUS_INVALID_ONGOING_SCAN_PERIOD = 4119;
+    public static final int NAN_STATUS_INVALID_DW_INTERVAL_VALUE = 4120;
+    public static final int NAN_STATUS_INVALID_DB_INTERVAL_VALUE = 4121;
 
-    // publish/subscribe termination reasons
+    /* publish/subscribe termination reasons */
     public static final int NAN_TERMINATED_REASON_INVALID = 8192;
     public static final int NAN_TERMINATED_REASON_TIMEOUT = 8193;
     public static final int NAN_TERMINATED_REASON_USER_REQUEST = 8194;
@@ -387,31 +694,34 @@
     public static final int NAN_TERMINATED_REASON_POST_DISC_LEN_EXCEEDED = 8200;
     public static final int NAN_TERMINATED_REASON_FURTHER_AVAIL_MAP_EMPTY = 8201;
 
-    private static int translateHalStatusToPublicStatus(int halStatus) {
+    /* 9000-9500 NDP Status type */
+    public static final int NAN_STATUS_NDP_UNSUPPORTED_CONCURRENCY = 9000;
+    public static final int NAN_STATUS_NDP_NAN_DATA_IFACE_CREATE_FAILED = 9001;
+    public static final int NAN_STATUS_NDP_NAN_DATA_IFACE_DELETE_FAILED = 9002;
+    public static final int NAN_STATUS_NDP_DATA_INITIATOR_REQUEST_FAILED = 9003;
+    public static final int NAN_STATUS_NDP_DATA_RESPONDER_REQUEST_FAILED = 9004;
+    public static final int NAN_STATUS_NDP_INVALID_SERVICE_INSTANCE_ID = 9005;
+    public static final int NAN_STATUS_NDP_INVALID_NDP_INSTANCE_ID = 9006;
+    public static final int NAN_STATUS_NDP_INVALID_RESPONSE_CODE = 9007;
+    public static final int NAN_STATUS_NDP_INVALID_APP_INFO_LEN = 9008;
+
+    /* OTA failures and timeouts during negotiation */
+    public static final int NAN_STATUS_NDP_MGMT_FRAME_REQUEST_FAILED = 9009;
+    public static final int NAN_STATUS_NDP_MGMT_FRAME_RESPONSE_FAILED = 9010;
+    public static final int NAN_STATUS_NDP_MGMT_FRAME_CONFIRM_FAILED = 9011;
+    public static final int NAN_STATUS_NDP_END_FAILED = 9012;
+    public static final int NAN_STATUS_NDP_MGMT_FRAME_END_REQUEST_FAILED = 9013;
+
+    /* 9500 onwards vendor specific error codes */
+    public static final int NAN_STATUS_NDP_VENDOR_SPECIFIC_ERROR = 9500;
+
+    private static int translateHalStatusToNanEventCallbackReason(int halStatus) {
         switch (halStatus) {
-            case NAN_STATUS_NO_SPACE_AVAILABLE:
-                return WifiNanSessionListener.FAIL_REASON_NO_RESOURCES;
-
-            case NAN_STATUS_TIMEOUT:
-            case NAN_STATUS_DE_FAILURE:
-            case NAN_STATUS_DISABLE_IN_PROGRESS:
-                return WifiNanSessionListener.FAIL_REASON_OTHER;
-
-            case NAN_STATUS_INVALID_MSG_VERSION:
-            case NAN_STATUS_INVALID_MSG_LEN:
-            case NAN_STATUS_INVALID_MSG_ID:
-            case NAN_STATUS_INVALID_HANDLE:
-            case NAN_STATUS_INVALID_PUBLISH_TYPE:
-            case NAN_STATUS_INVALID_TX_TYPE:
-            case NAN_STATUS_INVALID_MATCH_ALGORITHM:
-            case NAN_STATUS_INVALID_TLV_LEN:
-            case NAN_STATUS_INVALID_TLV_TYPE:
-            case NAN_STATUS_MISSING_TLV_TYPE:
-            case NAN_STATUS_INVALID_TOTAL_TLVS_LEN:
-            case NAN_STATUS_INVALID_MATCH_HANDLE:
-            case NAN_STATUS_INVALID_TLV_VALUE:
-            case NAN_STATUS_INVALID_TX_PRIORITY:
-            case NAN_STATUS_INVALID_CONNECTION_MAP:
+            case NAN_STATUS_SUCCESS:
+                /*
+                 * TODO: b/27914592 all of these codes will be cleaned-up/reduced.
+                 */
+                return WifiNanEventCallback.REASON_OTHER;
             case NAN_STATUS_INVALID_RSSI_CLOSE_VALUE:
             case NAN_STATUS_INVALID_RSSI_MIDDLE_VALUE:
             case NAN_STATUS_INVALID_HOP_COUNT_LIMIT:
@@ -434,13 +744,22 @@
             case NAN_STATUS_INVALID_POST_NAN_DISCOVERY_BITMAP_VALUE:
             case NAN_STATUS_MISSING_FUTHER_AVAILABILITY_MAP:
             case NAN_STATUS_INVALID_BAND_CONFIG_FLAGS:
-                return WifiNanSessionListener.FAIL_REASON_INVALID_ARGS;
+            case NAN_STATUS_INVALID_RANDOM_FACTOR_UPDATE_TIME_VALUE:
+            case NAN_STATUS_INVALID_ONGOING_SCAN_PERIOD:
+            case NAN_STATUS_INVALID_DW_INTERVAL_VALUE:
+            case NAN_STATUS_INVALID_DB_INTERVAL_VALUE:
+                return WifiNanEventCallback.REASON_INVALID_ARGS;
+        }
 
-                // publish/subscribe termination reasons
+        return WifiNanEventCallback.REASON_OTHER;
+    }
+
+    private static int translateHalStatusToNanSessionCallbackTerminate(int halStatus) {
+        switch (halStatus) {
             case NAN_TERMINATED_REASON_TIMEOUT:
             case NAN_TERMINATED_REASON_USER_REQUEST:
             case NAN_TERMINATED_REASON_COUNT_REACHED:
-                return WifiNanSessionListener.TERMINATE_REASON_DONE;
+                return WifiNanSessionCallback.TERMINATE_REASON_DONE;
 
             case NAN_TERMINATED_REASON_INVALID:
             case NAN_TERMINATED_REASON_FAILURE:
@@ -449,10 +768,46 @@
             case NAN_TERMINATED_REASON_POST_DISC_ATTR_EXPIRED:
             case NAN_TERMINATED_REASON_POST_DISC_LEN_EXCEEDED:
             case NAN_TERMINATED_REASON_FURTHER_AVAIL_MAP_EMPTY:
-                return WifiNanSessionListener.TERMINATE_REASON_FAIL;
+                return WifiNanSessionCallback.TERMINATE_REASON_FAIL;
         }
 
-        return WifiNanSessionListener.FAIL_REASON_OTHER;
+        return WifiNanSessionCallback.TERMINATE_REASON_FAIL;
+    }
+
+    private static int translateHalStatusToNanSessionCallbackReason(int halStatus) {
+        switch (halStatus) {
+            case NAN_STATUS_TIMEOUT:
+            case NAN_STATUS_DE_FAILURE:
+            case NAN_STATUS_INVALID_MSG_VERSION:
+            case NAN_STATUS_INVALID_MSG_LEN:
+            case NAN_STATUS_INVALID_MSG_ID:
+            case NAN_STATUS_INVALID_HANDLE:
+                return WifiNanSessionCallback.REASON_OTHER;
+            case NAN_STATUS_NO_SPACE_AVAILABLE:
+                return WifiNanSessionCallback.REASON_NO_RESOURCES;
+            case NAN_STATUS_INVALID_PUBLISH_TYPE:
+            case NAN_STATUS_INVALID_TX_TYPE:
+            case NAN_STATUS_INVALID_MATCH_ALGORITHM:
+                return WifiNanSessionCallback.REASON_INVALID_ARGS;
+            case NAN_STATUS_DISABLE_IN_PROGRESS:
+            case NAN_STATUS_INVALID_TLV_LEN:
+            case NAN_STATUS_INVALID_TLV_TYPE:
+            case NAN_STATUS_MISSING_TLV_TYPE:
+            case NAN_STATUS_INVALID_TOTAL_TLVS_LEN:
+            case NAN_STATUS_INVALID_MATCH_HANDLE:
+            case NAN_STATUS_INVALID_TLV_VALUE:
+            case NAN_STATUS_INVALID_TX_PRIORITY:
+            case NAN_STATUS_INVALID_CONNECTION_MAP:
+            case NAN_STATUS_INVALID_TCA_ID:
+            case NAN_STATUS_INVALID_STATS_ID:
+            case NAN_STATUS_NAN_NOT_ALLOWED:
+                return WifiNanSessionCallback.REASON_OTHER;
+            case NAN_STATUS_NO_OTA_ACK:
+            case NAN_STATUS_TX_FAIL:
+                return WifiNanSessionCallback.REASON_TX_FAIL;
+        }
+
+        return WifiNanSessionCallback.REASON_OTHER;
     }
 
     // callback from native
@@ -463,14 +818,17 @@
                     "onNanNotifyResponse: transactionId=" + transactionId + ", responseType="
                     + responseType + ", status=" + status + ", value=" + value);
         }
+        WifiNanStateManager stateMgr = WifiNanStateManager.getInstance();
 
         switch (responseType) {
             case NAN_RESPONSE_ENABLED:
+                /* fall through */
+            case NAN_RESPONSE_CONFIG:
                 if (status == NAN_STATUS_SUCCESS) {
-                    WifiNanStateManager.getInstance().onConfigCompleted(transactionId);
+                    stateMgr.onConfigSuccessResponse(transactionId);
                 } else {
-                    WifiNanStateManager.getInstance().onConfigFailed(transactionId,
-                            translateHalStatusToPublicStatus(status));
+                    stateMgr.onConfigFailedResponse(transactionId,
+                            translateHalStatusToNanEventCallbackReason(status));
                 }
                 break;
             case NAN_RESPONSE_PUBLISH_CANCEL:
@@ -481,10 +839,10 @@
                 break;
             case NAN_RESPONSE_TRANSMIT_FOLLOWUP:
                 if (status == NAN_STATUS_SUCCESS) {
-                    WifiNanStateManager.getInstance().onMessageSendSuccess(transactionId);
+                    stateMgr.onMessageSendQueuedSuccessResponse(transactionId);
                 } else {
-                    WifiNanStateManager.getInstance().onMessageSendFail(transactionId,
-                            translateHalStatusToPublicStatus(status));
+                    stateMgr.onMessageSendQueuedFailResponse(transactionId,
+                            translateHalStatusToNanSessionCallbackReason(status));
                 }
                 break;
             case NAN_RESPONSE_SUBSCRIBE_CANCEL:
@@ -493,9 +851,43 @@
                             + status + ", value=" + value);
                 }
                 break;
+            case NAN_RESPONSE_DP_INTERFACE_CREATE:
+                if (status != NAN_STATUS_SUCCESS) {
+                    Log.e(TAG,
+                            "onNanNotifyResponse: NAN_RESPONSE_DP_INTERFACE_CREATE error - status="
+                                    + status + ", value=" + value);
+                }
+                stateMgr.onCreateDataPathInterfaceResponse(transactionId,
+                        status == NAN_STATUS_SUCCESS, status);
+                break;
+            case NAN_RESPONSE_DP_INTERFACE_DELETE:
+                if (status != NAN_STATUS_SUCCESS) {
+                    Log.e(TAG,
+                            "onNanNotifyResponse: NAN_RESPONSE_DP_INTERFACE_DELETE error - status="
+                                    + status + ", value=" + value);
+                }
+                stateMgr.onDeleteDataPathInterfaceResponse(transactionId,
+                        status == NAN_STATUS_SUCCESS, status);
+                break;
+            case NAN_RESPONSE_DP_RESPONDER_RESPONSE:
+                if (status != NAN_STATUS_SUCCESS) {
+                    Log.e(TAG,
+                            "onNanNotifyResponse: NAN_RESPONSE_DP_RESPONDER_RESPONSE error - "
+                                    + "status=" + status + ", value=" + value);
+                }
+                stateMgr.onRespondToDataPathSetupRequestResponse(transactionId,
+                        status == NAN_STATUS_SUCCESS, status);
+                break;
+            case NAN_RESPONSE_DP_END:
+                if (status != NAN_STATUS_SUCCESS) {
+                    Log.e(TAG, "onNanNotifyResponse: NAN_RESPONSE_DP_END error - status=" + status
+                            + ", value=" + value);
+                }
+                stateMgr.onEndDataPathResponse(transactionId, status == NAN_STATUS_SUCCESS,
+                        status);
+                break;
             default:
-                WifiNanStateManager.getInstance().onUnknownTransaction(responseType, transactionId,
-                        translateHalStatusToPublicStatus(status));
+                Log.e(TAG, "onNanNotifyResponse: unclassified responseType=" + responseType);
                 break;
         }
     }
@@ -512,23 +904,25 @@
         switch (responseType) {
             case NAN_RESPONSE_PUBLISH:
                 if (status == NAN_STATUS_SUCCESS) {
-                    WifiNanStateManager.getInstance().onPublishSuccess(transactionId, pubSubId);
+                    WifiNanStateManager.getInstance().onSessionConfigSuccessResponse(transactionId,
+                            true, pubSubId);
                 } else {
-                    WifiNanStateManager.getInstance().onPublishFail(transactionId,
-                            translateHalStatusToPublicStatus(status));
+                    WifiNanStateManager.getInstance().onSessionConfigFailResponse(transactionId,
+                            true, translateHalStatusToNanSessionCallbackReason(status));
                 }
                 break;
             case NAN_RESPONSE_SUBSCRIBE:
                 if (status == NAN_STATUS_SUCCESS) {
-                    WifiNanStateManager.getInstance().onSubscribeSuccess(transactionId, pubSubId);
+                    WifiNanStateManager.getInstance().onSessionConfigSuccessResponse(transactionId,
+                            false, pubSubId);
                 } else {
-                    WifiNanStateManager.getInstance().onSubscribeFail(transactionId,
-                            translateHalStatusToPublicStatus(status));
+                    WifiNanStateManager.getInstance().onSessionConfigFailResponse(transactionId,
+                            false, translateHalStatusToNanSessionCallbackReason(status));
                 }
                 break;
             default:
-                WifiNanStateManager.getInstance().onUnknownTransaction(responseType, transactionId,
-                        translateHalStatusToPublicStatus(status));
+                Log.wtf(TAG, "onNanNotifyResponsePublishSubscribe: unclassified responseType="
+                        + responseType);
                 break;
         }
     }
@@ -536,18 +930,34 @@
     private static void onNanNotifyResponseCapabilities(short transactionId, int status, int value,
             Capabilities capabilities) {
         if (VDBG) {
-            Log.v(TAG, "onNanNotifyResponsePublishSubscribe: transactionId=" + transactionId
+            Log.v(TAG, "onNanNotifyResponseCapabilities: transactionId=" + transactionId
                     + ", status=" + status + ", value=" + value + ", capabilities=" + capabilities);
         }
 
         if (status == NAN_STATUS_SUCCESS) {
-            WifiNanStateManager.getInstance().onCapabilitiesUpdate(transactionId, capabilities);
+            WifiNanStateManager.getInstance().onCapabilitiesUpdateResponse(transactionId,
+                    capabilities);
         } else {
             Log.e(TAG,
                     "onNanNotifyResponseCapabilities: error status=" + status + ", value=" + value);
         }
     }
 
+    private static void onNanNotifyResponseDataPathInitiate(short transactionId, int status,
+            int value, int ndpId) {
+        if (VDBG) {
+            Log.v(TAG,
+                    "onNanNotifyResponseDataPathInitiate: transactionId=" + transactionId
+                            + ", status=" + status + ", value=" + value + ", ndpId=" + ndpId);
+        }
+        if (status == NAN_STATUS_SUCCESS) {
+            WifiNanStateManager.getInstance().onInitiateDataPathResponseSuccess(transactionId,
+                    ndpId);
+        } else {
+            WifiNanStateManager.getInstance().onInitiateDataPathResponseFail(transactionId, status);
+        }
+    }
+
     public static final int NAN_EVENT_ID_DISC_MAC_ADDR = 0;
     public static final int NAN_EVENT_ID_STARTED_CLUSTER = 1;
     public static final int NAN_EVENT_ID_JOINED_CLUSTER = 2;
@@ -560,13 +970,13 @@
         }
 
         if (eventType == NAN_EVENT_ID_DISC_MAC_ADDR) {
-            WifiNanStateManager.getInstance().onInterfaceAddressChange(mac);
+            WifiNanStateManager.getInstance().onInterfaceAddressChangeNotification(mac);
         } else if (eventType == NAN_EVENT_ID_STARTED_CLUSTER) {
-            WifiNanStateManager.getInstance()
-                    .onClusterChange(WifiNanClientState.CLUSTER_CHANGE_EVENT_STARTED, mac);
+            WifiNanStateManager.getInstance().onClusterChangeNotification(
+                    WifiNanClientState.CLUSTER_CHANGE_EVENT_STARTED, mac);
         } else if (eventType == NAN_EVENT_ID_JOINED_CLUSTER) {
-            WifiNanStateManager.getInstance()
-                    .onClusterChange(WifiNanClientState.CLUSTER_CHANGE_EVENT_JOINED, mac);
+            WifiNanStateManager.getInstance().onClusterChangeNotification(
+                    WifiNanClientState.CLUSTER_CHANGE_EVENT_JOINED, mac);
         } else {
             Log.w(TAG, "onDiscoveryEngineEvent: invalid eventType=" + eventType);
         }
@@ -579,11 +989,12 @@
         if (VDBG) {
             Log.v(TAG, "onMatchEvent: pubSubId=" + pubSubId + ", requestorInstanceId="
                     + requestorInstanceId + ", mac=" + String.valueOf(HexEncoding.encode(mac))
-                    + ", serviceSpecificInfo=" + serviceSpecificInfo + ", matchFilterLength="
-                    + matchFilterLength + ", matchFilter=" + matchFilter);
+                    + ", serviceSpecificInfo=" + Arrays.toString(serviceSpecificInfo)
+                    + ", matchFilterLength=" + matchFilterLength + ", matchFilter="
+                    + Arrays.toString(matchFilter));
         }
 
-        WifiNanStateManager.getInstance().onMatch(pubSubId, requestorInstanceId, mac,
+        WifiNanStateManager.getInstance().onMatchNotification(pubSubId, requestorInstanceId, mac,
                 serviceSpecificInfo, serviceSpecificInfoLength, matchFilter, matchFilterLength);
     }
 
@@ -591,8 +1002,8 @@
     private static void onPublishTerminated(int publishId, int status) {
         if (VDBG) Log.v(TAG, "onPublishTerminated: publishId=" + publishId + ", status=" + status);
 
-        WifiNanStateManager.getInstance().onPublishTerminated(publishId,
-                translateHalStatusToPublicStatus(status));
+        WifiNanStateManager.getInstance().onSessionTerminatedNotification(publishId,
+                translateHalStatusToNanSessionCallbackTerminate(status), true);
     }
 
     // callback from native
@@ -601,8 +1012,8 @@
             Log.v(TAG, "onSubscribeTerminated: subscribeId=" + subscribeId + ", status=" + status);
         }
 
-        WifiNanStateManager.getInstance().onSubscribeTerminated(subscribeId,
-                translateHalStatusToPublicStatus(status));
+        WifiNanStateManager.getInstance().onSessionTerminatedNotification(subscribeId,
+                translateHalStatusToNanSessionCallbackTerminate(status), false);
     }
 
     // callback from native
@@ -614,14 +1025,62 @@
                     + ", messageLength=" + messageLength);
         }
 
-        WifiNanStateManager.getInstance().onMessageReceived(pubSubId, requestorInstanceId, mac,
-                message, messageLength);
+        WifiNanStateManager.getInstance().onMessageReceivedNotification(pubSubId,
+                requestorInstanceId, mac, message, messageLength);
     }
 
     // callback from native
     private static void onDisabledEvent(int status) {
         if (VDBG) Log.v(TAG, "onDisabledEvent: status=" + status);
 
-        WifiNanStateManager.getInstance().onNanDown(translateHalStatusToPublicStatus(status));
+        WifiNanStateManager.getInstance()
+                .onNanDownNotification(translateHalStatusToNanEventCallbackReason(status));
+    }
+
+    // callback from native
+    private static void onTransmitFollowupEvent(short transactionId, int reason) {
+        if (VDBG) {
+            Log.v(TAG, "onTransmitFollowupEvent: transactionId=" + transactionId + ", reason="
+                    + reason);
+        }
+
+        if (reason == NAN_STATUS_SUCCESS) {
+            WifiNanStateManager.getInstance().onMessageSendSuccessNotification(transactionId);
+        } else {
+            WifiNanStateManager.getInstance().onMessageSendFailNotification(transactionId,
+                    translateHalStatusToNanSessionCallbackReason(reason));
+        }
+    }
+
+    private static void onDataPathRequest(int pubSubId, byte[] mac, int ndpId, byte[] message, int
+            messageLength) {
+        if (VDBG) {
+            Log.v(TAG, "onDataPathRequest: pubSubId=" + pubSubId + ", mac=" + String.valueOf(
+                    HexEncoding.encode(mac)) + ", ndpId=" + ndpId + ", messageLength="
+                    + messageLength);
+        }
+
+        WifiNanStateManager.getInstance()
+                .onDataPathRequestNotification(pubSubId, mac, ndpId, message, messageLength);
+    }
+
+    private static void onDataPathConfirm(int ndpId, byte[] mac, boolean accept, int reason, byte[]
+            message, int messageLength) {
+        if (VDBG) {
+            Log.v(TAG, "onDataPathConfirm: ndpId=" + ndpId + ", mac=" + String.valueOf(HexEncoding
+                    .encode(mac)) + ", accept=" + accept + ", reason=" + reason + ", "
+                    + "messageLength=" + messageLength);
+        }
+
+        WifiNanStateManager.getInstance()
+                .onDataPathConfirmNotification(ndpId, mac, accept, reason, message, messageLength);
+    }
+
+    private static void onDataPathEnd(int ndpId) {
+        if (VDBG) {
+            Log.v(TAG, "onDataPathEndNotification: ndpId=" + ndpId);
+        }
+
+        WifiNanStateManager.getInstance().onDataPathEndNotification(ndpId);
     }
 }
diff --git a/service/java/com/android/server/wifi/nan/WifiNanRttStateManager.java b/service/java/com/android/server/wifi/nan/WifiNanRttStateManager.java
new file mode 100644
index 0000000..4e15486
--- /dev/null
+++ b/service/java/com/android/server/wifi/nan/WifiNanRttStateManager.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.nan;
+
+import android.content.Context;
+import android.net.wifi.IRttManager;
+import android.net.wifi.RttManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.AsyncChannel;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Arrays;
+
+
+/**
+ * Manages interactions between the NAN and the RTT service. Duplicates some of the functionality
+ * of the RttManager.
+ */
+public class WifiNanRttStateManager {
+    private static final String TAG = "WifiNanRttStateManager";
+
+    private static final boolean DBG = false;
+    private static final boolean VDBG = false; // STOPSHIP if true
+
+    private final SparseArray<WifiNanClientState> mPendingOperations = new SparseArray<>();
+    private AsyncChannel mAsyncChannel;
+
+    /**
+     * Initializes the connection to the RTT service.
+     */
+    public void start(Context context, Looper looper) {
+        if (VDBG) Log.v(TAG, "start()");
+
+        IBinder b = ServiceManager.getService(Context.WIFI_RTT_SERVICE);
+        IRttManager service = IRttManager.Stub.asInterface(b);
+        if (service == null) {
+            Log.e(TAG, "start(): not able to get WIFI_RTT_SERVICE");
+            return;
+        }
+
+        startWithRttService(context, looper, service);
+    }
+
+    /**
+     * Initializes the connection to the RTT service.
+     */
+    @VisibleForTesting
+    public void startWithRttService(Context context, Looper looper, IRttManager service) {
+        Messenger messenger;
+        try {
+            messenger = service.getMessenger();
+        } catch (RemoteException e) {
+            Log.e(TAG, "start(): not able to getMessenger() of WIFI_RTT_SERVICE");
+            return;
+        }
+
+        mAsyncChannel = new AsyncChannel();
+        mAsyncChannel.connect(context, new NanRttHandler(looper), messenger);
+    }
+
+    private WifiNanClientState getAndRemovePendingOperationClient(int rangingId) {
+        WifiNanClientState client = mPendingOperations.get(rangingId);
+        mPendingOperations.delete(rangingId);
+        return client;
+    }
+
+    /**
+     * Start a ranging operation for the client + peer MAC.
+     */
+    public void startRanging(int rangingId, WifiNanClientState client,
+                             RttManager.RttParams[] params) {
+        if (VDBG) {
+            Log.v(TAG, "startRanging: rangingId=" + rangingId + ", parms="
+                    + Arrays.toString(params));
+        }
+
+        if (mAsyncChannel == null) {
+            Log.d(TAG, "startRanging(): AsyncChannel to RTT service not configured - failing");
+            client.onRangingFailure(rangingId, RttManager.REASON_NOT_AVAILABLE,
+                    "NAN service not able to configure connection to RTT service");
+            return;
+        }
+
+        mPendingOperations.put(rangingId, client);
+        RttManager.ParcelableRttParams pparams = new RttManager.ParcelableRttParams(params);
+        mAsyncChannel.sendMessage(RttManager.CMD_OP_START_RANGING, 0, rangingId, pparams);
+    }
+
+    private class NanRttHandler extends Handler {
+        NanRttHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (VDBG) Log.v(TAG, "handleMessage(): " + msg.what);
+
+            // channel configuration messages
+            switch (msg.what) {
+                case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
+                    if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
+                        mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
+                    } else {
+                        Log.e(TAG, "Failed to set up channel connection to RTT service");
+                        mAsyncChannel = null;
+                    }
+                    return;
+                case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED:
+                    /* NOP */
+                    return;
+                case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
+                    Log.e(TAG, "Channel connection to RTT service lost");
+                    mAsyncChannel = null;
+                    return;
+            }
+
+            // RTT-specific messages
+            WifiNanClientState client = getAndRemovePendingOperationClient(msg.arg2);
+            if (client == null) {
+                Log.e(TAG, "handleMessage(): RTT message (" + msg.what
+                        + ") -- cannot find registered pending operation client for ID "
+                        + msg.arg2);
+                return;
+            }
+
+            switch (msg.what) {
+                case RttManager.CMD_OP_SUCCEEDED: {
+                    int rangingId = msg.arg2;
+                    RttManager.ParcelableRttResults results = (RttManager.ParcelableRttResults)
+                            msg.obj;
+                    if (VDBG) {
+                        Log.v(TAG, "CMD_OP_SUCCEEDED: rangingId=" + rangingId + ", results="
+                                + results);
+                    }
+                    for (int i = 0; i < results.mResults.length; ++i) {
+                        /*
+                         * TODO: store peer ID rather than null in the return result.
+                         */
+                        results.mResults[i].bssid = null;
+                    }
+                    client.onRangingSuccess(rangingId, results);
+                    break;
+                }
+                case RttManager.CMD_OP_FAILED: {
+                    int rangingId = msg.arg2;
+                    int reason = msg.arg1;
+                    String description = ((Bundle) msg.obj).getString(RttManager.DESCRIPTION_KEY);
+                    if (VDBG) {
+                        Log.v(TAG, "CMD_OP_FAILED: rangingId=" + rangingId + ", reason=" + reason
+                                + ", description=" + description);
+                    }
+                    client.onRangingFailure(rangingId, reason, description);
+                    break;
+                }
+                case RttManager.CMD_OP_ABORTED: {
+                    int rangingId = msg.arg2;
+                    if (VDBG) {
+                        Log.v(TAG, "CMD_OP_ABORTED: rangingId=" + rangingId);
+                    }
+                    client.onRangingAborted(rangingId);
+                    break;
+                }
+                default:
+                    Log.e(TAG, "handleMessage(): ignoring message " + msg.what);
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Dump the internal state of the class.
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("WifiNanRttStateManager:");
+        pw.println("  mPendingOperations: [" + mPendingOperations + "]");
+    }
+}
diff --git a/service/java/com/android/server/wifi/nan/WifiNanService.java b/service/java/com/android/server/wifi/nan/WifiNanService.java
index b5920f2..6589067 100644
--- a/service/java/com/android/server/wifi/nan/WifiNanService.java
+++ b/service/java/com/android/server/wifi/nan/WifiNanService.java
@@ -21,6 +21,10 @@
 
 import com.android.server.SystemService;
 
+/**
+ * Service implementing Wi-Fi NAN functionality. Delegates actual interface
+ * implementation to WifiNanServiceImpl.
+ */
 public final class WifiNanService extends SystemService {
     private static final String TAG = "WifiNanService";
     final WifiNanServiceImpl mImpl;
@@ -40,6 +44,8 @@
     public void onBootPhase(int phase) {
         if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
             mImpl.start();
+        } else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
+            mImpl.startLate();
         }
     }
 }
diff --git a/service/java/com/android/server/wifi/nan/WifiNanServiceImpl.java b/service/java/com/android/server/wifi/nan/WifiNanServiceImpl.java
index deefe94..140c6fd 100644
--- a/service/java/com/android/server/wifi/nan/WifiNanServiceImpl.java
+++ b/service/java/com/android/server/wifi/nan/WifiNanServiceImpl.java
@@ -18,24 +18,33 @@
 
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.net.wifi.RttManager;
 import android.net.wifi.nan.ConfigRequest;
-import android.net.wifi.nan.IWifiNanEventListener;
+import android.net.wifi.nan.IWifiNanEventCallback;
 import android.net.wifi.nan.IWifiNanManager;
-import android.net.wifi.nan.IWifiNanSessionListener;
-import android.net.wifi.nan.PublishData;
-import android.net.wifi.nan.PublishSettings;
-import android.net.wifi.nan.SubscribeData;
-import android.net.wifi.nan.SubscribeSettings;
+import android.net.wifi.nan.IWifiNanSessionCallback;
+import android.net.wifi.nan.PublishConfig;
+import android.net.wifi.nan.SubscribeConfig;
+import android.net.wifi.nan.WifiNanEventCallback;
+import android.net.wifi.nan.WifiNanSession;
 import android.os.Binder;
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 import android.util.SparseArray;
+import android.util.SparseIntArray;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.Arrays;
 
+/**
+ * Implementation of the IWifiNanManager AIDL interface. Performs validity
+ * (permission and clientID-UID mapping) checks and delegates execution to the
+ * WifiNanStateManager singleton handler. Limited state to feedback which has to
+ * be provided instantly: client and session IDs.
+ */
 public class WifiNanServiceImpl extends IWifiNanManager.Stub {
     private static final String TAG = "WifiNanService";
     private static final boolean DBG = false;
@@ -43,180 +52,336 @@
 
     private Context mContext;
     private WifiNanStateManager mStateManager;
-    private final boolean mNanSupported;
 
     private final Object mLock = new Object();
-    private final SparseArray<IBinder.DeathRecipient> mDeathRecipientsByUid = new SparseArray<>();
-    private int mNextNetworkRequestToken = 1;
-    private int mNextSessionId = 1;
+    private final SparseArray<IBinder.DeathRecipient> mDeathRecipientsByClientId =
+            new SparseArray<>();
+    private int mNextClientId = 1;
+    private int mNextRangingId = 1;
+    private final SparseIntArray mUidByClientId = new SparseIntArray();
 
     public WifiNanServiceImpl(Context context) {
         mContext = context.getApplicationContext();
-
-        mNanSupported = mContext.getPackageManager()
-                .hasSystemFeature(PackageManager.FEATURE_WIFI_NAN);
-        if (DBG) Log.w(TAG, "WifiNanServiceImpl: mNanSupported=" + mNanSupported);
-
         mStateManager = WifiNanStateManager.getInstance();
     }
 
+    /**
+     * Proxy for the final native call of the parent class. Enables mocking of
+     * the function.
+     */
+    public int getMockableCallingUid() {
+        return getCallingUid();
+    }
+
+    /**
+     * Start the service: allocate a new thread (for now), start the handlers of
+     * the components of the service.
+     */
     public void start() {
         Log.i(TAG, "Starting Wi-Fi NAN service");
 
-        // TODO: share worker thread with other Wi-Fi handlers
+        // TODO: share worker thread with other Wi-Fi handlers (b/27924886)
         HandlerThread wifiNanThread = new HandlerThread("wifiNanService");
         wifiNanThread.start();
 
-        mStateManager.start(wifiNanThread.getLooper());
+        mStateManager.start(mContext, wifiNanThread.getLooper());
+    }
+
+    /**
+     * Start/initialize portions of the service which require the boot stage to be complete.
+     */
+    public void startLate() {
+        Log.i(TAG, "Late initialization of Wi-Fi NAN service");
+
+        mStateManager.startLate();
     }
 
     @Override
-    public void connect(final IBinder binder, IWifiNanEventListener listener, int events) {
+    public void enableUsage() {
         enforceAccessPermission();
         enforceChangePermission();
+        /*
+         * TODO: enforce additional permissions b/27696149.
+         */
 
-        final int uid = getCallingUid();
+        mStateManager.enableUsage();
+    }
 
-        if (VDBG) Log.v(TAG, "connect: uid=" + uid);
+    @Override
+    public void disableUsage() {
+        enforceAccessPermission();
+        enforceChangePermission();
+        /*
+         * TODO: enforce additional permissions b/27696149.
+         */
 
+        mStateManager.disableUsage();
+
+        /*
+         * Potential leak (b/27796984) since we keep app information here (uid,
+         * binder-link-to-death), while clearing all state information. However:
+         * (1) can't clear all information since don't have binder, (2)
+         * information will clear once app dies, (3) allows us to do security
+         * checks in the future.
+         */
+    }
+
+    @Override
+    public boolean isUsageEnabled() {
+        enforceAccessPermission();
+
+        return mStateManager.isUsageEnabled();
+    }
+
+    @Override
+    public int connect(final IBinder binder, IWifiNanEventCallback callback,
+            ConfigRequest configRequest) {
+        enforceAccessPermission();
+        enforceChangePermission();
+        if (callback == null) {
+            throw new IllegalArgumentException("Callback must not be null");
+        }
+        if (binder == null) {
+            throw new IllegalArgumentException("Binder must not be null");
+        }
+
+        if (configRequest != null) {
+            /*
+             * TODO: enforce additional permissions if configuration is
+             * non-standard (i.e. the system API). (b/27696149)
+             */
+        } else {
+            configRequest = new ConfigRequest.Builder().build();
+        }
+        configRequest.validate();
+
+        final int uid = getMockableCallingUid();
+
+        final int clientId;
+        synchronized (mLock) {
+            clientId = mNextClientId++;
+        }
+
+        if (VDBG) {
+            Log.v(TAG, "connect: uid=" + uid + ", clientId=" + clientId + ", configRequest"
+                    + configRequest);
+        }
 
         IBinder.DeathRecipient dr = new IBinder.DeathRecipient() {
             @Override
             public void binderDied() {
-                if (DBG) Log.d(TAG, "binderDied: uid=" + uid);
+                if (DBG) Log.d(TAG, "binderDied: clientId=" + clientId);
                 binder.unlinkToDeath(this, 0);
 
                 synchronized (mLock) {
-                    mDeathRecipientsByUid.delete(uid);
+                    mDeathRecipientsByClientId.delete(clientId);
+                    mUidByClientId.delete(clientId);
                 }
 
-                mStateManager.disconnect(uid);
+                mStateManager.disconnect(clientId);
             }
         };
-        synchronized (mLock) {
-            mDeathRecipientsByUid.put(uid, dr);
-        }
+
         try {
             binder.linkToDeath(dr, 0);
         } catch (RemoteException e) {
-            Log.w(TAG, "Error on linkToDeath - " + e);
+            Log.e(TAG, "Error on linkToDeath - " + e);
+            try {
+                callback.onConnectFail(WifiNanEventCallback.REASON_OTHER);
+            } catch (RemoteException e1) {
+                Log.e(TAG, "Error on onConnectFail()");
+            }
+            return 0;
         }
 
-        mStateManager.connect(uid, listener, events);
+        synchronized (mLock) {
+            mDeathRecipientsByClientId.put(clientId, dr);
+            mUidByClientId.put(clientId, uid);
+        }
+
+        mStateManager.connect(clientId, uid, callback, configRequest);
+
+        return clientId;
     }
 
     @Override
-    public void disconnect(IBinder binder) {
+    public void disconnect(int clientId, IBinder binder) {
         enforceAccessPermission();
         enforceChangePermission();
 
-        int uid = getCallingUid();
+        int uid = getMockableCallingUid();
+        enforceClientValidity(uid, clientId);
+        if (VDBG) Log.v(TAG, "disconnect: uid=" + uid + ", clientId=" + clientId);
 
-        if (VDBG) Log.v(TAG, "disconnect: uid=" + uid);
+        if (binder == null) {
+            throw new IllegalArgumentException("Binder must not be null");
+        }
 
         synchronized (mLock) {
-            IBinder.DeathRecipient dr = mDeathRecipientsByUid.get(uid);
+            IBinder.DeathRecipient dr = mDeathRecipientsByClientId.get(clientId);
             if (dr != null) {
                 binder.unlinkToDeath(dr, 0);
-                mDeathRecipientsByUid.delete(uid);
+                mDeathRecipientsByClientId.delete(clientId);
             }
+            mUidByClientId.delete(clientId);
         }
 
-        mStateManager.disconnect(uid);
+        mStateManager.disconnect(clientId);
     }
 
     @Override
-    public void requestConfig(ConfigRequest configRequest) {
+    public void terminateSession(int clientId, int sessionId) {
         enforceAccessPermission();
         enforceChangePermission();
 
+        int uid = getMockableCallingUid();
+        enforceClientValidity(uid, clientId);
+        if (VDBG) {
+            Log.v(TAG, "terminateSession: sessionId=" + sessionId + ", uid=" + uid + ", clientId="
+                    + clientId);
+        }
+
+        mStateManager.terminateSession(clientId, sessionId);
+    }
+
+    @Override
+    public void publish(int clientId, PublishConfig publishConfig,
+            IWifiNanSessionCallback callback) {
+        enforceAccessPermission();
+        enforceChangePermission();
+
+        if (callback == null) {
+            throw new IllegalArgumentException("Callback must not be null");
+        }
+        if (publishConfig == null) {
+            throw new IllegalArgumentException("PublishConfig must not be null");
+        }
+        publishConfig.validate();
+
+        int uid = getMockableCallingUid();
+        enforceClientValidity(uid, clientId);
+        if (VDBG) {
+            Log.v(TAG, "publish: uid=" + uid + ", clientId=" + clientId + ", publishConfig="
+                    + publishConfig + ", callback=" + callback);
+        }
+
+        mStateManager.publish(clientId, publishConfig, callback);
+    }
+
+    @Override
+    public void updatePublish(int clientId, int sessionId, PublishConfig publishConfig) {
+        enforceAccessPermission();
+        enforceChangePermission();
+
+        if (publishConfig == null) {
+            throw new IllegalArgumentException("PublishConfig must not be null");
+        }
+        publishConfig.validate();
+
+        int uid = getMockableCallingUid();
+        enforceClientValidity(uid, clientId);
+        if (VDBG) {
+            Log.v(TAG, "updatePublish: uid=" + uid + ", clientId=" + clientId + ", sessionId="
+                    + sessionId + ", config=" + publishConfig);
+        }
+
+        mStateManager.updatePublish(clientId, sessionId, publishConfig);
+    }
+
+    @Override
+    public void subscribe(int clientId, SubscribeConfig subscribeConfig,
+            IWifiNanSessionCallback callback) {
+        enforceAccessPermission();
+        enforceChangePermission();
+
+        if (callback == null) {
+            throw new IllegalArgumentException("Callback must not be null");
+        }
+        if (subscribeConfig == null) {
+            throw new IllegalArgumentException("SubscribeConfig must not be null");
+        }
+        subscribeConfig.validate();
+
+        int uid = getMockableCallingUid();
+        enforceClientValidity(uid, clientId);
+        if (VDBG) {
+            Log.v(TAG, "subscribe: uid=" + uid + ", clientId=" + clientId + ", config="
+                    + subscribeConfig + ", callback=" + callback);
+        }
+
+        mStateManager.subscribe(clientId, subscribeConfig, callback);
+    }
+
+    @Override
+    public void updateSubscribe(int clientId, int sessionId, SubscribeConfig subscribeConfig) {
+        enforceAccessPermission();
+        enforceChangePermission();
+
+        if (subscribeConfig == null) {
+            throw new IllegalArgumentException("SubscribeConfig must not be null");
+        }
+        subscribeConfig.validate();
+
+        int uid = getMockableCallingUid();
+        enforceClientValidity(uid, clientId);
+        if (VDBG) {
+            Log.v(TAG, "updateSubscribe: uid=" + uid + ", clientId=" + clientId + ", sessionId="
+                    + sessionId + ", config=" + subscribeConfig);
+        }
+
+        mStateManager.updateSubscribe(clientId, sessionId, subscribeConfig);
+    }
+
+    @Override
+    public void sendMessage(int clientId, int sessionId, int peerId, byte[] message,
+            int messageLength, int messageId, int retryCount) {
+        enforceAccessPermission();
+        enforceChangePermission();
+
+        if (messageLength != 0 && (message == null || message.length < messageLength)) {
+            throw new IllegalArgumentException(
+                    "Non-matching combination of message and messageLength");
+        }
+        if (retryCount < 0 || retryCount > WifiNanSession.MAX_SEND_RETRY_COUNT) {
+            throw new IllegalArgumentException("Invalid 'retryCount' must be non-negative "
+                    + "and <= WifiNanSession.MAX_SEND_RETRY_COUNT");
+        }
+
+        int uid = getMockableCallingUid();
+        enforceClientValidity(uid, clientId);
         if (VDBG) {
             Log.v(TAG,
-                    "requestConfig: uid=" + getCallingUid() + ", configRequest=" + configRequest);
+                    "sendMessage: sessionId=" + sessionId + ", uid=" + uid + ", clientId="
+                            + clientId + ", peerId=" + peerId + ", messageLength=" + messageLength
+                            + ", messageId=" + messageId + ", retryCount=" + retryCount);
         }
 
-        mStateManager.requestConfig(getCallingUid(), configRequest);
+        mStateManager.sendMessage(clientId, sessionId, peerId, message, messageLength, messageId,
+                retryCount);
     }
 
     @Override
-    public void stopSession(int sessionId) {
+    public int startRanging(int clientId, int sessionId, RttManager.ParcelableRttParams params) {
         enforceAccessPermission();
-        enforceChangePermission();
+        enforceLocationPermission();
 
-        if (VDBG) Log.v(TAG, "stopSession: sessionId=" + sessionId + ", uid=" + getCallingUid());
+        int uid = getMockableCallingUid();
+        enforceClientValidity(uid, clientId);
+        if (VDBG) {
+            Log.v(TAG, "startRanging: clientId=" + clientId + ", sessionId=" + sessionId + ", "
+                    + ", parms=" + Arrays.toString(params.mParams));
+        }
 
-        mStateManager.stopSession(getCallingUid(), sessionId);
-    }
+        if (params.mParams.length == 0) {
+            throw new IllegalArgumentException("Empty ranging parameters");
+        }
 
-    @Override
-    public void destroySession(int sessionId) {
-        enforceAccessPermission();
-        enforceChangePermission();
-
-        if (VDBG) Log.v(TAG, "destroySession: sessionId=" + sessionId + ", uid=" + getCallingUid());
-
-        mStateManager.destroySession(getCallingUid(), sessionId);
-    }
-
-    @Override
-    public int createSession(IWifiNanSessionListener listener, int events) {
-        enforceAccessPermission();
-        enforceChangePermission();
-
-        if (VDBG) Log.v(TAG, "createSession: uid=" + getCallingUid());
-
-        int sessionId;
+        int rangingId;
         synchronized (mLock) {
-            sessionId = mNextSessionId++;
+            rangingId = mNextRangingId++;
         }
-
-        mStateManager.createSession(getCallingUid(), sessionId, listener, events);
-
-        return sessionId;
-    }
-
-    @Override
-    public void publish(int sessionId, PublishData publishData, PublishSettings publishSettings) {
-        enforceAccessPermission();
-        enforceChangePermission();
-
-        if (VDBG) {
-            Log.v(TAG, "publish: uid=" + getCallingUid() + ", sessionId=" + sessionId + ", data='"
-                    + publishData + "', settings=" + publishSettings);
-        }
-
-        mStateManager.publish(getCallingUid(), sessionId, publishData, publishSettings);
-    }
-
-    @Override
-    public void subscribe(int sessionId, SubscribeData subscribeData,
-            SubscribeSettings subscribeSettings) {
-        enforceAccessPermission();
-        enforceChangePermission();
-
-        if (VDBG) {
-            Log.v(TAG, "subscribe: uid=" + getCallingUid() + ", sessionId=" + sessionId + ", data='"
-                    + subscribeData + "', settings=" + subscribeSettings);
-        }
-
-        mStateManager.subscribe(getCallingUid(), sessionId, subscribeData, subscribeSettings);
-    }
-
-    @Override
-    public void sendMessage(int sessionId, int peerId, byte[] message, int messageLength,
-            int messageId) {
-        enforceAccessPermission();
-        enforceChangePermission();
-
-        if (VDBG) {
-            Log.v(TAG,
-                    "sendMessage: sessionId=" + sessionId + ", uid=" + getCallingUid() + ", peerId="
-                            + peerId + ", messageLength=" + messageLength + ", messageId="
-                            + messageId);
-        }
-
-        mStateManager.sendMessage(getCallingUid(), sessionId, peerId, message, messageLength,
-                messageId);
+        mStateManager.startRanging(clientId, sessionId, params.mParams, rangingId);
+        return rangingId;
     }
 
     @Override
@@ -228,13 +393,24 @@
             return;
         }
         pw.println("Wi-Fi NAN Service");
-        pw.println("  mNanSupported: " + mNanSupported);
-        pw.println("  mNextNetworkRequestToken: " + mNextNetworkRequestToken);
-        pw.println("  mNextSessionId: " + mNextSessionId);
-        pw.println("  mDeathRecipientsByUid: " + mDeathRecipientsByUid);
+        synchronized (mLock) {
+            pw.println("  mNextClientId: " + mNextClientId);
+            pw.println("  mDeathRecipientsByClientId: " + mDeathRecipientsByClientId);
+            pw.println("  mUidByClientId: " + mUidByClientId);
+        }
         mStateManager.dump(fd, pw, args);
     }
 
+    private void enforceClientValidity(int uid, int clientId) {
+        synchronized (mLock) {
+            int uidIndex = mUidByClientId.indexOfKey(clientId);
+            if (uidIndex < 0 || mUidByClientId.valueAt(uidIndex) != uid) {
+                throw new SecurityException("Attempting to use invalid uid+clientId mapping: uid="
+                        + uid + ", clientId=" + clientId);
+            }
+        }
+    }
+
     private void enforceAccessPermission() {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE, TAG);
     }
@@ -242,4 +418,9 @@
     private void enforceChangePermission() {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE, TAG);
     }
+
+    private void enforceLocationPermission() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION,
+                TAG);
+    }
 }
diff --git a/service/java/com/android/server/wifi/nan/WifiNanSessionState.java b/service/java/com/android/server/wifi/nan/WifiNanSessionState.java
index ea64403..56c7d3a 100644
--- a/service/java/com/android/server/wifi/nan/WifiNanSessionState.java
+++ b/service/java/com/android/server/wifi/nan/WifiNanSessionState.java
@@ -16,12 +16,10 @@
 
 package com.android.server.wifi.nan;
 
-import android.net.wifi.nan.IWifiNanSessionListener;
-import android.net.wifi.nan.PublishData;
-import android.net.wifi.nan.PublishSettings;
-import android.net.wifi.nan.SubscribeData;
-import android.net.wifi.nan.SubscribeSettings;
-import android.net.wifi.nan.WifiNanSessionListener;
+import android.net.wifi.nan.IWifiNanSessionCallback;
+import android.net.wifi.nan.PublishConfig;
+import android.net.wifi.nan.SubscribeConfig;
+import android.net.wifi.nan.WifiNanSessionCallback;
 import android.os.RemoteException;
 import android.util.Log;
 import android.util.SparseArray;
@@ -31,183 +29,172 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
+/**
+ * Manages the state of a single NAN discovery session (publish or subscribe).
+ * Primary state consists of a callback through which session callbacks are
+ * executed as well as state related to currently active discovery sessions:
+ * publish/subscribe ID, and MAC address caching (hiding) from clients.
+ */
 public class WifiNanSessionState {
     private static final String TAG = "WifiNanSessionState";
     private static final boolean DBG = false;
     private static final boolean VDBG = false; // STOPSHIP if true
 
+    private int mSessionId;
+    private int mPubSubId;
+    private IWifiNanSessionCallback mCallback;
+    private boolean mIsPublishSession;
+
     private final SparseArray<String> mMacByRequestorInstanceId = new SparseArray<>();
 
-    private int mSessionId;
-    private IWifiNanSessionListener mListener;
-    private int mEvents;
-
-    private boolean mPubSubIdValid = false;
-    private int mPubSubId;
-
-    private static final int SESSION_TYPE_NOT_INIT = 0;
-    private static final int SESSION_TYPE_PUBLISH = 1;
-    private static final int SESSION_TYPE_SUBSCRIBE = 2;
-    private int mSessionType = SESSION_TYPE_NOT_INIT;
-
-    public WifiNanSessionState(int sessionId, IWifiNanSessionListener listener, int events) {
+    public WifiNanSessionState(int sessionId, int pubSubId, IWifiNanSessionCallback callback,
+                               boolean isPublishSession) {
         mSessionId = sessionId;
-        mListener = listener;
-        mEvents = events;
-    }
-
-    public void destroy() {
-        stop(WifiNanStateManager.getInstance().createNextTransactionId());
-        if (mPubSubIdValid) {
-            mMacByRequestorInstanceId.clear();
-            mListener = null;
-            mPubSubIdValid = false;
-        }
+        mPubSubId = pubSubId;
+        mCallback = callback;
+        mIsPublishSession = isPublishSession;
     }
 
     public int getSessionId() {
         return mSessionId;
     }
 
+    public int getPubSubId() {
+        return mPubSubId;
+    }
+
+    public IWifiNanSessionCallback getCallback() {
+        return mCallback;
+    }
+
+    /**
+     * Return the MAC address (String) of the specified peer ID - or a null if no such address 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;
+    }
+
+    /**
+     * Destroy the current discovery session - stops publishing or subscribing
+     * if currently active.
+     */
+    public void terminate() {
+        mCallback = null;
+
+        if (mIsPublishSession) {
+            WifiNanNative.getInstance().stopPublish((short) 0, mPubSubId);
+        } else {
+            WifiNanNative.getInstance().stopSubscribe((short) 0, mPubSubId);
+        }
+    }
+
+    /**
+     * Indicates whether the publish/subscribe ID (a HAL ID) corresponds to this
+     * session.
+     *
+     * @param pubSubId The publish/subscribe HAL ID to be tested.
+     * @return true if corresponds to this session, false otherwise.
+     */
     public boolean isPubSubIdSession(int pubSubId) {
-        return mPubSubIdValid && mPubSubId == pubSubId;
+        return mPubSubId == pubSubId;
     }
 
-    public void publish(short transactionId, PublishData data, PublishSettings settings) {
-        if (mSessionType == SESSION_TYPE_SUBSCRIBE) {
-            throw new IllegalStateException("A SUBSCRIBE session is being used for publish");
+    /**
+     * Modify a publish discovery session.
+     *
+     * @param transactionId Transaction ID for the transaction - used in the
+     *            async callback to match with the original request.
+     * @param config Configuration of the publish session.
+     */
+    public boolean updatePublish(short transactionId, PublishConfig config) {
+        if (!mIsPublishSession) {
+            Log.e(TAG, "A SUBSCRIBE session is being used to publish");
+            try {
+                mCallback.onSessionConfigFail(WifiNanSessionCallback.REASON_OTHER);
+            } catch (RemoteException e) {
+                Log.e(TAG, "updatePublish: RemoteException=" + e);
+            }
+            return false;
         }
-        mSessionType = SESSION_TYPE_PUBLISH;
 
-        WifiNanNative.getInstance().publish(transactionId, mPubSubIdValid ? mPubSubId : 0, data,
-                settings);
+        return WifiNanNative.getInstance().publish(transactionId, mPubSubId, config);
     }
 
-    public void subscribe(short transactionId, SubscribeData data, SubscribeSettings settings) {
-        if (mSessionType == SESSION_TYPE_PUBLISH) {
-            throw new IllegalStateException("A PUBLISH session is being used for publish");
+    /**
+     * Modify a subscribe discovery session.
+     *
+     * @param transactionId Transaction ID for the transaction - used in the
+     *            async callback to match with the original request.
+     * @param config Configuration of the subscribe session.
+     */
+    public boolean updateSubscribe(short transactionId, SubscribeConfig config) {
+        if (mIsPublishSession) {
+            Log.e(TAG, "A PUBLISH session is being used to subscribe");
+            try {
+                mCallback.onSessionConfigFail(WifiNanSessionCallback.REASON_OTHER);
+            } catch (RemoteException e) {
+                Log.e(TAG, "updateSubscribe: RemoteException=" + e);
+            }
+            return false;
         }
-        mSessionType = SESSION_TYPE_SUBSCRIBE;
 
-        WifiNanNative.getInstance().subscribe(transactionId, mPubSubIdValid ? mPubSubId : 0, data,
-                settings);
+        return WifiNanNative.getInstance().subscribe(transactionId, mPubSubId, config);
     }
 
-    public void sendMessage(short transactionId, int peerId, byte[] message, int messageLength,
+    /**
+     * Send a message to a peer which is part of a discovery session.
+     *
+     * @param transactionId Transaction ID for the transaction - used in the
+     *            async callback to match with the original request.
+     * @param peerId ID of the peer. Obtained through previous communication (a
+     *            match indication).
+     * @param message Message byte array to send to the peer.
+     * @param messageLength Length of the message byte array.
+     * @param messageId A message ID provided by caller to be used in any
+     *            callbacks related to the message (success/failure).
+     */
+    public boolean sendMessage(short transactionId, int peerId, byte[] message, int messageLength,
             int messageId) {
-        if (!mPubSubIdValid) {
-            Log.e(TAG, "sendMessage: attempting to send a message on a non-live session "
-                    + "(no successful publish or subscribe");
-            onMessageSendFail(messageId, WifiNanSessionListener.FAIL_REASON_NO_MATCH_SESSION);
-            return;
-        }
-
         String peerMacStr = mMacByRequestorInstanceId.get(peerId);
         if (peerMacStr == null) {
             Log.e(TAG, "sendMessage: attempting to send a message to an address which didn't "
                     + "match/contact us");
-            onMessageSendFail(messageId, WifiNanSessionListener.FAIL_REASON_NO_MATCH_SESSION);
-            return;
+            try {
+                mCallback.onMessageSendFail(messageId,
+                        WifiNanSessionCallback.REASON_NO_MATCH_SESSION);
+            } catch (RemoteException e) {
+                Log.e(TAG, "sendMessage: RemoteException=" + e);
+            }
+            return false;
         }
         byte[] peerMac = HexEncoding.decode(peerMacStr.toCharArray(), false);
 
-        WifiNanNative.getInstance().sendMessage(transactionId, mPubSubId, peerId, peerMac, message,
-                messageLength);
+        return WifiNanNative.getInstance().sendMessage(transactionId, mPubSubId, peerId, peerMac,
+                message, messageLength, messageId);
     }
 
-    public void stop(short transactionId) {
-        if (!mPubSubIdValid || mSessionType == SESSION_TYPE_NOT_INIT) {
-            Log.e(TAG, "sendMessage: attempting to stop pub/sub on a non-live session (no "
-                    + "successful publish or subscribe");
-            return;
-        }
-
-        if (mSessionType == SESSION_TYPE_PUBLISH) {
-            WifiNanNative.getInstance().stopPublish(transactionId, mPubSubId);
-        } else if (mSessionType == SESSION_TYPE_SUBSCRIBE) {
-            WifiNanNative.getInstance().stopSubscribe(transactionId, mPubSubId);
-        }
-    }
-
-    public void onPublishSuccess(int publishId) {
-        mPubSubId = publishId;
-        mPubSubIdValid = true;
-    }
-
-    public void onPublishFail(int status) {
-        mPubSubIdValid = false;
-        try {
-            if (mListener != null && (mEvents & WifiNanSessionListener.LISTEN_PUBLISH_FAIL) != 0) {
-                mListener.onPublishFail(status);
-            }
-        } catch (RemoteException e) {
-            Log.w(TAG, "onPublishFail: RemoteException (FYI): " + e);
-        }
-    }
-
-    public void onPublishTerminated(int status) {
-        mPubSubIdValid = false;
-        try {
-            if (mListener != null
-                    && (mEvents & WifiNanSessionListener.LISTEN_PUBLISH_TERMINATED) != 0) {
-                mListener.onPublishTerminated(status);
-            }
-        } catch (RemoteException e) {
-            Log.w(TAG, "onPublishTerminated: RemoteException (FYI): " + e);
-        }
-    }
-
-    public void onSubscribeSuccess(int subscribeId) {
-        mPubSubId = subscribeId;
-        mPubSubIdValid = true;
-    }
-
-    public void onSubscribeFail(int status) {
-        mPubSubIdValid = false;
-        try {
-            if (mListener != null
-                    && (mEvents & WifiNanSessionListener.LISTEN_SUBSCRIBE_FAIL) != 0) {
-                mListener.onSubscribeFail(status);
-            }
-        } catch (RemoteException e) {
-            Log.w(TAG, "onSubscribeFail: RemoteException (FYI): " + e);
-        }
-    }
-
-    public void onSubscribeTerminated(int status) {
-        mPubSubIdValid = false;
-        try {
-            if (mListener != null
-                    && (mEvents & WifiNanSessionListener.LISTEN_SUBSCRIBE_TERMINATED) != 0) {
-                mListener.onSubscribeTerminated(status);
-            }
-        } catch (RemoteException e) {
-            Log.w(TAG, "onSubscribeTerminated: RemoteException (FYI): " + e);
-        }
-    }
-
-    public void onMessageSendSuccess(int messageId) {
-        try {
-            if (mListener != null
-                    && (mEvents & WifiNanSessionListener.LISTEN_MESSAGE_SEND_SUCCESS) != 0) {
-                mListener.onMessageSendSuccess(messageId);
-            }
-        } catch (RemoteException e) {
-            Log.w(TAG, "onMessageSendSuccess: RemoteException (FYI): " + e);
-        }
-    }
-
-    public void onMessageSendFail(int messageId, int status) {
-        try {
-            if (mListener != null
-                    && (mEvents & WifiNanSessionListener.LISTEN_MESSAGE_SEND_FAIL) != 0) {
-                mListener.onMessageSendFail(messageId, status);
-            }
-        } catch (RemoteException e) {
-            Log.w(TAG, "onMessageSendFail: RemoteException (FYI): " + e);
-        }
-    }
-
+    /**
+     * Callback from HAL when a discovery occurs - i.e. when a match to an
+     * active subscription request or to a solicited publish request occurs.
+     * Propagates to client if registered.
+     *
+     * @param requestorInstanceId The ID used to identify the peer in this
+     *            matched session.
+     * @param peerMac The MAC address of the peer. Never propagated to client
+     *            due to privacy concerns.
+     * @param serviceSpecificInfo Information from the discovery advertisement
+     *            (usually not used in the match decisions).
+     * @param serviceSpecificInfoLength Length of the above information field.
+     * @param matchFilter The filter from the discovery advertisement (which was
+     *            used in the match decision).
+     * @param matchFilterLength Length of the above filter field.
+     */
     public void onMatch(int requestorInstanceId, byte[] peerMac, byte[] serviceSpecificInfo,
             int serviceSpecificInfoLength, byte[] matchFilter, int matchFilterLength) {
         String prevMac = mMacByRequestorInstanceId.get(requestorInstanceId);
@@ -216,15 +203,24 @@
         if (DBG) Log.d(TAG, "onMatch: previous peer MAC replaced - " + prevMac);
 
         try {
-            if (mListener != null && (mEvents & WifiNanSessionListener.LISTEN_MATCH) != 0) {
-                mListener.onMatch(requestorInstanceId, serviceSpecificInfo,
-                        serviceSpecificInfoLength, matchFilter, matchFilterLength);
-            }
+            mCallback.onMatch(requestorInstanceId, serviceSpecificInfo, serviceSpecificInfoLength,
+                    matchFilter, matchFilterLength);
         } catch (RemoteException e) {
             Log.w(TAG, "onMatch: RemoteException (FYI): " + e);
         }
     }
 
+    /**
+     * Callback from HAL when a message is received from a peer in a discovery
+     * session. Propagated to client if registered.
+     *
+     * @param requestorInstanceId An ID used to identify the peer.
+     * @param peerMac The MAC address of the peer sending the message. This
+     *            information is never propagated to the client due to privacy
+     *            concerns.
+     * @param message The received message.
+     * @param messageLength The length of the received message.
+     */
     public void onMessageReceived(int requestorInstanceId, byte[] peerMac, byte[] message,
             int messageLength) {
         String prevMac = mMacByRequestorInstanceId.get(requestorInstanceId);
@@ -235,21 +231,20 @@
         }
 
         try {
-            if (mListener != null
-                    && (mEvents & WifiNanSessionListener.LISTEN_MESSAGE_RECEIVED) != 0) {
-                mListener.onMessageReceived(requestorInstanceId, message, messageLength);
-            }
+            mCallback.onMessageReceived(requestorInstanceId, message, messageLength);
         } catch (RemoteException e) {
             Log.w(TAG, "onMessageReceived: RemoteException (FYI): " + e);
         }
     }
 
+    /**
+     * Dump the internal state of the class.
+     */
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("NanSessionState:");
         pw.println("  mSessionId: " + mSessionId);
-        pw.println("  mSessionType: " + mSessionType);
-        pw.println("  mEvents: " + mEvents);
-        pw.println("  mPubSubId: " + (mPubSubIdValid ? Integer.toString(mPubSubId) : "not valid"));
+        pw.println("  mIsPublishSession: " + mIsPublishSession);
+        pw.println("  mPubSubId: " + mPubSubId);
         pw.println("  mMacByRequestorInstanceId: [" + mMacByRequestorInstanceId + "]");
     }
 }
diff --git a/service/java/com/android/server/wifi/nan/WifiNanStateManager.java b/service/java/com/android/server/wifi/nan/WifiNanStateManager.java
index f7bfa55..86ff2dc 100644
--- a/service/java/com/android/server/wifi/nan/WifiNanStateManager.java
+++ b/service/java/com/android/server/wifi/nan/WifiNanStateManager.java
@@ -16,71 +16,140 @@
 
 package com.android.server.wifi.nan;
 
+import android.content.Context;
+import android.content.Intent;
+import android.net.wifi.RttManager;
 import android.net.wifi.nan.ConfigRequest;
-import android.net.wifi.nan.IWifiNanEventListener;
-import android.net.wifi.nan.IWifiNanSessionListener;
-import android.net.wifi.nan.PublishData;
-import android.net.wifi.nan.PublishSettings;
-import android.net.wifi.nan.SubscribeData;
-import android.net.wifi.nan.SubscribeSettings;
+import android.net.wifi.nan.IWifiNanEventCallback;
+import android.net.wifi.nan.IWifiNanSessionCallback;
+import android.net.wifi.nan.PublishConfig;
+import android.net.wifi.nan.SubscribeConfig;
+import android.net.wifi.nan.WifiNanEventCallback;
+import android.net.wifi.nan.WifiNanManager;
+import android.net.wifi.nan.WifiNanSessionCallback;
 import android.os.Bundle;
-import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
 import android.util.Log;
+import android.util.Pair;
 import android.util.SparseArray;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.MessageUtils;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import com.android.internal.util.WakeupMessage;
+
 import libcore.util.HexEncoding;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
 
+/**
+ * Manages the state of the Wi-Fi NAN system service.
+ */
 public class WifiNanStateManager {
     private static final String TAG = "WifiNanStateManager";
     private static final boolean DBG = false;
     private static final boolean VDBG = false; // STOPSHIP if true
 
+    @VisibleForTesting
+    public static final String HAL_COMMAND_TIMEOUT_TAG = TAG + " HAL Command Timeout";
+
+    @VisibleForTesting
+    public static final String HAL_SEND_MESSAGE_TIMEOUT_TAG = TAG + " HAL Send Message Timeout";
+
+    @VisibleForTesting
+    public static final String HAL_DATA_PATH_CONFIRM_TIMEOUT_TAG =
+            TAG + " HAL Data Path Confirm Timeout";
+
     private static WifiNanStateManager sNanStateManagerSingleton;
 
-    private static final int MESSAGE_CONNECT = 0;
-    private static final int MESSAGE_DISCONNECT = 1;
-    private static final int MESSAGE_REQUEST_CONFIG = 4;
-    private static final int MESSAGE_CREATE_SESSION = 5;
-    private static final int MESSAGE_DESTROY_SESSION = 6;
-    private static final int MESSAGE_PUBLISH = 7;
-    private static final int MESSAGE_SUBSCRIBE = 8;
-    private static final int MESSAGE_SEND_MESSAGE = 9;
-    private static final int MESSAGE_STOP_SESSION = 10;
-    private static final int MESSAGE_ON_CONFIG_COMPLETED = 11;
-    private static final int MESSAGE_ON_CONFIG_FAILED = 12;
-    private static final int MESSAGE_ON_NAN_DOWN = 13;
-    private static final int MESSAGE_ON_INTERFACE_CHANGE = 14;
-    private static final int MESSAGE_ON_CLUSTER_CHANGE = 15;
-    private static final int MESSAGE_ON_PUBLISH_SUCCESS = 16;
-    private static final int MESSAGE_ON_PUBLISH_FAIL = 17;
-    private static final int MESSAGE_ON_PUBLISH_TERMINATED = 18;
-    private static final int MESSAGE_ON_SUBSCRIBE_SUCCESS = 19;
-    private static final int MESSAGE_ON_SUBSCRIBE_FAIL = 20;
-    private static final int MESSAGE_ON_SUBSCRIBE_TERMINATED = 21;
-    private static final int MESSAGE_ON_MESSAGE_SEND_SUCCESS = 22;
-    private static final int MESSAGE_ON_MESSAGE_SEND_FAIL = 23;
-    private static final int MESSAGE_ON_UNKNOWN_TRANSACTION = 24;
-    private static final int MESSAGE_ON_MATCH = 25;
-    private static final int MESSAGE_ON_MESSAGE_RECEIVED = 26;
-    private static final int MESSAGE_ON_CAPABILITIES_UPDATED = 27;
+    /*
+     * State machine message types. There are sub-types for the messages (except for TIMEOUTs).
+     * Format:
+     * - Message.arg1: contains message sub-type
+     * - Message.arg2: contains transaction ID for RESPONSE & RESPONSE_TIMEOUT
+     */
+    private static final int MESSAGE_TYPE_COMMAND = 1;
+    private static final int MESSAGE_TYPE_RESPONSE = 2;
+    private static final int MESSAGE_TYPE_NOTIFICATION = 3;
+    private static final int MESSAGE_TYPE_RESPONSE_TIMEOUT = 4;
+    private static final int MESSAGE_TYPE_SEND_MESSAGE_TIMEOUT = 5;
+    private static final int MESSAGE_TYPE_DATA_PATH_TIMEOUT = 6;
 
+    /*
+     * Message sub-types:
+     */
+    private static final int COMMAND_TYPE_CONNECT = 100;
+    private static final int COMMAND_TYPE_DISCONNECT = 101;
+    private static final int COMMAND_TYPE_TERMINATE_SESSION = 102;
+    private static final int COMMAND_TYPE_PUBLISH = 103;
+    private static final int COMMAND_TYPE_UPDATE_PUBLISH = 104;
+    private static final int COMMAND_TYPE_SUBSCRIBE = 105;
+    private static final int COMMAND_TYPE_UPDATE_SUBSCRIBE = 106;
+    private static final int COMMAND_TYPE_ENQUEUE_SEND_MESSAGE = 107;
+    private static final int COMMAND_TYPE_ENABLE_USAGE = 108;
+    private static final int COMMAND_TYPE_DISABLE_USAGE = 109;
+    private static final int COMMAND_TYPE_START_RANGING = 110;
+    private static final int COMMAND_TYPE_GET_CAPABILITIES = 111;
+    private static final int COMMAND_TYPE_CREATE_ALL_DATA_PATH_INTERFACES = 112;
+    private static final int COMMAND_TYPE_DELETE_ALL_DATA_PATH_INTERFACES = 113;
+    private static final int COMMAND_TYPE_CREATE_DATA_PATH_INTERFACE = 114;
+    private static final int COMMAND_TYPE_DELETE_DATA_PATH_INTERFACE = 115;
+    private static final int COMMAND_TYPE_INITIATE_DATA_PATH_SETUP = 116;
+    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 RESPONSE_TYPE_ON_CONFIG_SUCCESS = 200;
+    private static final int RESPONSE_TYPE_ON_CONFIG_FAIL = 201;
+    private static final int RESPONSE_TYPE_ON_SESSION_CONFIG_SUCCESS = 202;
+    private static final int RESPONSE_TYPE_ON_SESSION_CONFIG_FAIL = 203;
+    private static final int RESPONSE_TYPE_ON_MESSAGE_SEND_QUEUED_SUCCESS = 204;
+    private static final int RESPONSE_TYPE_ON_MESSAGE_SEND_QUEUED_FAIL = 205;
+    private static final int RESPONSE_TYPE_ON_CAPABILITIES_UPDATED = 206;
+    private static final int RESPONSE_TYPE_ON_CREATE_INTERFACE = 207;
+    private static final int RESPONSE_TYPE_ON_DELETE_INTERFACE = 208;
+    private static final int RESPONSE_TYPE_ON_INITIATE_DATA_PATH_SUCCESS = 209;
+    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 NOTIFICATION_TYPE_INTERFACE_CHANGE = 301;
+    private static final int NOTIFICATION_TYPE_CLUSTER_CHANGE = 302;
+    private static final int NOTIFICATION_TYPE_MATCH = 303;
+    private static final int NOTIFICATION_TYPE_SESSION_TERMINATED = 304;
+    private static final int NOTIFICATION_TYPE_MESSAGE_RECEIVED = 305;
+    private static final int NOTIFICATION_TYPE_NAN_DOWN = 306;
+    private static final int NOTIFICATION_TYPE_ON_MESSAGE_SEND_SUCCESS = 307;
+    private static final int NOTIFICATION_TYPE_ON_MESSAGE_SEND_FAIL = 308;
+    private static final int NOTIFICATION_TYPE_ON_DATA_PATH_REQUEST = 309;
+    private static final int NOTIFICATION_TYPE_ON_DATA_PATH_CONFIRM = 310;
+    private static final int NOTIFICATION_TYPE_ON_DATA_PATH_END = 311;
+
+    private static final SparseArray<String> sSmToString = MessageUtils.findMessageNames(
+            new Class[]{WifiNanStateManager.class},
+            new String[]{"MESSAGE_TYPE", "COMMAND_TYPE", "RESPONSE_TYPE", "NOTIFICATION_TYPE"});
+
+    /*
+     * Keys used when passing (some) arguments to the Handler thread (too many
+     * arguments to pass in the short-cut Message members).
+     */
+    private static final String MESSAGE_BUNDLE_KEY_SESSION_TYPE = "session_type";
     private static final String MESSAGE_BUNDLE_KEY_SESSION_ID = "session_id";
-    private static final String MESSAGE_BUNDLE_KEY_EVENTS = "events";
-    private static final String MESSAGE_BUNDLE_KEY_PUBLISH_DATA = "publish_data";
-    private static final String MESSAGE_BUNDLE_KEY_PUBLISH_SETTINGS = "publish_settings";
-    private static final String MESSAGE_BUNDLE_KEY_SUBSCRIBE_DATA = "subscribe_data";
-    private static final String MESSAGE_BUNDLE_KEY_SUBSCRIBE_SETTINGS = "subscribe_settings";
+    private static final String MESSAGE_BUNDLE_KEY_CONFIG = "config";
     private static final String MESSAGE_BUNDLE_KEY_MESSAGE = "message";
     private static final String MESSAGE_BUNDLE_KEY_MESSAGE_PEER_ID = "message_peer_id";
     private static final String MESSAGE_BUNDLE_KEY_MESSAGE_ID = "message_id";
-    private static final String MESSAGE_BUNDLE_KEY_RESPONSE_TYPE = "response_type";
     private static final String MESSAGE_BUNDLE_KEY_SSI_LENGTH = "ssi_length";
     private static final String MESSAGE_BUNDLE_KEY_SSI_DATA = "ssi_data";
     private static final String MESSAGE_BUNDLE_KEY_FILTER_LENGTH = "filter_length";
@@ -88,20 +157,52 @@
     private static final String MESSAGE_BUNDLE_KEY_MAC_ADDRESS = "mac_address";
     private static final String MESSAGE_BUNDLE_KEY_MESSAGE_DATA = "message_data";
     private static final String MESSAGE_BUNDLE_KEY_MESSAGE_LENGTH = "message_length";
+    private static final String MESSAGE_BUNDLE_KEY_REQ_INSTANCE_ID = "req_instance_id";
+    private static final String MESSAGE_BUNDLE_KEY_RANGING_ID = "ranging_id";
+    private static final String MESSAGE_BUNDLE_KEY_SEND_MESSAGE_ENQUEUE_TIME = "message_queue_time";
+    private static final String MESSAGE_BUNDLE_KEY_RETRY_COUNT = "retry_count";
+    private static final String MESSAGE_BUNDLE_KEY_SUCCESS_FLAG = "success_flag";
+    private static final String MESSAGE_BUNDLE_KEY_STATUS_CODE = "status_code";
+    private static final String MESSAGE_BUNDLE_KEY_INTERFACE_NAME = "interface_name";
+    private static final String MESSAGE_BUNDLE_KEY_PUB_SUB_ID = "pub_sub_id";
+    private static final String MESSAGE_BUNDLE_KEY_CHANNEL_REQ_TYPE = "channel_request_type";
+    private static final String MESSAGE_BUNDLE_KEY_CHANNEL = "channel";
+    private static final String MESSAGE_BUNDLE_KEY_PEER_ID = "peer_id";
+    private static final String MESSAGE_BUNDLE_KEY_UID = "uid";
+    private static final String MESSAGE_BUNDLE_KEY_SENT_MESSAGE = "send_message";
+    private static final String MESSAGE_BUNDLE_KEY_MESSAGE_ARRIVAL_SEQ = "message_arrival_seq";
 
-    private WifiNanNative.Capabilities mCapabilities;
+    /*
+     * Asynchronous access with no lock
+     */
+    private volatile boolean mUsageEnabled = false;
 
-    private WifiNanStateHandler mHandler;
+    /*
+     * Synchronous access: state is only accessed through the state machine
+     * handler thread: no need to use a lock.
+     */
+    private Context mContext;
+    /* package */ WifiNanNative.Capabilities mCapabilities;
+    private WifiNanStateMachine mSm;
+    private WifiNanRttStateManager mRtt;
+    private WifiNanDataPathStateManager mDataPathMgr;
 
-    // no synchronization necessary: only access through Handler
     private final SparseArray<WifiNanClientState> mClients = new SparseArray<>();
-    private final SparseArray<TransactionInfoBase> mPendingResponses = new SparseArray<>();
-    private short mNextTransactionId = 1;
+    private ConfigRequest mCurrentNanConfiguration = null;
+
+    private static final byte[] ALL_ZERO_MAC = new byte[] {0, 0, 0, 0, 0, 0};
+    private byte[] mCurrentDiscoveryInterfaceMac = ALL_ZERO_MAC;
 
     private WifiNanStateManager() {
         // EMPTY: singleton pattern
     }
 
+    /**
+     * Access the singleton NAN state manager. Use a singleton since need to be
+     * accessed (for now) from several other child classes.
+     *
+     * @return The state manager singleton.
+     */
     public static WifiNanStateManager getInstance() {
         if (sNanStateManagerSingleton == null) {
             sNanStateManagerSingleton = new WifiNanStateManager();
@@ -110,384 +211,810 @@
         return sNanStateManagerSingleton;
     }
 
-    public void start(Looper looper) {
+    /**
+     * 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) {
         Log.i(TAG, "start()");
 
-        mHandler = new WifiNanStateHandler(looper);
+        mContext = context;
+        mSm = new WifiNanStateMachine(TAG, looper);
+        mSm.setDbg(DBG);
+        mSm.start();
+
+        mRtt = new WifiNanRttStateManager();
+        mDataPathMgr = new WifiNanDataPathStateManager(this);
+        mDataPathMgr.start(mContext, mSm.getHandler().getLooper());
     }
 
-    public void connect(int uid, IWifiNanEventListener listener, int events) {
-        Message msg = mHandler.obtainMessage(MESSAGE_CONNECT);
-        msg.arg1 = uid;
-        msg.arg2 = events;
-        msg.obj = listener;
-        mHandler.sendMessage(msg);
+    /**
+     * Initialize the late-initialization sub-services: depend on other services already existing.
+     */
+    public void startLate() {
+        mRtt.start(mContext, mSm.getHandler().getLooper());
     }
 
-    public void disconnect(int uid) {
-        Message msg = mHandler.obtainMessage(MESSAGE_DISCONNECT);
-        msg.arg1 = uid;
-        mHandler.sendMessage(msg);
+    /**
+     * Get the client state for the specified ID (or null if none exists).
+     */
+    /* package */ WifiNanClientState getClient(int clientId) {
+        return mClients.get(clientId);
     }
 
-    public void requestConfig(int uid, ConfigRequest configRequest) {
-        Message msg = mHandler.obtainMessage(MESSAGE_REQUEST_CONFIG);
-        msg.arg1 = uid;
-        msg.obj = configRequest;
-        mHandler.sendMessage(msg);
+    /*
+     * COMMANDS
+     */
+
+    /**
+     * Place a request for a new client connection on the state machine queue.
+     */
+    public void connect(int clientId, int uid, IWifiNanEventCallback callback,
+            ConfigRequest configRequest) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
+        msg.arg1 = COMMAND_TYPE_CONNECT;
+        msg.arg2 = clientId;
+        msg.obj = callback;
+        msg.getData().putParcelable(MESSAGE_BUNDLE_KEY_CONFIG, configRequest);
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_UID, uid);
+        mSm.sendMessage(msg);
     }
 
-    public void stopSession(int uid, int sessionId) {
-        Message msg = mHandler.obtainMessage(MESSAGE_STOP_SESSION);
-        msg.arg1 = uid;
-        msg.arg2 = sessionId;
-        mHandler.sendMessage(msg);
+    /**
+     * Place a request to disconnect (destroy) an existing client on the state
+     * machine queue.
+     */
+    public void disconnect(int clientId) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
+        msg.arg1 = COMMAND_TYPE_DISCONNECT;
+        msg.arg2 = clientId;
+        mSm.sendMessage(msg);
     }
 
-    public void destroySession(int uid, int sessionId) {
-        Message msg = mHandler.obtainMessage(MESSAGE_DESTROY_SESSION);
-        msg.arg1 = uid;
-        msg.arg2 = sessionId;
-        mHandler.sendMessage(msg);
+    /**
+     * Place a request to stop a discovery session on the state machine queue.
+     */
+    public void terminateSession(int clientId, int sessionId) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
+        msg.arg1 = COMMAND_TYPE_TERMINATE_SESSION;
+        msg.arg2 = clientId;
+        msg.obj = sessionId;
+        mSm.sendMessage(msg);
     }
 
-    public void createSession(int uid, int sessionId, IWifiNanSessionListener listener,
-            int events) {
-        Bundle data = new Bundle();
-        data.putInt(MESSAGE_BUNDLE_KEY_EVENTS, events);
-
-        Message msg = mHandler.obtainMessage(MESSAGE_CREATE_SESSION);
-        msg.setData(data);
-        msg.arg1 = uid;
-        msg.arg2 = sessionId;
-        msg.obj = listener;
-        mHandler.sendMessage(msg);
+    /**
+     * Place a request to start a new publish discovery session on the state
+     * machine queue.
+     */
+    public void publish(int clientId, PublishConfig publishConfig,
+            IWifiNanSessionCallback callback) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
+        msg.arg1 = COMMAND_TYPE_PUBLISH;
+        msg.arg2 = clientId;
+        msg.obj = callback;
+        msg.getData().putParcelable(MESSAGE_BUNDLE_KEY_CONFIG, publishConfig);
+        mSm.sendMessage(msg);
     }
 
-    public void publish(int uid, int sessionId, PublishData publishData,
-            PublishSettings publishSettings) {
-        Bundle data = new Bundle();
-        data.putParcelable(MESSAGE_BUNDLE_KEY_PUBLISH_DATA, publishData);
-        data.putParcelable(MESSAGE_BUNDLE_KEY_PUBLISH_SETTINGS, publishSettings);
-
-        Message msg = mHandler.obtainMessage(MESSAGE_PUBLISH);
-        msg.setData(data);
-        msg.arg1 = uid;
-        msg.arg2 = sessionId;
-        mHandler.sendMessage(msg);
+    /**
+     * Place a request to modify an existing publish discovery session on the
+     * state machine queue.
+     */
+    public void updatePublish(int clientId, int sessionId, PublishConfig publishConfig) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
+        msg.arg1 = COMMAND_TYPE_UPDATE_PUBLISH;
+        msg.arg2 = clientId;
+        msg.obj = publishConfig;
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_SESSION_ID, sessionId);
+        mSm.sendMessage(msg);
     }
 
-    public void subscribe(int uid, int sessionId, SubscribeData subscribeData,
-            SubscribeSettings subscribeSettings) {
-        Bundle data = new Bundle();
-        data.putParcelable(MESSAGE_BUNDLE_KEY_SUBSCRIBE_DATA, subscribeData);
-        data.putParcelable(MESSAGE_BUNDLE_KEY_SUBSCRIBE_SETTINGS, subscribeSettings);
-
-        Message msg = mHandler.obtainMessage(MESSAGE_SUBSCRIBE);
-        msg.setData(data);
-        msg.arg1 = uid;
-        msg.arg2 = sessionId;
-        mHandler.sendMessage(msg);
+    /**
+     * Place a request to start a new subscribe discovery session on the state
+     * machine queue.
+     */
+    public void subscribe(int clientId, SubscribeConfig subscribeConfig,
+            IWifiNanSessionCallback callback) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
+        msg.arg1 = COMMAND_TYPE_SUBSCRIBE;
+        msg.arg2 = clientId;
+        msg.obj = callback;
+        msg.getData().putParcelable(MESSAGE_BUNDLE_KEY_CONFIG, subscribeConfig);
+        mSm.sendMessage(msg);
     }
 
-    public void sendMessage(int uid, int sessionId, int peerId, byte[] message, int messageLength,
-            int messageId) {
-        Bundle data = new Bundle();
-        data.putInt(MESSAGE_BUNDLE_KEY_SESSION_ID, sessionId);
-        data.putInt(MESSAGE_BUNDLE_KEY_MESSAGE_PEER_ID, peerId);
-        data.putByteArray(MESSAGE_BUNDLE_KEY_MESSAGE, message);
-        data.putInt(MESSAGE_BUNDLE_KEY_MESSAGE_ID, messageId);
-
-        Message msg = mHandler.obtainMessage(MESSAGE_SEND_MESSAGE);
-        msg.arg1 = uid;
-        msg.arg2 = messageLength;
-        msg.setData(data);
-        mHandler.sendMessage(msg);
+    /**
+     * Place a request to modify an existing subscribe discovery session on the
+     * state machine queue.
+     */
+    public void updateSubscribe(int clientId, int sessionId, SubscribeConfig subscribeConfig) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
+        msg.arg1 = COMMAND_TYPE_UPDATE_SUBSCRIBE;
+        msg.arg2 = clientId;
+        msg.obj = subscribeConfig;
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_SESSION_ID, sessionId);
+        mSm.sendMessage(msg);
     }
 
-    public void onCapabilitiesUpdate(short transactionId, WifiNanNative.Capabilities capabilities) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_CAPABILITIES_UPDATED);
-        msg.arg1 = transactionId;
+    /**
+     * Place a request to send a message on a discovery session on the state
+     * machine queue.
+     */
+    public void sendMessage(int clientId, int sessionId, int peerId, byte[] message,
+            int messageLength, int messageId, int retryCount) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
+        msg.arg1 = COMMAND_TYPE_ENQUEUE_SEND_MESSAGE;
+        msg.arg2 = clientId;
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_SESSION_ID, sessionId);
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_MESSAGE_PEER_ID, peerId);
+        msg.getData().putByteArray(MESSAGE_BUNDLE_KEY_MESSAGE, message);
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_MESSAGE_ID, messageId);
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_MESSAGE_LENGTH, messageLength);
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_RETRY_COUNT, retryCount);
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * Place a request to range a peer on the discovery session on the state machine queue.
+     */
+    public void startRanging(int clientId, int sessionId, RttManager.RttParams[] params,
+                             int rangingId) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
+        msg.arg1 = COMMAND_TYPE_START_RANGING;
+        msg.arg2 = clientId;
+        msg.obj = params;
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_SESSION_ID, sessionId);
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_RANGING_ID, rangingId);
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * Enable usage of NAN. Doesn't actually turn on NAN (form clusters) - that
+     * only happens when a connection is created.
+     */
+    public void enableUsage() {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
+        msg.arg1 = COMMAND_TYPE_ENABLE_USAGE;
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * Disable usage of NAN. Terminates all existing clients with onNanDown().
+     */
+    public void disableUsage() {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
+        msg.arg1 = COMMAND_TYPE_DISABLE_USAGE;
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * Checks whether NAN usage is enabled (not necessarily that NAN is up right
+     * now) or disabled.
+     *
+     * @return A boolean indicating whether NAN usage is enabled (true) or
+     *         disabled (false).
+     */
+    public boolean isUsageEnabled() {
+        return mUsageEnabled;
+    }
+
+    /**
+     * Get the capabilities of the current NAN firmware.
+     */
+    public void getCapabilities() {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
+        msg.arg1 = COMMAND_TYPE_GET_CAPABILITIES;
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * Create all NAN data path interfaces which are supported by the firmware capabilities.
+     */
+    public void createAllDataPathInterfaces() {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
+        msg.arg1 = COMMAND_TYPE_CREATE_ALL_DATA_PATH_INTERFACES;
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * delete all NAN data path interfaces.
+     */
+    public void deleteAllDataPathInterfaces() {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
+        msg.arg1 = COMMAND_TYPE_DELETE_ALL_DATA_PATH_INTERFACES;
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * Create the specified data-path interface. Doesn't actually creates a data-path.
+     */
+    public void createDataPathInterface(String interfaceName) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
+        msg.arg1 = COMMAND_TYPE_CREATE_DATA_PATH_INTERFACE;
+        msg.obj = interfaceName;
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * Deletes the specified data-path interface.
+     */
+    public void deleteDataPathInterface(String interfaceName) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
+        msg.arg1 = COMMAND_TYPE_DELETE_DATA_PATH_INTERFACE;
+        msg.obj = interfaceName;
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * Command to initiate a data-path (executed by the initiator).
+     */
+    public void initiateDataPathSetup(String networkSpecifier, int peerId, int channelRequestType,
+            int channel, byte[] peer, String interfaceName, byte[] token) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
+        msg.arg1 = COMMAND_TYPE_INITIATE_DATA_PATH_SETUP;
+        msg.obj = networkSpecifier;
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_PEER_ID, peerId);
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_CHANNEL_REQ_TYPE, channelRequestType);
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_CHANNEL, channel);
+        msg.getData().putByteArray(MESSAGE_BUNDLE_KEY_MAC_ADDRESS, peer);
+        msg.getData().putString(MESSAGE_BUNDLE_KEY_INTERFACE_NAME, interfaceName);
+        msg.getData().putByteArray(MESSAGE_BUNDLE_KEY_MESSAGE, token);
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * Command to respond to the data-path request (executed by the responder).
+     */
+    public void respondToDataPathRequest(boolean accept, int ndpId, String interfaceName,
+            String token) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
+        msg.arg1 = COMMAND_TYPE_RESPOND_TO_DATA_PATH_SETUP_REQUEST;
+        msg.arg2 = ndpId;
+        msg.obj = accept;
+        msg.getData().putString(MESSAGE_BUNDLE_KEY_INTERFACE_NAME, interfaceName);
+        msg.getData().putString(MESSAGE_BUNDLE_KEY_MESSAGE, token);
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * Command to terminate the specified data-path.
+     */
+    public void endDataPath(int ndpId) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
+        msg.arg1 = COMMAND_TYPE_END_DATA_PATH;
+        msg.arg2 = ndpId;
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * NAN follow-on messages (L2 messages) are queued by the firmware for transmission
+     * on-the-air. The firmware has limited queue depth. The host queues all messages and doles
+     * them out to the firmware when possible. This command removes the next messages for
+     * transmission from the host queue and attempts to send it through the firmware. The queues
+     * are inspected when the command is executed - not when the command is placed on the handler
+     * (i.e. not evaluated here).
+     */
+    private void transmitNextMessage() {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
+        msg.arg1 = COMMAND_TYPE_TRANSMIT_NEXT_MESSAGE;
+        mSm.sendMessage(msg);
+    }
+
+    /*
+     * RESPONSES
+     */
+
+    /**
+     * Place a callback request on the state machine queue: configuration
+     * request completed (successfully).
+     */
+    public void onConfigSuccessResponse(short transactionId) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_RESPONSE);
+        msg.arg1 = RESPONSE_TYPE_ON_CONFIG_SUCCESS;
+        msg.arg2 = transactionId;
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * Place a callback request on the state machine queue: configuration
+     * request failed.
+     */
+    public void onConfigFailedResponse(short transactionId, int reason) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_RESPONSE);
+        msg.arg1 = RESPONSE_TYPE_ON_CONFIG_FAIL;
+        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) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_RESPONSE);
+        msg.arg1 = RESPONSE_TYPE_ON_SESSION_CONFIG_SUCCESS;
+        msg.arg2 = transactionId;
+        msg.obj = pubSubId;
+        msg.getData().putBoolean(MESSAGE_BUNDLE_KEY_SESSION_TYPE, isPublish);
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * Place a callback request on the state machine queue: session
+     * configuration (new or update) request failed.
+     */
+    public void onSessionConfigFailResponse(short transactionId, boolean isPublish, int reason) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_RESPONSE);
+        msg.arg1 = RESPONSE_TYPE_ON_SESSION_CONFIG_FAIL;
+        msg.arg2 = transactionId;
+        msg.obj = reason;
+        msg.getData().putBoolean(MESSAGE_BUNDLE_KEY_SESSION_TYPE, isPublish);
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * Place a callback request on the state machine queue: message has been queued successfully.
+     */
+    public void onMessageSendQueuedSuccessResponse(short transactionId) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_RESPONSE);
+        msg.arg1 = RESPONSE_TYPE_ON_MESSAGE_SEND_QUEUED_SUCCESS;
+        msg.arg2 = transactionId;
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * Place a callback request on the state machine queue: attempt to queue the message failed.
+     */
+    public void onMessageSendQueuedFailResponse(short transactionId, int reason) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_RESPONSE);
+        msg.arg1 = RESPONSE_TYPE_ON_MESSAGE_SEND_QUEUED_FAIL;
+        msg.arg2 = transactionId;
+        msg.obj = reason;
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * Place a callback request on the state machine queue: update vendor
+     * capabilities of the NAN stack.
+     */
+    public void onCapabilitiesUpdateResponse(short transactionId,
+            WifiNanNative.Capabilities capabilities) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_RESPONSE);
+        msg.arg1 = RESPONSE_TYPE_ON_CAPABILITIES_UPDATED;
+        msg.arg2 = transactionId;
         msg.obj = capabilities;
-        mHandler.sendMessage(msg);
+        mSm.sendMessage(msg);
     }
 
-    public void onConfigCompleted(short transactionId) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_CONFIG_COMPLETED);
-        msg.arg1 = transactionId;
-        mHandler.sendMessage(msg);
+    /**
+     * Places a callback request on the state machine queue: data-path interface creation command
+     * completed.
+     */
+    public void onCreateDataPathInterfaceResponse(short transactionId, boolean success,
+            int reasonOnFailure) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_RESPONSE);
+        msg.arg1 = RESPONSE_TYPE_ON_CREATE_INTERFACE;
+        msg.arg2 = transactionId;
+        msg.getData().putBoolean(MESSAGE_BUNDLE_KEY_SUCCESS_FLAG, success);
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_STATUS_CODE, reasonOnFailure);
+        mSm.sendMessage(msg);
     }
 
-    public void onConfigFailed(short transactionId, int reason) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_CONFIG_FAILED);
-        msg.arg1 = transactionId;
-        msg.arg2 = reason;
-        mHandler.sendMessage(msg);
+    /**
+     * Places a callback request on the state machine queue: data-path interface deletion command
+     * completed.
+     */
+    public void onDeleteDataPathInterfaceResponse(short transactionId, boolean success,
+            int reasonOnFailure) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_RESPONSE);
+        msg.arg1 = RESPONSE_TYPE_ON_DELETE_INTERFACE;
+        msg.arg2 = transactionId;
+        msg.getData().putBoolean(MESSAGE_BUNDLE_KEY_SUCCESS_FLAG, success);
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_STATUS_CODE, reasonOnFailure);
+        mSm.sendMessage(msg);
     }
 
-    public void onPublishSuccess(short transactionId, int publishId) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_PUBLISH_SUCCESS);
-        msg.arg1 = transactionId;
-        msg.arg2 = publishId;
-        mHandler.sendMessage(msg);
+    /**
+     * Response from firmware to {@link #initiateDataPathSetup(String, int, int, int, byte[],
+     * String, byte[])}. Indicates that command has started succesfully (not completed!).
+     */
+    public void onInitiateDataPathResponseSuccess(short transactionId, int ndpId) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_RESPONSE);
+        msg.arg1 = RESPONSE_TYPE_ON_INITIATE_DATA_PATH_SUCCESS;
+        msg.arg2 = transactionId;
+        msg.obj = ndpId;
+        mSm.sendMessage(msg);
     }
 
-    public void onPublishFail(short transactionId, int status) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_PUBLISH_FAIL);
-        msg.arg1 = transactionId;
-        msg.arg2 = status;
-        mHandler.sendMessage(msg);
+    /**
+     * Response from firmware to
+     * {@link #initiateDataPathSetup(String, int, int, int, byte[], String, byte[])}. Indicates
+     * that command has failed.
+     */
+    public void onInitiateDataPathResponseFail(short transactionId, int reason) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_RESPONSE);
+        msg.arg1 = RESPONSE_TYPE_ON_INITIATE_DATA_PATH_FAIL;
+        msg.arg2 = transactionId;
+        msg.obj = reason;
+        mSm.sendMessage(msg);
     }
 
-    public void onMessageSendSuccess(short transactionId) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_MESSAGE_SEND_SUCCESS);
-        msg.arg1 = transactionId;
-        mHandler.sendMessage(msg);
+    /**
+     * Response from firmware to {@link #respondToDataPathRequest(boolean, int, String, String)}.
+     */
+    public void onRespondToDataPathSetupRequestResponse(short transactionId, boolean success,
+            int reasonOnFailure) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_RESPONSE);
+        msg.arg1 = RESPONSE_TYPE_ON_RESPOND_TO_DATA_PATH_SETUP_REQUEST;
+        msg.arg2 = transactionId;
+        msg.getData().putBoolean(MESSAGE_BUNDLE_KEY_SUCCESS_FLAG, success);
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_STATUS_CODE, reasonOnFailure);
+        mSm.sendMessage(msg);
     }
 
-    public void onMessageSendFail(short transactionId, int status) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_MESSAGE_SEND_FAIL);
-        msg.arg1 = transactionId;
-        msg.arg2 = status;
-        mHandler.sendMessage(msg);
+    /**
+     * Response from firmware to {@link #endDataPath(int)}.
+     */
+    public void onEndDataPathResponse(short transactionId, boolean success, int reasonOnFailure) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_RESPONSE);
+        msg.arg1 = RESPONSE_TYPE_ON_END_DATA_PATH;
+        msg.arg2 = transactionId;
+        msg.getData().putBoolean(MESSAGE_BUNDLE_KEY_SUCCESS_FLAG, success);
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_STATUS_CODE, reasonOnFailure);
+        mSm.sendMessage(msg);
     }
 
-    public void onSubscribeSuccess(short transactionId, int subscribeId) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_SUBSCRIBE_SUCCESS);
-        msg.arg1 = transactionId;
-        msg.arg2 = subscribeId;
-        mHandler.sendMessage(msg);
-    }
+    /*
+     * NOTIFICATIONS
+     */
 
-    public void onSubscribeFail(short transactionId, int status) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_SUBSCRIBE_FAIL);
-        msg.arg1 = transactionId;
-        msg.arg2 = status;
-        mHandler.sendMessage(msg);
-    }
-
-    public void onUnknownTransaction(int responseType, short transactionId, int status) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_UNKNOWN_TRANSACTION);
-        Bundle data = new Bundle();
-        data.putInt(MESSAGE_BUNDLE_KEY_RESPONSE_TYPE, responseType);
-        msg.setData(data);
-        msg.arg1 = transactionId;
-        msg.arg2 = status;
-        mHandler.sendMessage(msg);
-    }
-
-    public void onInterfaceAddressChange(byte[] mac) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_INTERFACE_CHANGE);
+    /**
+     * Place a callback request on the state machine queue: the discovery
+     * interface has changed.
+     */
+    public void onInterfaceAddressChangeNotification(byte[] mac) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_NOTIFICATION);
+        msg.arg1 = NOTIFICATION_TYPE_INTERFACE_CHANGE;
         msg.obj = mac;
-        mHandler.sendMessage(msg);
+        mSm.sendMessage(msg);
     }
 
-    public void onClusterChange(int flag, byte[] clusterId) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_CLUSTER_CHANGE);
-        msg.arg1 = flag;
+    /**
+     * Place a callback request on the state machine queue: the cluster
+     * membership has changed (e.g. due to starting a new cluster or joining
+     * another cluster).
+     */
+    public void onClusterChangeNotification(int flag, byte[] clusterId) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_NOTIFICATION);
+        msg.arg1 = NOTIFICATION_TYPE_CLUSTER_CHANGE;
+        msg.arg2 = flag;
         msg.obj = clusterId;
-        mHandler.sendMessage(msg);
+        mSm.sendMessage(msg);
     }
 
-    public void onMatch(int pubSubId, int requestorInstanceId, byte[] peerMac,
+    /**
+     * Place a callback request on the state machine queue: a discovery match
+     * has occurred - e.g. our subscription discovered someone else publishing a
+     * matching service (to the one we were looking for).
+     */
+    public void onMatchNotification(int pubSubId, int requestorInstanceId, byte[] peerMac,
             byte[] serviceSpecificInfo, int serviceSpecificInfoLength, byte[] matchFilter,
             int matchFilterLength) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_MATCH);
-        msg.arg1 = pubSubId;
-        msg.arg2 = requestorInstanceId;
-        Bundle data = new Bundle();
-        data.putByteArray(MESSAGE_BUNDLE_KEY_MAC_ADDRESS, peerMac);
-        data.putByteArray(MESSAGE_BUNDLE_KEY_SSI_DATA, serviceSpecificInfo);
-        data.putInt(MESSAGE_BUNDLE_KEY_SSI_LENGTH, serviceSpecificInfoLength);
-        data.putByteArray(MESSAGE_BUNDLE_KEY_FILTER_DATA, matchFilter);
-        data.putInt(MESSAGE_BUNDLE_KEY_FILTER_LENGTH, matchFilterLength);
-        msg.setData(data);
-        mHandler.sendMessage(msg);
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_NOTIFICATION);
+        msg.arg1 = NOTIFICATION_TYPE_MATCH;
+        msg.arg2 = pubSubId;
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_REQ_INSTANCE_ID, requestorInstanceId);
+        msg.getData().putByteArray(MESSAGE_BUNDLE_KEY_MAC_ADDRESS, peerMac);
+        msg.getData().putByteArray(MESSAGE_BUNDLE_KEY_SSI_DATA, serviceSpecificInfo);
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_SSI_LENGTH, serviceSpecificInfoLength);
+        msg.getData().putByteArray(MESSAGE_BUNDLE_KEY_FILTER_DATA, matchFilter);
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_FILTER_LENGTH, matchFilterLength);
+        mSm.sendMessage(msg);
     }
 
-    public void onPublishTerminated(int publishId, int status) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_PUBLISH_TERMINATED);
-        msg.arg1 = publishId;
-        msg.arg2 = status;
-        mHandler.sendMessage(msg);
+    /**
+     * Place a callback request on the state machine queue: a session (publish
+     * or subscribe) has terminated (per plan or due to an error).
+     */
+    public void onSessionTerminatedNotification(int pubSubId, int reason, boolean isPublish) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_NOTIFICATION);
+        msg.arg1 = NOTIFICATION_TYPE_SESSION_TERMINATED;
+        msg.arg2 = pubSubId;
+        msg.obj = reason;
+        msg.getData().putBoolean(MESSAGE_BUNDLE_KEY_SESSION_TYPE, isPublish);
+        mSm.sendMessage(msg);
     }
 
-    public void onSubscribeTerminated(int subscribeId, int status) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_SUBSCRIBE_TERMINATED);
-        msg.arg1 = subscribeId;
-        msg.arg2 = status;
-        mHandler.sendMessage(msg);
-    }
-
-    public void onMessageReceived(int pubSubId, int requestorInstanceId, byte[] peerMac,
+    /**
+     * Place a callback request on the state machine queue: a message has been
+     * received as part of a discovery session.
+     */
+    public void onMessageReceivedNotification(int pubSubId, int requestorInstanceId, byte[] peerMac,
             byte[] message, int messageLength) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_MESSAGE_RECEIVED);
-        msg.arg1 = pubSubId;
-        msg.arg2 = requestorInstanceId;
-        Bundle data = new Bundle();
-        data.putByteArray(MESSAGE_BUNDLE_KEY_MAC_ADDRESS, peerMac);
-        data.putByteArray(MESSAGE_BUNDLE_KEY_MESSAGE_DATA, message);
-        data.putInt(MESSAGE_BUNDLE_KEY_MESSAGE_LENGTH, messageLength);
-        msg.setData(data);
-        mHandler.sendMessage(msg);
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_NOTIFICATION);
+        msg.arg1 = NOTIFICATION_TYPE_MESSAGE_RECEIVED;
+        msg.arg2 = pubSubId;
+        msg.obj = requestorInstanceId;
+        msg.getData().putByteArray(MESSAGE_BUNDLE_KEY_MAC_ADDRESS, peerMac);
+        msg.getData().putByteArray(MESSAGE_BUNDLE_KEY_MESSAGE_DATA, message);
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_MESSAGE_LENGTH, messageLength);
+        mSm.sendMessage(msg);
     }
 
-    public void onNanDown(int reason) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_NAN_DOWN);
-        msg.arg1 = reason;
-        mHandler.sendMessage(msg);
+    /**
+     * Place a callback request on the state machine queue: NAN is going down.
+     */
+    public void onNanDownNotification(int reason) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_NOTIFICATION);
+        msg.arg1 = NOTIFICATION_TYPE_NAN_DOWN;
+        msg.arg2 = reason;
+        mSm.sendMessage(msg);
     }
 
-    private class WifiNanStateHandler extends Handler {
-        WifiNanStateHandler(android.os.Looper looper) {
-            super(looper);
+    /**
+     * Notification that a message has been sent successfully (i.e. an ACK has been received).
+     */
+    public void onMessageSendSuccessNotification(short transactionId) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_NOTIFICATION);
+        msg.arg1 = NOTIFICATION_TYPE_ON_MESSAGE_SEND_SUCCESS;
+        msg.arg2 = transactionId;
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * Notification that a message transmission has failed due to the indicated reason - e.g. no ACK
+     * was received.
+     */
+    public void onMessageSendFailNotification(short transactionId, int reason) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_NOTIFICATION);
+        msg.arg1 = NOTIFICATION_TYPE_ON_MESSAGE_SEND_FAIL;
+        msg.arg2 = transactionId;
+        msg.obj = reason;
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * Place a callback request on the state machine queue: data-path request (from peer) received.
+     */
+    public void onDataPathRequestNotification(int pubSubId, byte[] mac, int ndpId, byte[] message,
+            int messageLength) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_NOTIFICATION);
+        msg.arg1 = NOTIFICATION_TYPE_ON_DATA_PATH_REQUEST;
+        msg.arg2 = pubSubId;
+        msg.obj = ndpId;
+        msg.getData().putByteArray(MESSAGE_BUNDLE_KEY_MAC_ADDRESS, mac);
+        msg.getData().putByteArray(MESSAGE_BUNDLE_KEY_MESSAGE_DATA, message);
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_MESSAGE_LENGTH, messageLength);
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * Place a callback request on the state machine queue: data-path confirmation received - i.e.
+     * data-path is now up.
+     */
+    public void onDataPathConfirmNotification(int ndpId, byte[] mac, boolean accept, int reason,
+            byte[] message, int messageLength) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_NOTIFICATION);
+        msg.arg1 = NOTIFICATION_TYPE_ON_DATA_PATH_CONFIRM;
+        msg.arg2 = ndpId;
+        msg.getData().putByteArray(MESSAGE_BUNDLE_KEY_MAC_ADDRESS, mac);
+        msg.getData().putBoolean(MESSAGE_BUNDLE_KEY_SUCCESS_FLAG, accept);
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_STATUS_CODE, reason);
+        msg.getData().putByteArray(MESSAGE_BUNDLE_KEY_MESSAGE_DATA, message);
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_MESSAGE_LENGTH, messageLength);
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * Place a callback request on the state machine queue: the specified data-path has been
+     * terminated.
+     */
+    public void onDataPathEndNotification(int ndpId) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_NOTIFICATION);
+        msg.arg1 = NOTIFICATION_TYPE_ON_DATA_PATH_END;
+        msg.arg2 = ndpId;
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * State machine.
+     */
+    @VisibleForTesting
+    class WifiNanStateMachine extends StateMachine {
+        private static final int TRANSACTION_ID_IGNORE = 0;
+
+        private DefaultState mDefaultState = new DefaultState();
+        private WaitState mWaitState = new WaitState();
+        private WaitForResponseState mWaitForResponseState = new WaitForResponseState();
+
+        private short mNextTransactionId = 1;
+        public int mNextSessionId = 1;
+
+        private Message mCurrentCommand;
+        private short mCurrentTransactionId = TRANSACTION_ID_IGNORE;
+
+        private static final long NAN_SEND_MESSAGE_TIMEOUT = 10_000;
+        private int mSendArrivalSequenceCounter = 0;
+        private boolean mSendQueueBlocked = false;
+        private final SparseArray<Message> mHostQueuedSendMessages = new SparseArray<>();
+        private final Map<Short, Message> mFwQueuedSendMessages = new LinkedHashMap<>();
+        private WakeupMessage mSendMessageTimeoutMessage = new WakeupMessage(mContext, getHandler(),
+                HAL_SEND_MESSAGE_TIMEOUT_TAG, MESSAGE_TYPE_SEND_MESSAGE_TIMEOUT);
+
+        private static final long NAN_WAIT_FOR_DP_CONFIRM_TIMEOUT = 5_000;
+        private final Map<String, WakeupMessage> mDataPathConfirmTimeoutMessages = new HashMap<>();
+
+        WifiNanStateMachine(String name, Looper looper) {
+            super(name, looper);
+
+            addState(mDefaultState);
+            /* --> */ addState(mWaitState, mDefaultState);
+            /* --> */ addState(mWaitForResponseState, mDefaultState);
+
+            setInitialState(mWaitState);
         }
 
-        @Override
-        public void handleMessage(Message msg) {
-            if (DBG) {
-                Log.d(TAG, "Message: " + msg.what);
+        public void onNanDownCleanupSendQueueState() {
+            mSendQueueBlocked = false;
+            mHostQueuedSendMessages.clear();
+            mFwQueuedSendMessages.clear();
+        }
+
+        private class DefaultState extends State {
+            @Override
+            public boolean processMessage(Message msg) {
+                if (VDBG) {
+                    Log.v(TAG, getName() + msg.toString());
+                }
+
+                switch (msg.what) {
+                    case MESSAGE_TYPE_NOTIFICATION:
+                        processNotification(msg);
+                        return HANDLED;
+                    case MESSAGE_TYPE_SEND_MESSAGE_TIMEOUT:
+                        processSendMessageTimeout();
+                        return HANDLED;
+                    case MESSAGE_TYPE_DATA_PATH_TIMEOUT: {
+                        String networkSpecifier = (String) msg.obj;
+
+                        if (VDBG) {
+                            Log.v(TAG, "MESSAGE_TYPE_DATA_PATH_TIMEOUT: networkSpecifier="
+                                    + networkSpecifier);
+                        }
+
+                        mDataPathMgr.handleDataPathTimeout(networkSpecifier);
+                        mDataPathConfirmTimeoutMessages.remove(networkSpecifier);
+                        return HANDLED;
+                    }
+                    default:
+                        /* fall-through */
+                }
+
+                Log.wtf(TAG,
+                        "DefaultState: should not get non-NOTIFICATION in this state: msg=" + msg);
+                return NOT_HANDLED;
             }
-            switch (msg.what) {
-                case MESSAGE_CONNECT: {
-                    if (VDBG) {
-                        Log.d(TAG, "NAN connection request received");
-                    }
-                    connectLocal(msg.arg1, (IWifiNanEventListener) msg.obj, msg.arg2);
-                    break;
-                }
-                case MESSAGE_DISCONNECT: {
-                    if (VDBG) {
-                        Log.d(TAG, "NAN disconnection request received");
-                    }
-                    disconnectLocal(msg.arg1);
-                    break;
-                }
-                case MESSAGE_REQUEST_CONFIG: {
-                    if (VDBG) {
-                        Log.d(TAG, "NAN configuration request received");
-                    }
-                    requestConfigLocal(msg.arg1, (ConfigRequest) msg.obj);
-                    break;
-                }
-                case MESSAGE_CREATE_SESSION: {
-                    if (VDBG) {
-                        Log.d(TAG, "Create session");
-                    }
-                    int events = msg.getData().getInt(MESSAGE_BUNDLE_KEY_EVENTS);
-                    createSessionLocal(msg.arg1, msg.arg2, (IWifiNanSessionListener) msg.obj,
-                            events);
-                    break;
-                }
-                case MESSAGE_DESTROY_SESSION: {
-                    if (VDBG) {
-                        Log.d(TAG, "Destroy session");
-                    }
-                    destroySessionLocal(msg.arg1, msg.arg2);
-                    break;
-                }
-                case MESSAGE_PUBLISH: {
-                    Bundle data = msg.getData();
-                    PublishData publishData = (PublishData) data
-                            .getParcelable(MESSAGE_BUNDLE_KEY_PUBLISH_DATA);
-                    PublishSettings publishSettings = (PublishSettings) data
-                            .getParcelable(MESSAGE_BUNDLE_KEY_PUBLISH_SETTINGS);
-                    if (VDBG) {
-                        Log.d(TAG,
-                                "Publish: data='" + publishData + "', settings=" + publishSettings);
-                    }
+        }
 
-                    publishLocal(msg.arg1, msg.arg2, publishData, publishSettings);
-                    break;
+        private class WaitState extends State {
+            @Override
+            public boolean processMessage(Message msg) {
+                if (VDBG) {
+                    Log.v(TAG, getName() + msg.toString());
                 }
-                case MESSAGE_SUBSCRIBE: {
-                    Bundle data = msg.getData();
-                    SubscribeData subscribeData = (SubscribeData) data
-                            .getParcelable(MESSAGE_BUNDLE_KEY_SUBSCRIBE_DATA);
-                    SubscribeSettings subscribeSettings = (SubscribeSettings) data
-                            .getParcelable(MESSAGE_BUNDLE_KEY_SUBSCRIBE_SETTINGS);
-                    if (VDBG) {
-                        Log.d(TAG, "Subscribe: data='" + subscribeData + "', settings="
-                                + subscribeSettings);
-                    }
 
-                    subscribeLocal(msg.arg1, msg.arg2, subscribeData, subscribeSettings);
-                    break;
+                switch (msg.what) {
+                    case MESSAGE_TYPE_COMMAND:
+                        if (processCommand(msg)) {
+                            transitionTo(mWaitForResponseState);
+                        }
+                        return HANDLED;
+                    case MESSAGE_TYPE_RESPONSE:
+                        /* fall-through */
+                    case MESSAGE_TYPE_RESPONSE_TIMEOUT:
+                        /*
+                         * remnants/delayed/out-of-sync messages - but let
+                         * WaitForResponseState deal with them (identified as
+                         * out-of-date by transaction ID).
+                         */
+                        deferMessage(msg);
+                        return HANDLED;
+                    default:
+                        /* fall-through */
                 }
-                case MESSAGE_SEND_MESSAGE: {
-                    Bundle data = msg.getData();
-                    int sessionId = msg.getData().getInt(MESSAGE_BUNDLE_KEY_SESSION_ID);
-                    int peerId = data.getInt(MESSAGE_BUNDLE_KEY_MESSAGE_PEER_ID);
-                    byte[] message = data.getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE);
-                    int messageId = data.getInt(MESSAGE_BUNDLE_KEY_MESSAGE_ID);
 
-                    if (VDBG) {
-                        Log.d(TAG, "Send Message: message='" + message + "' (ID=" + messageId
-                                + ") to peerId=" + peerId);
-                    }
+                return NOT_HANDLED;
+            }
+        }
 
-                    sendFollowonMessageLocal(msg.arg1, sessionId, peerId, message, msg.arg2,
-                            messageId);
+        private class WaitForResponseState extends State {
+            private static final long NAN_COMMAND_TIMEOUT = 5_000;
+            private WakeupMessage mTimeoutMessage;
+
+            @Override
+            public void enter() {
+                mTimeoutMessage = new WakeupMessage(mContext, getHandler(), HAL_COMMAND_TIMEOUT_TAG,
+                        MESSAGE_TYPE_RESPONSE_TIMEOUT, mCurrentCommand.arg1, mCurrentTransactionId);
+                mTimeoutMessage.schedule(SystemClock.elapsedRealtime() + NAN_COMMAND_TIMEOUT);
+            }
+
+            @Override
+            public void exit() {
+                mTimeoutMessage.cancel();
+            }
+
+            @Override
+            public boolean processMessage(Message msg) {
+                if (VDBG) {
+                    Log.v(TAG, getName() + msg.toString());
+                }
+
+                switch (msg.what) {
+                    case MESSAGE_TYPE_COMMAND:
+                        /*
+                         * don't want COMMANDs in this state - defer until back
+                         * in WaitState
+                         */
+                        deferMessage(msg);
+                        return HANDLED;
+                    case MESSAGE_TYPE_RESPONSE:
+                        if (msg.arg2 == mCurrentTransactionId) {
+                            processResponse(msg);
+                            transitionTo(mWaitState);
+                        } else {
+                            Log.w(TAG,
+                                    "WaitForResponseState: processMessage: non-matching "
+                                            + "transaction ID on RESPONSE (a very late "
+                                            + "response) -- msg=" + msg);
+                            /* no transition */
+                        }
+                        return HANDLED;
+                    case MESSAGE_TYPE_RESPONSE_TIMEOUT:
+                        if (msg.arg2 == mCurrentTransactionId) {
+                            processTimeout(msg);
+                            transitionTo(mWaitState);
+                        } else {
+                            Log.w(TAG, "WaitForResponseState: processMessage: non-matching "
+                                    + "transaction ID on RESPONSE_TIMEOUT (either a non-cancelled "
+                                    + "timeout or a race condition with cancel) -- msg=" + msg);
+                            /* no transition */
+                        }
+                        return HANDLED;
+                    default:
+                        /* fall-through */
+                }
+
+                return NOT_HANDLED;
+            }
+        }
+
+        private void processNotification(Message msg) {
+            if (VDBG) {
+                Log.v(TAG, "processNotification: msg=" + msg);
+            }
+
+            switch (msg.arg1) {
+                case NOTIFICATION_TYPE_INTERFACE_CHANGE: {
+                    byte[] mac = (byte[]) msg.obj;
+
+                    onInterfaceAddressChangeLocal(mac);
                     break;
                 }
-                case MESSAGE_STOP_SESSION: {
-                    if (VDBG) {
-                        Log.d(TAG, "Stop session");
-                    }
-                    stopSessionLocal(msg.arg1, msg.arg2);
+                case NOTIFICATION_TYPE_CLUSTER_CHANGE: {
+                    int flag = msg.arg2;
+                    byte[] clusterId = (byte[]) msg.obj;
+
+                    onClusterChangeLocal(flag, clusterId);
                     break;
                 }
-                case MESSAGE_ON_CAPABILITIES_UPDATED:
-                    onCapabilitiesUpdatedLocal((short) msg.arg1,
-                            (WifiNanNative.Capabilities) msg.obj);
-                    break;
-                case MESSAGE_ON_CONFIG_COMPLETED:
-                    onConfigCompletedLocal((short) msg.arg1);
-                    break;
-                case MESSAGE_ON_CONFIG_FAILED:
-                    onConfigFailedLocal((short) msg.arg1, msg.arg2);
-                    break;
-                case MESSAGE_ON_NAN_DOWN:
-                    onNanDownLocal(msg.arg1);
-                    break;
-                case MESSAGE_ON_INTERFACE_CHANGE:
-                    onInterfaceAddressChangeLocal((byte[]) msg.obj);
-                    break;
-                case MESSAGE_ON_CLUSTER_CHANGE:
-                    onClusterChangeLocal(msg.arg1, (byte[]) msg.obj);
-                    break;
-                case MESSAGE_ON_PUBLISH_SUCCESS:
-                    onPublishSuccessLocal((short) msg.arg1, msg.arg2);
-                    break;
-                case MESSAGE_ON_PUBLISH_FAIL:
-                    onPublishFailLocal((short) msg.arg1, msg.arg2);
-                    break;
-                case MESSAGE_ON_PUBLISH_TERMINATED:
-                    onPublishTerminatedLocal(msg.arg1, msg.arg2);
-                    break;
-                case MESSAGE_ON_SUBSCRIBE_SUCCESS:
-                    onSubscribeSuccessLocal((short) msg.arg1, msg.arg2);
-                    break;
-                case MESSAGE_ON_SUBSCRIBE_FAIL:
-                    onSubscribeFailLocal((short) msg.arg1, msg.arg2);
-                    break;
-                case MESSAGE_ON_SUBSCRIBE_TERMINATED:
-                    onSubscribeTerminatedLocal(msg.arg1, msg.arg2);
-                    break;
-                case MESSAGE_ON_MESSAGE_SEND_SUCCESS:
-                    onMessageSendSuccessLocal((short) msg.arg1);
-                    break;
-                case MESSAGE_ON_MESSAGE_SEND_FAIL:
-                    onMessageSendFailLocal((short) msg.arg1, msg.arg2);
-                    break;
-                case MESSAGE_ON_UNKNOWN_TRANSACTION:
-                    onUnknownTransactionLocal(
-                            msg.getData().getInt(MESSAGE_BUNDLE_KEY_RESPONSE_TYPE),
-                            (short) msg.arg1, msg.arg2);
-                    break;
-                case MESSAGE_ON_MATCH: {
-                    int pubSubId = msg.arg1;
-                    int requestorInstanceId = msg.arg2;
+                case NOTIFICATION_TYPE_MATCH: {
+                    int pubSubId = msg.arg2;
+                    int requestorInstanceId = msg.getData()
+                            .getInt(MESSAGE_BUNDLE_KEY_REQ_INSTANCE_ID);
                     byte[] peerMac = msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MAC_ADDRESS);
                     byte[] serviceSpecificInfo = msg.getData()
                             .getByteArray(MESSAGE_BUNDLE_KEY_SSI_DATA);
@@ -495,394 +1022,1361 @@
                             .getInt(MESSAGE_BUNDLE_KEY_SSI_LENGTH);
                     byte[] matchFilter = msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_FILTER_DATA);
                     int matchFilterLength = msg.getData().getInt(MESSAGE_BUNDLE_KEY_FILTER_LENGTH);
+
                     onMatchLocal(pubSubId, requestorInstanceId, peerMac, serviceSpecificInfo,
                             serviceSpecificInfoLength, matchFilter, matchFilterLength);
                     break;
                 }
-                case MESSAGE_ON_MESSAGE_RECEIVED: {
-                    int pubSubId = msg.arg1;
-                    int requestorInstanceId = msg.arg2;
+                case NOTIFICATION_TYPE_SESSION_TERMINATED: {
+                    int pubSubId = msg.arg2;
+                    int reason = (Integer) msg.obj;
+                    boolean isPublish = msg.getData().getBoolean(MESSAGE_BUNDLE_KEY_SESSION_TYPE);
+
+                    onSessionTerminatedLocal(pubSubId, isPublish, reason);
+                    break;
+                }
+                case NOTIFICATION_TYPE_MESSAGE_RECEIVED: {
+                    int pubSubId = msg.arg2;
+                    int requestorInstanceId = (Integer) msg.obj;
                     byte[] peerMac = msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MAC_ADDRESS);
                     byte[] message = msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE_DATA);
                     int messageLength = msg.getData().getInt(MESSAGE_BUNDLE_KEY_MESSAGE_LENGTH);
+
                     onMessageReceivedLocal(pubSubId, requestorInstanceId, peerMac, message,
                             messageLength);
                     break;
                 }
+                case NOTIFICATION_TYPE_NAN_DOWN: {
+                    int reason = msg.arg2;
+
+                    /*
+                     * TODO: b/28615938. Use reason code to determine whether or not need clean-up
+                     * local state (only needed if NAN_DOWN is due to internal firmware reason, e.g.
+                     * concurrency, rather than due to a requested shutdown).
+                     */
+
+                    onNanDownLocal();
+
+                    break;
+                }
+                case NOTIFICATION_TYPE_ON_MESSAGE_SEND_SUCCESS: {
+                    short transactionId = (short) msg.arg2;
+                    Message queuedSendCommand = mFwQueuedSendMessages.get(transactionId);
+                    if (VDBG) {
+                        Log.v(TAG, "NOTIFICATION_TYPE_ON_MESSAGE_SEND_SUCCESS: queuedSendCommand="
+                                + queuedSendCommand);
+                    }
+                    if (queuedSendCommand == null) {
+                        Log.w(TAG,
+                                "processNotification: NOTIFICATION_TYPE_ON_MESSAGE_SEND_SUCCESS:"
+                                        + " transactionId=" + transactionId
+                                        + " - no such queued send command (timed-out?)");
+                    } else {
+                        mFwQueuedSendMessages.remove(transactionId);
+                        updateSendMessageTimeout();
+                        onMessageSendSuccessLocal(queuedSendCommand);
+                    }
+                    mSendQueueBlocked = false;
+                    transmitNextMessage();
+
+                    break;
+                }
+                case NOTIFICATION_TYPE_ON_MESSAGE_SEND_FAIL: {
+                    short transactionId = (short) msg.arg2;
+                    int reason = (Integer) msg.obj;
+                    Message sentMessage = mFwQueuedSendMessages.get(transactionId);
+                    if (VDBG) {
+                        Log.v(TAG, "NOTIFICATION_TYPE_ON_MESSAGE_SEND_FAIL: sentMessage="
+                                + sentMessage);
+                    }
+                    if (sentMessage == null) {
+                        Log.w(TAG,
+                                "processNotification: NOTIFICATION_TYPE_ON_MESSAGE_SEND_FAIL:"
+                                        + " transactionId=" + transactionId
+                                        + " - no such queued send command (timed-out?)");
+                    } else {
+                        mFwQueuedSendMessages.remove(transactionId);
+                        updateSendMessageTimeout();
+
+                        int retryCount = sentMessage.getData()
+                                .getInt(MESSAGE_BUNDLE_KEY_RETRY_COUNT);
+                        if (retryCount > 0 && reason == WifiNanSessionCallback.REASON_TX_FAIL) {
+                            if (DBG) {
+                                Log.d(TAG,
+                                        "NOTIFICATION_TYPE_ON_MESSAGE_SEND_FAIL: transactionId="
+                                                + transactionId + ", reason=" + reason
+                                                + ": retransmitting - retryCount=" + retryCount);
+                            }
+                            sentMessage.getData().putInt(MESSAGE_BUNDLE_KEY_RETRY_COUNT,
+                                    retryCount - 1);
+
+                            int arrivalSeq = sentMessage.getData().getInt(
+                                    MESSAGE_BUNDLE_KEY_MESSAGE_ARRIVAL_SEQ);
+                            mHostQueuedSendMessages.put(arrivalSeq, sentMessage);
+                        } else {
+                            onMessageSendFailLocal(sentMessage, reason);
+                        }
+                        mSendQueueBlocked = false;
+                        transmitNextMessage();
+                    }
+                    break;
+                }
+                case NOTIFICATION_TYPE_ON_DATA_PATH_REQUEST: {
+                    String networkSpecifier = mDataPathMgr.onDataPathRequest(msg.arg2,
+                            msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MAC_ADDRESS),
+                            (int) msg.obj,
+                            msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE_DATA),
+                            msg.getData().getInt(MESSAGE_BUNDLE_KEY_MESSAGE_LENGTH));
+
+                    if (networkSpecifier != null) {
+                        WakeupMessage timeout = new WakeupMessage(mContext, getHandler(),
+                                HAL_DATA_PATH_CONFIRM_TIMEOUT_TAG, MESSAGE_TYPE_DATA_PATH_TIMEOUT,
+                                0, 0, networkSpecifier);
+                        mDataPathConfirmTimeoutMessages.put(networkSpecifier, timeout);
+                        timeout.schedule(
+                                SystemClock.elapsedRealtime() + NAN_WAIT_FOR_DP_CONFIRM_TIMEOUT);
+                    }
+
+                    break;
+                }
+                case NOTIFICATION_TYPE_ON_DATA_PATH_CONFIRM: {
+                    String networkSpecifier = mDataPathMgr.onDataPathConfirm(msg.arg2,
+                            msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MAC_ADDRESS),
+                            msg.getData().getBoolean(MESSAGE_BUNDLE_KEY_SUCCESS_FLAG),
+                            msg.getData().getInt(MESSAGE_BUNDLE_KEY_STATUS_CODE),
+                            msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE_DATA),
+                            msg.getData().getInt(MESSAGE_BUNDLE_KEY_MESSAGE_LENGTH));
+
+                    if (networkSpecifier != null) {
+                        WakeupMessage timeout = mDataPathConfirmTimeoutMessages.remove(
+                                networkSpecifier);
+                        if (timeout != null) {
+                            timeout.cancel();
+                        }
+                    }
+
+                    break;
+                }
+                case NOTIFICATION_TYPE_ON_DATA_PATH_END:
+                    mDataPathMgr.onDataPathEnd(msg.arg2);
+                    break;
                 default:
-                    Log.e(TAG, "Unknown message code: " + msg.what);
+                    Log.wtf(TAG, "processNotification: this isn't a NOTIFICATION -- msg=" + msg);
+                    return;
             }
         }
+
+        /**
+         * Execute the command specified by the input Message. Returns a true if
+         * need to wait for a RESPONSE, otherwise a false. We may not have to
+         * wait for a RESPONSE if there was an error in the state (so no command
+         * is sent to HAL) OR if we choose not to wait for response - e.g. for
+         * disconnected/terminate commands failure is not possible.
+         */
+        private boolean processCommand(Message msg) {
+            if (VDBG) {
+                Log.v(TAG, "processCommand: msg=" + msg);
+            }
+
+            if (mCurrentCommand != null) {
+                Log.wtf(TAG,
+                        "processCommand: receiving a command (msg=" + msg
+                                + ") but current (previous) command isn't null (prev_msg="
+                                + mCurrentCommand + ")");
+                mCurrentCommand = null;
+            }
+
+            mCurrentTransactionId = mNextTransactionId++;
+
+            boolean waitForResponse = true;
+
+            switch (msg.arg1) {
+                case COMMAND_TYPE_CONNECT: {
+                    int clientId = msg.arg2;
+                    IWifiNanEventCallback callback = (IWifiNanEventCallback) msg.obj;
+                    ConfigRequest configRequest = (ConfigRequest) msg.getData()
+                            .getParcelable(MESSAGE_BUNDLE_KEY_CONFIG);
+                    int uid = msg.getData().getInt(MESSAGE_BUNDLE_KEY_UID);
+
+                    waitForResponse = connectLocal(mCurrentTransactionId, clientId, uid, callback,
+                            configRequest);
+                    break;
+                }
+                case COMMAND_TYPE_DISCONNECT: {
+                    int clientId = msg.arg2;
+
+                    waitForResponse = disconnectLocal(mCurrentTransactionId, clientId);
+                    break;
+                }
+                case COMMAND_TYPE_TERMINATE_SESSION: {
+                    int clientId = msg.arg2;
+                    int sessionId = (Integer) msg.obj;
+
+                    terminateSessionLocal(clientId, sessionId);
+                    waitForResponse = false;
+                    break;
+                }
+                case COMMAND_TYPE_PUBLISH: {
+                    int clientId = msg.arg2;
+                    IWifiNanSessionCallback callback = (IWifiNanSessionCallback) msg.obj;
+                    PublishConfig publishConfig = (PublishConfig) msg.getData()
+                            .getParcelable(MESSAGE_BUNDLE_KEY_CONFIG);
+
+                    waitForResponse = publishLocal(mCurrentTransactionId, clientId, publishConfig,
+                            callback);
+                    break;
+                }
+                case COMMAND_TYPE_UPDATE_PUBLISH: {
+                    int clientId = msg.arg2;
+                    int sessionId = msg.getData().getInt(MESSAGE_BUNDLE_KEY_SESSION_ID);
+                    PublishConfig publishConfig = (PublishConfig) msg.obj;
+
+                    waitForResponse = updatePublishLocal(mCurrentTransactionId, clientId, sessionId,
+                            publishConfig);
+                    break;
+                }
+                case COMMAND_TYPE_SUBSCRIBE: {
+                    int clientId = msg.arg2;
+                    IWifiNanSessionCallback callback = (IWifiNanSessionCallback) msg.obj;
+                    SubscribeConfig subscribeConfig = (SubscribeConfig) msg.getData()
+                            .getParcelable(MESSAGE_BUNDLE_KEY_CONFIG);
+
+                    waitForResponse = subscribeLocal(mCurrentTransactionId, clientId,
+                            subscribeConfig, callback);
+                    break;
+                }
+                case COMMAND_TYPE_UPDATE_SUBSCRIBE: {
+                    int clientId = msg.arg2;
+                    int sessionId = msg.getData().getInt(MESSAGE_BUNDLE_KEY_SESSION_ID);
+                    SubscribeConfig subscribeConfig = (SubscribeConfig) msg.obj;
+
+                    waitForResponse = updateSubscribeLocal(mCurrentTransactionId, clientId,
+                            sessionId, subscribeConfig);
+                    break;
+                }
+                case COMMAND_TYPE_ENQUEUE_SEND_MESSAGE: {
+                    if (VDBG) {
+                        Log.v(TAG, "processCommand: ENQUEUE_SEND_MESSAGE - messageId="
+                                + msg.getData().getInt(MESSAGE_BUNDLE_KEY_MESSAGE_ID)
+                                + ", mSendArrivalSequenceCounter=" + mSendArrivalSequenceCounter);
+                    }
+                    Message sendMsg = obtainMessage(msg.what);
+                    sendMsg.copyFrom(msg);
+                    sendMsg.getData().putInt(MESSAGE_BUNDLE_KEY_MESSAGE_ARRIVAL_SEQ,
+                            mSendArrivalSequenceCounter);
+                    mHostQueuedSendMessages.put(mSendArrivalSequenceCounter, sendMsg);
+                    mSendArrivalSequenceCounter++;
+                    waitForResponse = false;
+
+                    if (!mSendQueueBlocked) {
+                        transmitNextMessage();
+                    }
+
+                    break;
+                }
+                case COMMAND_TYPE_TRANSMIT_NEXT_MESSAGE: {
+                    if (mSendQueueBlocked || mHostQueuedSendMessages.size() == 0) {
+                        if (VDBG) {
+                            Log.v(TAG, "processCommand: SEND_TOP_OF_QUEUE_MESSAGE - blocked or "
+                                    + "empty host queue");
+                        }
+                        waitForResponse = false;
+                    } else {
+                        if (VDBG) {
+                            Log.v(TAG, "processCommand: SEND_TOP_OF_QUEUE_MESSAGE - "
+                                    + "sendArrivalSequenceCounter="
+                                    + mHostQueuedSendMessages.keyAt(0));
+                        }
+                        Message sendMessage = mHostQueuedSendMessages.valueAt(0);
+                        mHostQueuedSendMessages.removeAt(0);
+
+                        Bundle data = sendMessage.getData();
+                        int clientId = sendMessage.arg2;
+                        int sessionId = sendMessage.getData().getInt(MESSAGE_BUNDLE_KEY_SESSION_ID);
+                        int peerId = data.getInt(MESSAGE_BUNDLE_KEY_MESSAGE_PEER_ID);
+                        byte[] message = data.getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE);
+                        int messageId = data.getInt(MESSAGE_BUNDLE_KEY_MESSAGE_ID);
+                        int messageLength = data.getInt(MESSAGE_BUNDLE_KEY_MESSAGE_LENGTH);
+
+                        msg.getData().putParcelable(MESSAGE_BUNDLE_KEY_SENT_MESSAGE, sendMessage);
+
+                        waitForResponse = sendFollowonMessageLocal(mCurrentTransactionId, clientId,
+                                sessionId, peerId, message, messageLength, messageId);
+                    }
+                    break;
+                }
+                case COMMAND_TYPE_ENABLE_USAGE:
+                    enableUsageLocal();
+                    waitForResponse = false;
+                    break;
+                case COMMAND_TYPE_DISABLE_USAGE:
+                    disableUsageLocal();
+                    waitForResponse = false;
+                    break;
+                case COMMAND_TYPE_START_RANGING: {
+                    Bundle data = msg.getData();
+
+                    int clientId = msg.arg2;
+                    RttManager.RttParams[] params = (RttManager.RttParams[]) msg.obj;
+                    int sessionId = data.getInt(MESSAGE_BUNDLE_KEY_SESSION_ID);
+                    int rangingId = data.getInt(MESSAGE_BUNDLE_KEY_RANGING_ID);
+
+                    startRangingLocal(clientId, sessionId, params, rangingId);
+                    waitForResponse = false;
+                    break;
+                }
+                case COMMAND_TYPE_GET_CAPABILITIES:
+                    if (mCapabilities == null) {
+                        waitForResponse = WifiNanNative.getInstance().getCapabilities(
+                                mCurrentTransactionId);
+                    } else {
+                        if (VDBG) {
+                            Log.v(TAG, "COMMAND_TYPE_GET_CAPABILITIES: already have capabilities - "
+                                    + "skipping");
+                        }
+                        waitForResponse = false;
+                    }
+                    break;
+                case COMMAND_TYPE_CREATE_ALL_DATA_PATH_INTERFACES:
+                    mDataPathMgr.createAllInterfaces();
+                    waitForResponse = false;
+                    break;
+                case COMMAND_TYPE_DELETE_ALL_DATA_PATH_INTERFACES:
+                    mDataPathMgr.deleteAllInterfaces();
+                    waitForResponse = false;
+                    break;
+                case COMMAND_TYPE_CREATE_DATA_PATH_INTERFACE:
+                    waitForResponse = WifiNanNative.getInstance().createNanNetworkInterface(
+                            mCurrentTransactionId, (String) msg.obj);
+                    break;
+                case COMMAND_TYPE_DELETE_DATA_PATH_INTERFACE:
+                    waitForResponse = WifiNanNative.getInstance().deleteNanNetworkInterface(
+                            mCurrentTransactionId, (String) msg.obj);
+                    break;
+                case COMMAND_TYPE_INITIATE_DATA_PATH_SETUP: {
+                    Bundle data = msg.getData();
+
+                    String networkSpecifier = (String) msg.obj;
+
+                    int peerId = data.getInt(MESSAGE_BUNDLE_KEY_PEER_ID);
+                    int channelRequestType = data.getInt(MESSAGE_BUNDLE_KEY_CHANNEL_REQ_TYPE);
+                    int channel = data.getInt(MESSAGE_BUNDLE_KEY_CHANNEL);
+                    byte[] peer = data.getByteArray(MESSAGE_BUNDLE_KEY_MAC_ADDRESS);
+                    String interfaceName = data.getString(MESSAGE_BUNDLE_KEY_INTERFACE_NAME);
+                    byte[] token = data.getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE);
+
+                    waitForResponse = initiateDataPathSetupLocal(mCurrentTransactionId, peerId,
+                            channelRequestType, channel, peer, interfaceName, token);
+
+                    if (waitForResponse) {
+                        WakeupMessage timeout = new WakeupMessage(mContext, getHandler(),
+                                HAL_DATA_PATH_CONFIRM_TIMEOUT_TAG, MESSAGE_TYPE_DATA_PATH_TIMEOUT,
+                                0, 0, networkSpecifier);
+                        mDataPathConfirmTimeoutMessages.put(networkSpecifier, timeout);
+                        timeout.schedule(
+                                SystemClock.elapsedRealtime() + NAN_WAIT_FOR_DP_CONFIRM_TIMEOUT);
+                    }
+                    break;
+                }
+                case COMMAND_TYPE_RESPOND_TO_DATA_PATH_SETUP_REQUEST: {
+                    Bundle data = msg.getData();
+
+                    int ndpId = msg.arg2;
+                    boolean accept = (boolean) msg.obj;
+                    String interfaceName = data.getString(MESSAGE_BUNDLE_KEY_INTERFACE_NAME);
+                    String token = data.getString(MESSAGE_BUNDLE_KEY_MESSAGE);
+
+                    waitForResponse = respondToDataPathRequestLocal(mCurrentTransactionId, accept,
+                            ndpId, interfaceName, token);
+
+                    break;
+                }
+                case COMMAND_TYPE_END_DATA_PATH:
+                    waitForResponse = endDataPathLocal(mCurrentTransactionId, msg.arg2);
+                    break;
+                default:
+                    waitForResponse = false;
+                    Log.wtf(TAG, "processCommand: this isn't a COMMAND -- msg=" + msg);
+                    /* fall-through */
+            }
+
+            if (!waitForResponse) {
+                mCurrentTransactionId = TRANSACTION_ID_IGNORE;
+            } else {
+                mCurrentCommand = obtainMessage(msg.what);
+                mCurrentCommand.copyFrom(msg);
+            }
+
+            return waitForResponse;
+        }
+
+        private void processResponse(Message msg) {
+            if (VDBG) {
+                Log.v(TAG, "processResponse: msg=" + msg);
+            }
+
+            if (mCurrentCommand == null) {
+                Log.wtf(TAG, "processResponse: no existing command stored!? msg=" + msg);
+                mCurrentTransactionId = TRANSACTION_ID_IGNORE;
+                return;
+            }
+
+            switch (msg.arg1) {
+                case RESPONSE_TYPE_ON_CONFIG_SUCCESS:
+                    onConfigCompletedLocal(mCurrentCommand);
+                    break;
+                case RESPONSE_TYPE_ON_CONFIG_FAIL: {
+                    int reason = (Integer) msg.obj;
+
+                    onConfigFailedLocal(mCurrentCommand, reason);
+                    break;
+                }
+                case RESPONSE_TYPE_ON_SESSION_CONFIG_SUCCESS: {
+                    int pubSubId = (Integer) msg.obj;
+                    boolean isPublish = msg.getData().getBoolean(MESSAGE_BUNDLE_KEY_SESSION_TYPE);
+
+                    onSessionConfigSuccessLocal(mCurrentCommand, pubSubId, isPublish);
+                    break;
+                }
+                case RESPONSE_TYPE_ON_SESSION_CONFIG_FAIL: {
+                    int reason = (Integer) msg.obj;
+                    boolean isPublish = msg.getData().getBoolean(MESSAGE_BUNDLE_KEY_SESSION_TYPE);
+
+                    onSessionConfigFailLocal(mCurrentCommand, isPublish, reason);
+                    break;
+                }
+                case RESPONSE_TYPE_ON_MESSAGE_SEND_QUEUED_SUCCESS: {
+                    Message sentMessage = mCurrentCommand.getData().getParcelable(
+                            MESSAGE_BUNDLE_KEY_SENT_MESSAGE);
+                    sentMessage.getData().putLong(MESSAGE_BUNDLE_KEY_SEND_MESSAGE_ENQUEUE_TIME,
+                            SystemClock.elapsedRealtime());
+                    mFwQueuedSendMessages.put(mCurrentTransactionId, sentMessage);
+                    updateSendMessageTimeout();
+                    if (!mSendQueueBlocked) {
+                        transmitNextMessage();
+                    }
+
+                    if (VDBG) {
+                        Log.v(TAG, "processResponse: ON_MESSAGE_SEND_QUEUED_SUCCESS - arrivalSeq="
+                                + sentMessage.getData().getInt(
+                                MESSAGE_BUNDLE_KEY_MESSAGE_ARRIVAL_SEQ));
+                    }
+                    break;
+                }
+                case RESPONSE_TYPE_ON_MESSAGE_SEND_QUEUED_FAIL: {
+                    if (VDBG) {
+                        Log.v(TAG, "processResponse: ON_MESSAGE_SEND_QUEUED_FAIL - blocking!");
+                    }
+                    // TODO: b/29459286 - once there's a unique code for "queue is full" use it!
+                    int reason = (Integer) msg.obj;
+
+                    Message sentMessage = mCurrentCommand.getData().getParcelable(
+                            MESSAGE_BUNDLE_KEY_SENT_MESSAGE);
+                    int arrivalSeq = sentMessage.getData().getInt(
+                            MESSAGE_BUNDLE_KEY_MESSAGE_ARRIVAL_SEQ);
+                    mHostQueuedSendMessages.put(arrivalSeq, sentMessage);
+                    mSendQueueBlocked = true;
+
+                    if (VDBG) {
+                        Log.v(TAG,
+                                "processResponse: ON_MESSAGE_SEND_QUEUED_FAIL - arrivalSeq="
+                                        + arrivalSeq + " -- blocking");
+                    }
+                    break;
+                }
+                case RESPONSE_TYPE_ON_CAPABILITIES_UPDATED: {
+                    onCapabilitiesUpdatedResponseLocal((WifiNanNative.Capabilities) msg.obj);
+                    break;
+                }
+                case RESPONSE_TYPE_ON_CREATE_INTERFACE:
+                    onCreateDataPathInterfaceResponseLocal(mCurrentCommand,
+                            msg.getData().getBoolean(MESSAGE_BUNDLE_KEY_SUCCESS_FLAG),
+                            msg.getData().getInt(MESSAGE_BUNDLE_KEY_STATUS_CODE));
+                    break;
+                case RESPONSE_TYPE_ON_DELETE_INTERFACE:
+                    onDeleteDataPathInterfaceResponseLocal(mCurrentCommand,
+                            msg.getData().getBoolean(MESSAGE_BUNDLE_KEY_SUCCESS_FLAG),
+                            msg.getData().getInt(MESSAGE_BUNDLE_KEY_STATUS_CODE));
+                    break;
+                case RESPONSE_TYPE_ON_INITIATE_DATA_PATH_SUCCESS:
+                    onInitiateDataPathResponseSuccessLocal(mCurrentCommand, (int) msg.obj);
+                    break;
+                case RESPONSE_TYPE_ON_INITIATE_DATA_PATH_FAIL:
+                    onInitiateDataPathResponseFailLocal(mCurrentCommand, (int) msg.obj);
+                    break;
+                case RESPONSE_TYPE_ON_RESPOND_TO_DATA_PATH_SETUP_REQUEST:
+                    onRespondToDataPathSetupRequestResponseLocal(mCurrentCommand,
+                            msg.getData().getBoolean(MESSAGE_BUNDLE_KEY_SUCCESS_FLAG),
+                            msg.getData().getInt(MESSAGE_BUNDLE_KEY_STATUS_CODE));
+                    break;
+                case RESPONSE_TYPE_ON_END_DATA_PATH:
+                    onEndPathEndResponseLocal(mCurrentCommand,
+                            msg.getData().getBoolean(MESSAGE_BUNDLE_KEY_SUCCESS_FLAG),
+                            msg.getData().getInt(MESSAGE_BUNDLE_KEY_STATUS_CODE));
+                    break;
+                default:
+                    Log.wtf(TAG, "processResponse: this isn't a RESPONSE -- msg=" + msg);
+                    mCurrentCommand = null;
+                    mCurrentTransactionId = TRANSACTION_ID_IGNORE;
+                    return;
+            }
+
+            mCurrentCommand = null;
+            mCurrentTransactionId = TRANSACTION_ID_IGNORE;
+        }
+
+        private void processTimeout(Message msg) {
+            if (VDBG) {
+                Log.v(TAG, "processTimeout: msg=" + msg);
+            }
+
+            if (mCurrentCommand == null) {
+                Log.wtf(TAG, "processTimeout: no existing command stored!? msg=" + msg);
+                mCurrentTransactionId = TRANSACTION_ID_IGNORE;
+                return;
+            }
+
+            /*
+             * Only have to handle those COMMANDs which wait for a response.
+             */
+            switch (msg.arg1) {
+                case COMMAND_TYPE_CONNECT: {
+                    onConfigFailedLocal(mCurrentCommand, WifiNanEventCallback.REASON_OTHER);
+                    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, WifiNanEventCallback.REASON_OTHER);
+                    break;
+                }
+                case COMMAND_TYPE_TERMINATE_SESSION: {
+                    Log.wtf(TAG, "processTimeout: TERMINATE_SESSION - shouldn't be waiting!");
+                    break;
+                }
+                case COMMAND_TYPE_PUBLISH: {
+                    onSessionConfigFailLocal(mCurrentCommand, true,
+                            WifiNanSessionCallback.REASON_OTHER);
+                    break;
+                }
+                case COMMAND_TYPE_UPDATE_PUBLISH: {
+                    onSessionConfigFailLocal(mCurrentCommand, true,
+                            WifiNanSessionCallback.REASON_OTHER);
+                    break;
+                }
+                case COMMAND_TYPE_SUBSCRIBE: {
+                    onSessionConfigFailLocal(mCurrentCommand, false,
+                            WifiNanSessionCallback.REASON_OTHER);
+                    break;
+                }
+                case COMMAND_TYPE_UPDATE_SUBSCRIBE: {
+                    onSessionConfigFailLocal(mCurrentCommand, false,
+                            WifiNanSessionCallback.REASON_OTHER);
+                    break;
+                }
+                case COMMAND_TYPE_ENQUEUE_SEND_MESSAGE: {
+                    Log.wtf(TAG, "processTimeout: ENQUEUE_SEND_MESSAGE - shouldn't be waiting!");
+                    break;
+                }
+                case COMMAND_TYPE_TRANSMIT_NEXT_MESSAGE: {
+                    Message sentMessage = mCurrentCommand.getData().getParcelable(
+                            MESSAGE_BUNDLE_KEY_SENT_MESSAGE);
+                    onMessageSendFailLocal(sentMessage, WifiNanSessionCallback.REASON_TX_FAIL);
+                    mSendQueueBlocked = false;
+                    transmitNextMessage();
+                    break;
+                }
+                case COMMAND_TYPE_ENABLE_USAGE:
+                    Log.wtf(TAG, "processTimeout: ENABLE_USAGE - shouldn't be waiting!");
+                    break;
+                case COMMAND_TYPE_DISABLE_USAGE:
+                    Log.wtf(TAG, "processTimeout: DISABLE_USAGE - shouldn't be waiting!");
+                    break;
+                case COMMAND_TYPE_START_RANGING:
+                    Log.wtf(TAG, "processTimeout: START_RANGING - shouldn't be waiting!");
+                    break;
+                case COMMAND_TYPE_GET_CAPABILITIES:
+                    Log.e(TAG,
+                            "processTimeout: GET_CAPABILITIES timed-out - strange, will try again"
+                                    + " when next enabled!?");
+                    break;
+                case COMMAND_TYPE_CREATE_ALL_DATA_PATH_INTERFACES:
+                    Log.wtf(TAG,
+                            "processTimeout: CREATE_ALL_DATA_PATH_INTERFACES - shouldn't be "
+                                    + "waiting!");
+                    break;
+                case COMMAND_TYPE_DELETE_ALL_DATA_PATH_INTERFACES:
+                    Log.wtf(TAG,
+                            "processTimeout: DELETE_ALL_DATA_PATH_INTERFACES - shouldn't be "
+                                    + "waiting!");
+                    break;
+                case COMMAND_TYPE_CREATE_DATA_PATH_INTERFACE:
+                    // TODO: fix status: timeout
+                    onCreateDataPathInterfaceResponseLocal(mCurrentCommand, false, 0);
+                    break;
+                case COMMAND_TYPE_DELETE_DATA_PATH_INTERFACE:
+                    // TODO: fix status: timeout
+                    onDeleteDataPathInterfaceResponseLocal(mCurrentCommand, false, 0);
+                    break;
+                case COMMAND_TYPE_INITIATE_DATA_PATH_SETUP:
+                    // TODO: fix status: timeout
+                    onInitiateDataPathResponseFailLocal(mCurrentCommand, 0);
+                    break;
+                case COMMAND_TYPE_RESPOND_TO_DATA_PATH_SETUP_REQUEST:
+                    // TODO: fix status: timeout
+                    onRespondToDataPathSetupRequestResponseLocal(mCurrentCommand, false, 0);
+                    break;
+                case COMMAND_TYPE_END_DATA_PATH:
+                    // TODO: fix status: timeout
+                    onEndPathEndResponseLocal(mCurrentCommand, false, 0);
+                    break;
+                default:
+                    Log.wtf(TAG, "processTimeout: this isn't a COMMAND -- msg=" + msg);
+                    /* fall-through */
+            }
+
+            mCurrentCommand = null;
+            mCurrentTransactionId = TRANSACTION_ID_IGNORE;
+        }
+
+        private void updateSendMessageTimeout() {
+            if (VDBG) {
+                Log.v(TAG, "updateSendMessageTimeout: mHostQueuedSendMessages.size()="
+                        + mHostQueuedSendMessages.size() + ", mFwQueuedSendMessages.size()="
+                        + mFwQueuedSendMessages.size() + ", mSendQueueBlocked="
+                        + mSendQueueBlocked);
+            }
+            Iterator<Message> it = mFwQueuedSendMessages.values().iterator();
+            if (it.hasNext()) {
+                /*
+                 * Schedule timeout based on the first message in the queue (which is the earliest
+                 * submitted message). Timeout = queuing time + timeout constant.
+                 */
+                Message msg = it.next();
+                mSendMessageTimeoutMessage.schedule(
+                        msg.getData().getLong(MESSAGE_BUNDLE_KEY_SEND_MESSAGE_ENQUEUE_TIME)
+                        + NAN_SEND_MESSAGE_TIMEOUT);
+            } else {
+                mSendMessageTimeoutMessage.cancel();
+            }
+        }
+
+        private void processSendMessageTimeout() {
+            if (VDBG) {
+                Log.v(TAG, "processSendMessageTimeout: mHostQueuedSendMessages.size()="
+                        + mHostQueuedSendMessages.size() + ", mFwQueuedSendMessages.size()="
+                        + mFwQueuedSendMessages.size() + ", mSendQueueBlocked="
+                        + mSendQueueBlocked);
+
+            }
+            /*
+             * Note: using 'first' to always time-out (remove) at least 1 notification (partially)
+             * due to test code needs: there's no way to mock elapsedRealtime(). TODO: replace with
+             * injected getClock() once moved off of mmwd.
+             */
+            boolean first = true;
+            long currentTime = SystemClock.elapsedRealtime();
+            Iterator<Map.Entry<Short, Message>> it = mFwQueuedSendMessages.entrySet().iterator();
+            while (it.hasNext()) {
+                Map.Entry<Short, Message> entry = it.next();
+                short transactionId = entry.getKey();
+                Message message = entry.getValue();
+                long messageEnqueueTime = message.getData().getLong(
+                        MESSAGE_BUNDLE_KEY_SEND_MESSAGE_ENQUEUE_TIME);
+                if (first || messageEnqueueTime + NAN_SEND_MESSAGE_TIMEOUT <= currentTime) {
+                    if (VDBG) {
+                        Log.v(TAG, "processSendMessageTimeout: expiring - transactionId="
+                                + transactionId + ", message=" + message
+                                + ", due to messageEnqueueTime=" + messageEnqueueTime
+                                + ", currentTime=" + currentTime);
+                    }
+                    onMessageSendFailLocal(message, WifiNanSessionCallback.REASON_TX_FAIL);
+                    it.remove();
+                    first = false;
+                } else {
+                    break;
+                }
+            }
+            updateSendMessageTimeout();
+            mSendQueueBlocked = false;
+            transmitNextMessage();
+        }
+
+        @Override
+        protected String getLogRecString(Message msg) {
+            StringBuilder sb = new StringBuilder(WifiNanStateManager.messageToString(msg));
+
+            if (msg.what == MESSAGE_TYPE_COMMAND
+                    && mCurrentTransactionId != TRANSACTION_ID_IGNORE) {
+                sb.append(" (Transaction ID=").append(mCurrentTransactionId).append(")");
+            }
+
+            return sb.toString();
+        }
+
+        @Override
+        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            pw.println("WifiNanStateMachine:");
+            pw.println("  mNextTransactionId: " + mNextTransactionId);
+            pw.println("  mNextSessionId: " + mNextSessionId);
+            pw.println("  mCurrentCommand: " + mCurrentCommand);
+            pw.println("  mCurrentTransaction: " + mCurrentTransactionId);
+            pw.println("  mSendQueueBlocked: " + mSendQueueBlocked);
+            pw.println("  mSendArrivalSequenceCounter: " + mSendArrivalSequenceCounter);
+            pw.println("  mHostQueuedSendMessages: [" + mHostQueuedSendMessages + "]");
+            pw.println("  mFwQueuedSendMessages: [" + mFwQueuedSendMessages + "]");
+            super.dump(fd, pw, args);
+        }
+    }
+
+    private void sendNanStateChangedBroadcast(boolean enabled) {
+        if (VDBG) {
+            Log.v(TAG, "sendNanStateChangedBroadcast: enabled=" + enabled);
+        }
+        final Intent intent = new Intent(WifiNanManager.WIFI_NAN_STATE_CHANGED_ACTION);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        if (enabled) {
+            intent.putExtra(WifiNanManager.EXTRA_WIFI_STATE, WifiNanManager.WIFI_NAN_STATE_ENABLED);
+        } else {
+            intent.putExtra(WifiNanManager.EXTRA_WIFI_STATE,
+                    WifiNanManager.WIFI_NAN_STATE_DISABLED);
+        }
+        mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
     }
 
     /*
-     * Transaction management classes & operations
+     * COMMANDS
      */
 
-    // non-synchronized (should be ok as long as only used from NanStateManager,
-    // NanClientState, and NanSessionState)
-    /* package */ short createNextTransactionId() {
-        return mNextTransactionId++;
-    }
-
-    private static class TransactionInfoBase {
-        short mTransactionId;
-    }
-
-    private static class TransactionInfoSession extends TransactionInfoBase {
-        public WifiNanClientState mClient;
-        public WifiNanSessionState mSession;
-    }
-
-    private static class TransactionInfoMessage extends TransactionInfoSession {
-        public int mMessageId;
-    }
-
-    private static class TransactionInfoConfig extends TransactionInfoBase {
-        public ConfigRequest mConfig;
-    }
-
-    private void allocateAndRegisterTransactionId(TransactionInfoBase info) {
-        info.mTransactionId = createNextTransactionId();
-
-        mPendingResponses.put(info.mTransactionId, info);
-    }
-
-    private void fillInTransactionInfoSession(TransactionInfoSession info, int uid,
-            int sessionId) {
-        WifiNanClientState client = mClients.get(uid);
-        if (client == null) {
-            throw new IllegalStateException(
-                    "getAndRegisterTransactionId: no client exists for uid=" + uid);
-        }
-        info.mClient = client;
-
-        WifiNanSessionState session = info.mClient.getSession(sessionId);
-        if (session == null) {
-            throw new IllegalStateException(
-                    "getAndRegisterSessionTransactionId: no session exists for uid=" + uid
-                            + ", sessionId=" + sessionId);
-        }
-        info.mSession = session;
-    }
-
-    private TransactionInfoBase createTransactionInfo() {
-        TransactionInfoBase info = new TransactionInfoBase();
-        allocateAndRegisterTransactionId(info);
-        return info;
-    }
-
-    private TransactionInfoSession createTransactionInfoSession(int uid, int sessionId) {
-        TransactionInfoSession info = new TransactionInfoSession();
-        fillInTransactionInfoSession(info, uid, sessionId);
-        allocateAndRegisterTransactionId(info);
-        return info;
-    }
-
-    private TransactionInfoMessage createTransactionInfoMessage(int uid, int sessionId,
-            int messageId) {
-        TransactionInfoMessage info = new TransactionInfoMessage();
-        fillInTransactionInfoSession(info, uid, sessionId);
-        info.mMessageId = messageId;
-        allocateAndRegisterTransactionId(info);
-        return info;
-    }
-
-    private TransactionInfoConfig createTransactionInfoConfig(ConfigRequest configRequest) {
-        TransactionInfoConfig info = new TransactionInfoConfig();
-        info.mConfig = configRequest;
-        allocateAndRegisterTransactionId(info);
-        return info;
-    }
-
-    private TransactionInfoBase getAndRemovePendingResponseTransactionInfo(short transactionId) {
-        TransactionInfoBase transInfo = mPendingResponses.get(transactionId);
-        if (transInfo != null) {
-            mPendingResponses.remove(transactionId);
-        }
-
-        return transInfo;
-    }
-
-    private WifiNanSessionState getNanSessionStateForPubSubId(int pubSubId) {
-        for (int i = 0; i < mClients.size(); ++i) {
-            WifiNanSessionState session = mClients.valueAt(i)
-                    .getNanSessionStateForPubSubId(pubSubId);
-            if (session != null) {
-                return session;
-            }
-        }
-
-        return null;
-    }
-
-    /*
-     * Actions (calls from API to service)
-     */
-    private void connectLocal(int uid, IWifiNanEventListener listener, int events) {
+    private boolean connectLocal(short transactionId, int clientId, int uid,
+            IWifiNanEventCallback callback, ConfigRequest configRequest) {
         if (VDBG) {
-            Log.v(TAG, "connect(): uid=" + uid + ", listener=" + listener + ", events=" + events);
+            Log.v(TAG, "connectLocal(): transactionId=" + transactionId + ", clientId=" + clientId
+                    + ", uid=" + uid + ", callback=" + callback + ", configRequest="
+                    + configRequest);
         }
 
-        if (mClients.get(uid) != null) {
-            Log.e(TAG, "connect: entry already exists for uid=" + uid);
-            return;
+        if (!mUsageEnabled) {
+            Log.w(TAG, "connect(): called with mUsageEnabled=false");
+            return false;
         }
 
-        WifiNanClientState client = new WifiNanClientState(uid, listener, events);
-        mClients.put(uid, client);
+        if (mClients.get(clientId) != null) {
+            Log.e(TAG, "connectLocal: entry already exists for clientId=" + clientId);
+        }
+
+        if (mCurrentNanConfiguration != null
+                && !mCurrentNanConfiguration.equalsOnTheAir(configRequest)) {
+            try {
+                callback.onConnectFail(
+                        WifiNanEventCallback.REASON_ALREADY_CONNECTED_INCOMPAT_CONFIG);
+            } catch (RemoteException e) {
+                Log.w(TAG, "connectLocal onConnectFail(): RemoteException (FYI): " + e);
+            }
+            return false;
+        }
+
+        ConfigRequest merged = mergeConfigRequests(configRequest);
+        if (mCurrentNanConfiguration != null && mCurrentNanConfiguration.equals(merged)) {
+            try {
+                callback.onConnectSuccess();
+            } catch (RemoteException e) {
+                Log.w(TAG, "connectLocal onConnectSuccess(): RemoteException (FYI): " + e);
+            }
+            WifiNanClientState client = new WifiNanClientState(clientId, uid, callback,
+                    configRequest);
+            client.onInterfaceAddressChange(mCurrentDiscoveryInterfaceMac);
+            mClients.append(clientId, client);
+            return false;
+        }
+
+        return WifiNanNative.getInstance().enableAndConfigure(transactionId, merged,
+                mCurrentNanConfiguration == null);
     }
 
-    private void disconnectLocal(int uid) {
+    private boolean disconnectLocal(short transactionId, int clientId) {
         if (VDBG) {
-            Log.v(TAG, "disconnect(): uid=" + uid);
+            Log.v(TAG,
+                    "disconnectLocal(): transactionId=" + transactionId + ", clientId=" + clientId);
         }
 
-        WifiNanClientState client = mClients.get(uid);
-        mClients.delete(uid);
-
+        WifiNanClientState client = mClients.get(clientId);
         if (client == null) {
-            Log.e(TAG, "disconnect: no entry for uid=" + uid);
-            return;
+            Log.e(TAG, "disconnectLocal: no entry for clientId=" + clientId);
+            return false;
         }
-
-        List<Integer> toRemove = new ArrayList<>();
-        for (int i = 0; i < mPendingResponses.size(); ++i) {
-            TransactionInfoBase info = mPendingResponses.valueAt(i);
-            if (!(info instanceof TransactionInfoSession)) {
-                continue;
-            }
-            if (((TransactionInfoSession) info).mClient.getUid() == uid) {
-                toRemove.add(i);
-            }
-        }
-        for (Integer id : toRemove) {
-            mPendingResponses.removeAt(id);
-        }
-
+        mClients.delete(clientId);
         client.destroy();
 
         if (mClients.size() == 0) {
-            WifiNanNative.getInstance().disable(createTransactionInfo().mTransactionId);
-            return;
+            mCurrentNanConfiguration = null;
+            WifiNanNative.getInstance().disable((short) 0);
+            return false;
         }
 
-        ConfigRequest merged = mergeConfigRequests();
+        ConfigRequest merged = mergeConfigRequests(null);
+        if (merged.equals(mCurrentNanConfiguration)) {
+            return false;
+        }
 
-        WifiNanNative.getInstance()
-                .enableAndConfigure(createTransactionInfoConfig(merged).mTransactionId, merged);
+        return WifiNanNative.getInstance().enableAndConfigure(transactionId, merged, false);
     }
 
-    private void requestConfigLocal(int uid, ConfigRequest configRequest) {
+    private void terminateSessionLocal(int clientId, int sessionId) {
         if (VDBG) {
-            Log.v(TAG, "requestConfig(): uid=" + uid + ", configRequest=" + configRequest);
+            Log.v(TAG,
+                    "terminateSessionLocal(): clientId=" + clientId + ", sessionId=" + sessionId);
         }
 
-        WifiNanClientState client = mClients.get(uid);
+        WifiNanClientState client = mClients.get(clientId);
         if (client == null) {
-            Log.e(TAG, "requestConfig: no client exists for uid=" + uid);
+            Log.e(TAG, "terminateSession: no client exists for clientId=" + clientId);
             return;
         }
 
-        client.setConfigRequest(configRequest);
-
-        ConfigRequest merged = mergeConfigRequests();
-
-        WifiNanNative.getInstance()
-                .enableAndConfigure(createTransactionInfoConfig(merged).mTransactionId, merged);
+        client.terminateSession(sessionId);
     }
 
-    private void createSessionLocal(int uid, int sessionId, IWifiNanSessionListener listener,
-            int events) {
+    private boolean publishLocal(short transactionId, int clientId, PublishConfig publishConfig,
+            IWifiNanSessionCallback callback) {
         if (VDBG) {
-            Log.v(TAG, "createSession(): uid=" + uid + ", sessionId=" + sessionId + ", listener="
-                    + listener + ", events=" + events);
+            Log.v(TAG, "publishLocal(): transactionId=" + transactionId + ", clientId=" + clientId
+                    + ", publishConfig=" + publishConfig + ", callback=" + callback);
         }
 
-        WifiNanClientState client = mClients.get(uid);
+        WifiNanClientState client = mClients.get(clientId);
         if (client == null) {
-            Log.e(TAG, "createSession: no client exists for uid=" + uid);
-            return;
+            Log.e(TAG, "publishLocal: no client exists for clientId=" + clientId);
+            return false;
         }
 
-        client.createSession(sessionId, listener, events);
+        return WifiNanNative.getInstance().publish(transactionId, 0, publishConfig);
     }
 
-    private void destroySessionLocal(int uid, int sessionId) {
+    private boolean updatePublishLocal(short transactionId, int clientId, int sessionId,
+            PublishConfig publishConfig) {
         if (VDBG) {
-            Log.v(TAG, "destroySession(): uid=" + uid + ", sessionId=" + sessionId);
+            Log.v(TAG, "updatePublishLocal(): transactionId=" + transactionId + ", clientId="
+                    + clientId + ", sessionId=" + sessionId + ", publishConfig=" + publishConfig);
         }
 
-        WifiNanClientState client = mClients.get(uid);
+        WifiNanClientState client = mClients.get(clientId);
         if (client == null) {
-            Log.e(TAG, "destroySession: no client exists for uid=" + uid);
+            Log.e(TAG, "updatePublishLocal: no client exists for clientId=" + clientId);
+            return false;
+        }
+
+        WifiNanSessionState session = client.getSession(sessionId);
+        if (session == null) {
+            Log.e(TAG, "updatePublishLocal: no session exists for clientId=" + clientId
+                    + ", sessionId=" + sessionId);
+            return false;
+        }
+
+        return session.updatePublish(transactionId, publishConfig);
+    }
+
+    private boolean subscribeLocal(short transactionId, int clientId,
+            SubscribeConfig subscribeConfig, IWifiNanSessionCallback callback) {
+        if (VDBG) {
+            Log.v(TAG, "subscribeLocal(): transactionId=" + transactionId + ", clientId=" + clientId
+                    + ", subscribeConfig=" + subscribeConfig + ", callback=" + callback);
+        }
+
+        WifiNanClientState client = mClients.get(clientId);
+        if (client == null) {
+            Log.e(TAG, "subscribeLocal: no client exists for clientId=" + clientId);
+            return false;
+        }
+
+        return WifiNanNative.getInstance().subscribe(transactionId, 0, subscribeConfig);
+    }
+
+    private boolean updateSubscribeLocal(short transactionId, int clientId, int sessionId,
+            SubscribeConfig subscribeConfig) {
+        if (VDBG) {
+            Log.v(TAG,
+                    "updateSubscribeLocal(): transactionId=" + transactionId + ", clientId="
+                            + clientId + ", sessionId=" + sessionId + ", subscribeConfig="
+                            + subscribeConfig);
+        }
+
+        WifiNanClientState client = mClients.get(clientId);
+        if (client == null) {
+            Log.e(TAG, "updateSubscribeLocal: no client exists for clientId=" + clientId);
+            return false;
+        }
+
+        WifiNanSessionState session = client.getSession(sessionId);
+        if (session == null) {
+            Log.e(TAG, "updateSubscribeLocal: no session exists for clientId=" + clientId
+                    + ", sessionId=" + sessionId);
+            return false;
+        }
+
+        return session.updateSubscribe(transactionId, subscribeConfig);
+    }
+
+    private boolean sendFollowonMessageLocal(short transactionId, int clientId, int sessionId,
+            int peerId, byte[] message, int messageLength, int messageId) {
+        if (VDBG) {
+            Log.v(TAG,
+                    "sendFollowonMessageLocal(): transactionId=" + transactionId + ", clientId="
+                            + clientId + ", sessionId=" + sessionId + ", peerId=" + peerId
+                            + ", messageLength=" + messageLength + ", messageId=" + messageId);
+        }
+
+        WifiNanClientState client = mClients.get(clientId);
+        if (client == null) {
+            Log.e(TAG, "sendFollowonMessageLocal: no client exists for clientId=" + clientId);
+            return false;
+        }
+
+        WifiNanSessionState session = client.getSession(sessionId);
+        if (session == null) {
+            Log.e(TAG, "sendFollowonMessageLocal: no session exists for clientId=" + clientId
+                    + ", sessionId=" + sessionId);
+            return false;
+        }
+
+        return session.sendMessage(transactionId, peerId, message, messageLength, messageId);
+    }
+
+    private void enableUsageLocal() {
+        if (mUsageEnabled) {
             return;
         }
 
-        List<Integer> toRemove = new ArrayList<>();
-        for (int i = 0; i < mPendingResponses.size(); ++i) {
-            TransactionInfoBase info = mPendingResponses.valueAt(i);
-            if (!(info instanceof TransactionInfoSession)) {
-                continue;
-            }
-            TransactionInfoSession infoSession = (TransactionInfoSession) info;
-            if (infoSession.mClient.getUid() == uid
-                    && infoSession.mSession.getSessionId() == sessionId) {
-                toRemove.add(i);
+        mUsageEnabled = true;
+        getCapabilities();
+        createAllDataPathInterfaces();
+        sendNanStateChangedBroadcast(true);
+    }
+
+    private void disableUsageLocal() {
+        if (!mUsageEnabled) {
+            return;
+        }
+
+        mUsageEnabled = false;
+        WifiNanNative.getInstance().disable((short) 0);
+        WifiNanNative.getInstance().deInitNan();
+        onNanDownLocal();
+
+        deleteAllDataPathInterfaces();
+        sendNanStateChangedBroadcast(false);
+    }
+
+    private void startRangingLocal(int clientId, int sessionId, RttManager.RttParams[] params,
+                                   int rangingId) {
+        if (VDBG) {
+            Log.v(TAG, "startRangingLocal: clientId=" + clientId + ", sessionId=" + sessionId
+                    + ", parms=" + Arrays.toString(params) + ", rangingId=" + rangingId);
+        }
+
+        WifiNanClientState client = mClients.get(clientId);
+        if (client == null) {
+            Log.e(TAG, "startRangingLocal: no client exists for clientId=" + clientId);
+            return;
+        }
+
+        WifiNanSessionState session = client.getSession(sessionId);
+        if (session == null) {
+            Log.e(TAG, "startRangingLocal: no session exists for clientId=" + clientId
+                    + ", sessionId=" + sessionId);
+            client.onRangingFailure(rangingId, RttManager.REASON_INVALID_REQUEST,
+                    "Invalid session ID");
+            return;
+        }
+
+        for (RttManager.RttParams param : params) {
+            String peerIdStr = param.bssid;
+            try {
+                param.bssid = session.getMac(Integer.parseInt(peerIdStr), ":");
+                if (param.bssid == null) {
+                    Log.d(TAG, "startRangingLocal: no MAC address for peer ID=" + peerIdStr);
+                    param.bssid = "";
+                }
+            } catch (NumberFormatException e) {
+                Log.e(TAG, "startRangingLocal: invalid peer ID specification (in bssid field): '"
+                        + peerIdStr + "'");
+                param.bssid = "";
             }
         }
-        for (Integer id : toRemove) {
-            mPendingResponses.removeAt(id);
-        }
 
-        client.destroySession(sessionId);
+        mRtt.startRanging(rangingId, client, params);
     }
 
-    private void publishLocal(int uid, int sessionId, PublishData publishData,
-            PublishSettings publishSettings) {
+    private boolean initiateDataPathSetupLocal(short transactionId, int peerId,
+            int channelRequestType, int channel, byte[] peer, String interfaceName, byte[] token) {
         if (VDBG) {
-            Log.v(TAG, "publish(): uid=" + uid + ", sessionId=" + sessionId + ", data="
-                    + publishData + ", settings=" + publishSettings);
+            Log.v(TAG,
+                    "initiateDataPathSetupLocal(): transactionId=" + transactionId + ", peerId="
+                            + peerId + ", channelRequestType=" + channelRequestType + ", channel="
+                            + channel + ", peer=" + String.valueOf(HexEncoding.encode(peer))
+                            + ", interfaceName=" + interfaceName + ", token=" + token);
         }
 
-        TransactionInfoSession info = createTransactionInfoSession(uid, sessionId);
-
-        info.mSession.publish(info.mTransactionId, publishData, publishSettings);
+        return WifiNanNative.getInstance().initiateDataPath(transactionId, peerId,
+                channelRequestType, channel, peer, interfaceName, token, token.length);
     }
 
-    private void subscribeLocal(int uid, int sessionId, SubscribeData subscribeData,
-            SubscribeSettings subscribeSettings) {
+    private boolean respondToDataPathRequestLocal(short transactionId, boolean accept,
+            int ndpId, String interfaceName, String token) {
         if (VDBG) {
-            Log.v(TAG, "subscribe(): uid=" + uid + ", sessionId=" + sessionId + ", data="
-                    + subscribeData + ", settings=" + subscribeSettings);
+            Log.v(TAG,
+                    "respondToDataPathRequestLocal(): transactionId=" + transactionId + ", accept="
+                            + accept + ", ndpId=" + ndpId + ", interfaceName=" + interfaceName
+                            + ", token=" + token);
         }
 
-        TransactionInfoSession info = createTransactionInfoSession(uid, sessionId);
+        byte[] tokenBytes = token.getBytes();
 
-        info.mSession.subscribe(info.mTransactionId, subscribeData, subscribeSettings);
+        return WifiNanNative.getInstance().respondToDataPathRequest(transactionId, accept, ndpId,
+                interfaceName, tokenBytes, tokenBytes.length);
     }
 
-    private void sendFollowonMessageLocal(int uid, int sessionId, int peerId, byte[] message,
-            int messageLength, int messageId) {
+    private boolean endDataPathLocal(short transactionId, int ndpId) {
         if (VDBG) {
-            Log.v(TAG, "sendMessage(): uid=" + uid + ", sessionId=" + sessionId + ", peerId="
-                    + peerId + ", messageLength=" + messageLength + ", messageId=" + messageId);
+            Log.v(TAG,
+                    "endDataPathLocal: transactionId=" + transactionId + ", ndpId=" + ndpId);
         }
 
-        TransactionInfoMessage info = createTransactionInfoMessage(uid, sessionId, messageId);
-
-        info.mSession.sendMessage(info.mTransactionId, peerId, message, messageLength, messageId);
-    }
-
-    private void stopSessionLocal(int uid, int sessionId) {
-        if (VDBG) {
-            Log.v(TAG, "stopSession(): uid=" + uid + ", sessionId=" + sessionId);
-        }
-
-        TransactionInfoSession info = createTransactionInfoSession(uid, sessionId);
-
-        info.mSession.stop(info.mTransactionId);
+        return WifiNanNative.getInstance().endDataPath(transactionId, ndpId);
     }
 
     /*
-     * Callbacks (calls from HAL/Native to service)
+     * RESPONSES
      */
 
-    private void onCapabilitiesUpdatedLocal(short transactionId,
-            WifiNanNative.Capabilities capabilities) {
+    private void onConfigCompletedLocal(Message completedCommand) {
         if (VDBG) {
-            Log.v(TAG, "onCapabilitiesUpdatedLocal: transactionId=" + transactionId
-                    + ", capabilites=" + capabilities);
+            Log.v(TAG, "onConfigCompleted: completedCommand=" + completedCommand);
+        }
+
+        if (completedCommand.arg1 == COMMAND_TYPE_CONNECT) {
+            Bundle data = completedCommand.getData();
+
+            int clientId = completedCommand.arg2;
+            IWifiNanEventCallback callback = (IWifiNanEventCallback) completedCommand.obj;
+            ConfigRequest configRequest = (ConfigRequest) data
+                    .getParcelable(MESSAGE_BUNDLE_KEY_CONFIG);
+            int uid = data.getInt(MESSAGE_BUNDLE_KEY_UID);
+
+            WifiNanClientState client = new WifiNanClientState(clientId, uid, callback,
+                    configRequest);
+            mClients.put(clientId, client);
+            try {
+                callback.onConnectSuccess();
+            } catch (RemoteException e) {
+                Log.w(TAG,
+                        "onConfigCompletedLocal onConnectSuccess(): RemoteException (FYI): " + e);
+            }
+            client.onInterfaceAddressChange(mCurrentDiscoveryInterfaceMac);
+        } else if (completedCommand.arg1 == COMMAND_TYPE_DISCONNECT) {
+            /*
+             * NOP (i.e. updated configuration after disconnecting a client)
+             */
+        } else {
+            Log.wtf(TAG, "onConfigCompletedLocal: unexpected completedCommand=" + completedCommand);
+            return;
+        }
+
+        mCurrentNanConfiguration = mergeConfigRequests(null);
+    }
+
+    private void onConfigFailedLocal(Message failedCommand, int reason) {
+        if (VDBG) {
+            Log.v(TAG,
+                    "onConfigFailedLocal: failedCommand=" + failedCommand + ", reason=" + reason);
+        }
+
+        if (failedCommand.arg1 == COMMAND_TYPE_CONNECT) {
+            IWifiNanEventCallback callback = (IWifiNanEventCallback) failedCommand.obj;
+
+            try {
+                callback.onConnectFail(reason);
+            } catch (RemoteException e) {
+                Log.w(TAG, "onConfigFailedLocal onConnectFail(): RemoteException (FYI): " + e);
+            }
+        } else if (failedCommand.arg1 == COMMAND_TYPE_DISCONNECT) {
+            /*
+             * 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).
+             */
+        } else {
+            Log.wtf(TAG, "onConfigFailedLocal: unexpected failedCommand=" + failedCommand);
+            return;
+        }
+
+    }
+
+    private void onSessionConfigSuccessLocal(Message completedCommand, int pubSubId,
+            boolean isPublish) {
+        if (VDBG) {
+            Log.v(TAG, "onSessionConfigSuccessLocal: completedCommand=" + completedCommand
+                    + ", pubSubId=" + pubSubId + ", isPublish=" + isPublish);
+        }
+
+        if (completedCommand.arg1 == COMMAND_TYPE_PUBLISH
+                || completedCommand.arg1 == COMMAND_TYPE_SUBSCRIBE) {
+            int clientId = completedCommand.arg2;
+            IWifiNanSessionCallback callback = (IWifiNanSessionCallback) completedCommand.obj;
+
+            WifiNanClientState client = mClients.get(clientId);
+            if (client == null) {
+                Log.e(TAG,
+                        "onSessionConfigSuccessLocal: no client exists for clientId=" + clientId);
+                return;
+            }
+
+            int sessionId = mSm.mNextSessionId++;
+            try {
+                callback.onSessionStarted(sessionId);
+            } catch (RemoteException e) {
+                Log.e(TAG, "onSessionConfigSuccessLocal: onSessionStarted() RemoteException=" + e);
+                return;
+            }
+
+            WifiNanSessionState session = new WifiNanSessionState(sessionId, pubSubId, callback,
+                    isPublish);
+            client.addSession(session);
+        } else if (completedCommand.arg1 == COMMAND_TYPE_UPDATE_PUBLISH
+                || completedCommand.arg1 == COMMAND_TYPE_UPDATE_SUBSCRIBE) {
+            int clientId = completedCommand.arg2;
+            int sessionId = completedCommand.getData().getInt(MESSAGE_BUNDLE_KEY_SESSION_ID);
+
+            WifiNanClientState client = mClients.get(clientId);
+            if (client == null) {
+                Log.e(TAG,
+                        "onSessionConfigSuccessLocal: no client exists for clientId=" + clientId);
+                return;
+            }
+
+            WifiNanSessionState session = client.getSession(sessionId);
+            if (session == null) {
+                Log.e(TAG, "onSessionConfigSuccessLocal: no session exists for clientId=" + clientId
+                        + ", sessionId=" + sessionId);
+                return;
+            }
+
+            try {
+                session.getCallback().onSessionConfigSuccess();
+            } catch (RemoteException e) {
+                Log.e(TAG, "onSessionConfigSuccessLocal: onSessionConfigSuccess() RemoteException="
+                        + e);
+            }
+        } else {
+            Log.wtf(TAG,
+                    "onSessionConfigSuccessLocal: unexpected completedCommand=" + completedCommand);
+        }
+    }
+
+    private void onSessionConfigFailLocal(Message failedCommand, boolean isPublish, int reason) {
+        if (VDBG) {
+            Log.v(TAG, "onSessionConfigFailLocal: failedCommand=" + failedCommand + ", isPublish="
+                    + isPublish + ", reason=" + reason);
+        }
+
+        if (failedCommand.arg1 == COMMAND_TYPE_PUBLISH
+                || failedCommand.arg1 == COMMAND_TYPE_SUBSCRIBE) {
+            IWifiNanSessionCallback callback = (IWifiNanSessionCallback) failedCommand.obj;
+            try {
+                callback.onSessionConfigFail(reason);
+            } catch (RemoteException e) {
+                Log.w(TAG, "onSessionConfigFailLocal onSessionConfigFail(): RemoteException (FYI): "
+                        + e);
+            }
+        } else if (failedCommand.arg1 == COMMAND_TYPE_UPDATE_PUBLISH
+                || failedCommand.arg1 == COMMAND_TYPE_UPDATE_SUBSCRIBE) {
+            int clientId = failedCommand.arg2;
+            int sessionId = failedCommand.getData().getInt(MESSAGE_BUNDLE_KEY_SESSION_ID);
+
+            WifiNanClientState client = mClients.get(clientId);
+            if (client == null) {
+                Log.e(TAG, "onSessionConfigFailLocal: no client exists for clientId=" + clientId);
+                return;
+            }
+
+            WifiNanSessionState session = client.getSession(sessionId);
+            if (session == null) {
+                Log.e(TAG, "onSessionConfigFailLocal: no session exists for clientId=" + clientId
+                        + ", sessionId=" + sessionId);
+                return;
+            }
+
+            try {
+                session.getCallback().onSessionConfigFail(reason);
+            } catch (RemoteException e) {
+                Log.e(TAG, "onSessionConfigFailLocal: onSessionConfigFail() RemoteException=" + e);
+            }
+        } else {
+            Log.wtf(TAG, "onSessionConfigFailLocal: unexpected failedCommand=" + failedCommand);
+        }
+    }
+
+    private void onMessageSendSuccessLocal(Message completedCommand) {
+        if (VDBG) {
+            Log.v(TAG, "onMessageSendSuccess: completedCommand=" + completedCommand);
+        }
+
+        int clientId = completedCommand.arg2;
+        int sessionId = completedCommand.getData().getInt(MESSAGE_BUNDLE_KEY_SESSION_ID);
+        int messageId = completedCommand.getData().getInt(MESSAGE_BUNDLE_KEY_MESSAGE_ID);
+
+        WifiNanClientState client = mClients.get(clientId);
+        if (client == null) {
+            Log.e(TAG, "onMessageSendSuccessLocal: no client exists for clientId=" + clientId);
+            return;
+        }
+
+        WifiNanSessionState session = client.getSession(sessionId);
+        if (session == null) {
+            Log.e(TAG, "onMessageSendSuccessLocal: no session exists for clientId=" + clientId
+                    + ", sessionId=" + sessionId);
+            return;
+        }
+
+        try {
+            session.getCallback().onMessageSendSuccess(messageId);
+        } catch (RemoteException e) {
+            Log.w(TAG, "onMessageSendSuccessLocal: RemoteException (FYI): " + e);
+        }
+    }
+
+    private void onMessageSendFailLocal(Message failedCommand, int reason) {
+        if (VDBG) {
+            Log.v(TAG, "onMessageSendFail: failedCommand=" + failedCommand + ", reason=" + reason);
+        }
+
+        int clientId = failedCommand.arg2;
+        int sessionId = failedCommand.getData().getInt(MESSAGE_BUNDLE_KEY_SESSION_ID);
+        int messageId = failedCommand.getData().getInt(MESSAGE_BUNDLE_KEY_MESSAGE_ID);
+
+        WifiNanClientState client = mClients.get(clientId);
+        if (client == null) {
+            Log.e(TAG, "onMessageSendFailLocal: no client exists for clientId=" + clientId);
+            return;
+        }
+
+        WifiNanSessionState session = client.getSession(sessionId);
+        if (session == null) {
+            Log.e(TAG, "onMessageSendFailLocal: no session exists for clientId=" + clientId
+                    + ", sessionId=" + sessionId);
+            return;
+        }
+
+        try {
+            session.getCallback().onMessageSendFail(messageId, reason);
+        } catch (RemoteException e) {
+            Log.e(TAG, "onMessageSendFailLocal: onMessageSendFail RemoteException=" + e);
+        }
+    }
+
+    private void onCapabilitiesUpdatedResponseLocal(WifiNanNative.Capabilities capabilities) {
+        if (VDBG) {
+            Log.v(TAG, "onCapabilitiesUpdatedResponseLocal: capabilites=" + capabilities);
         }
 
         mCapabilities = capabilities;
     }
 
-    private void onConfigCompletedLocal(short transactionId) {
+    private void onCreateDataPathInterfaceResponseLocal(Message command, boolean success,
+            int reasonOnFailure) {
         if (VDBG) {
-            Log.v(TAG, "onConfigCompleted: transactionId=" + transactionId);
+            Log.v(TAG, "onCreateDataPathInterfaceResponseLocal: command=" + command + ", success="
+                    + success + ", reasonOnFailure=" + reasonOnFailure);
         }
 
-        TransactionInfoBase info = getAndRemovePendingResponseTransactionInfo(transactionId);
-        if (info == null) {
-            Log.e(TAG, "onConfigCompleted: no transaction info for transactionId=" + transactionId);
-            return;
-        }
-        if (!(info instanceof TransactionInfoConfig)) {
-            Log.e(TAG, "onConfigCompleted: invalid info structure stored for transactionId="
-                    + transactionId);
-            return;
-        }
-        TransactionInfoConfig infoConfig = (TransactionInfoConfig) info;
-
-        if (DBG) {
-            Log.d(TAG, "onConfigCompleted: request=" + infoConfig.mConfig);
-        }
-
-        for (int i = 0; i < mClients.size(); ++i) {
-            WifiNanClientState client = mClients.valueAt(i);
-            client.onConfigCompleted(infoConfig.mConfig);
+        if (success) {
+            if (DBG) {
+                Log.d(TAG, "onCreateDataPathInterfaceResponseLocal: successfully created interface "
+                        + command.obj);
+            }
+            mDataPathMgr.onInterfaceCreated((String) command.obj);
+        } else {
+            Log.e(TAG,
+                    "onCreateDataPathInterfaceResponseLocal: failed when trying to create "
+                            + "interface "
+                            + command.obj + ". Reason code=" + reasonOnFailure);
         }
     }
 
-    private void onConfigFailedLocal(short transactionId, int reason) {
+    private void onDeleteDataPathInterfaceResponseLocal(Message command, boolean success,
+            int reasonOnFailure) {
         if (VDBG) {
-            Log.v(TAG, "onEnableFailed: transactionId=" + transactionId + ", reason=" + reason);
+            Log.v(TAG, "onDeleteDataPathInterfaceResponseLocal: command=" + command + ", success="
+                    + success + ", reasonOnFailure=" + reasonOnFailure);
         }
 
-        TransactionInfoBase info = getAndRemovePendingResponseTransactionInfo(transactionId);
-        if (info == null) {
-            Log.e(TAG, "onConfigFailed: no transaction info for transactionId=" + transactionId);
-            return;
-        }
-        if (!(info instanceof TransactionInfoConfig)) {
-            Log.e(TAG, "onConfigCompleted: invalid info structure stored for transactionId="
-                    + transactionId);
-            return;
-        }
-        TransactionInfoConfig infoConfig = (TransactionInfoConfig) info;
-
-        if (DBG) {
-            Log.d(TAG, "onConfigFailed: request=" + infoConfig.mConfig);
-        }
-
-        for (int i = 0; i < mClients.size(); ++i) {
-            WifiNanClientState client = mClients.valueAt(i);
-            client.onConfigFailed(infoConfig.mConfig, reason);
+        if (success) {
+            if (DBG) {
+                Log.d(TAG, "onDeleteDataPathInterfaceResponseLocal: successfully deleted interface "
+                        + command.obj);
+            }
+            mDataPathMgr.onInterfaceDeleted((String) command.obj);
+        } else {
+            Log.e(TAG,
+                    "onDeleteDataPathInterfaceResponseLocal: failed when trying to delete "
+                            + "interface "
+                            + command.obj + ". Reason code=" + reasonOnFailure);
         }
     }
 
-    private void onNanDownLocal(int reason) {
+    private void onInitiateDataPathResponseSuccessLocal(Message command, int ndpId) {
         if (VDBG) {
-            Log.v(TAG, "onNanDown: reason=" + reason);
+            Log.v(TAG, "onInitiateDataPathResponseSuccessLocal: command=" + command + ", ndpId="
+                    + ndpId);
         }
 
-        int interested = 0;
-        for (int i = 0; i < mClients.size(); ++i) {
-            WifiNanClientState client = mClients.valueAt(i);
-            interested += client.onNanDown(reason);
-        }
-
-        if (interested == 0) {
-            Log.e(TAG, "onNanDown: event received but no listeners registered for this event "
-                    + "- should be disabled from fw!");
-        }
+        mDataPathMgr.onDataPathInitiateSuccess((String) command.obj, ndpId);
     }
 
+    private void onInitiateDataPathResponseFailLocal(Message command, int reason) {
+        if (VDBG) {
+            Log.v(TAG, "onInitiateDataPathResponseFailLocal: command=" + command + ", reason="
+                    + reason);
+        }
+
+        mDataPathMgr.onDataPathInitiateFail((String) command.obj, reason);
+    }
+
+    private void onRespondToDataPathSetupRequestResponseLocal(Message command, boolean success,
+            int reasonOnFailure) {
+        if (VDBG) {
+            Log.v(TAG, "onRespondToDataPathSetupRequestResponseLocal: command=" + command
+                    + ", success=" + success + ", reasonOnFailure=" + reasonOnFailure);
+        }
+
+        // TODO: do something with this
+    }
+
+    private void onEndPathEndResponseLocal(Message command, boolean success, int reasonOnFailure) {
+        if (VDBG) {
+            Log.v(TAG, "onEndPathEndResponseLocal: command=" + command
+                    + ", success=" + success + ", reasonOnFailure=" + reasonOnFailure);
+        }
+
+        // TODO: do something with this
+    }
+
+    /*
+     * NOTIFICATIONS
+     */
+
     private void onInterfaceAddressChangeLocal(byte[] mac) {
         if (VDBG) {
             Log.v(TAG, "onInterfaceAddressChange: mac=" + String.valueOf(HexEncoding.encode(mac)));
         }
 
-        int interested = 0;
+        mCurrentDiscoveryInterfaceMac = mac;
+
         for (int i = 0; i < mClients.size(); ++i) {
             WifiNanClientState client = mClients.valueAt(i);
-            interested += client.onInterfaceAddressChange(mac);
-        }
-
-        if (interested == 0) {
-            Log.e(TAG, "onInterfaceAddressChange: event received but no listeners registered "
-                    + "for this event - should be disabled from fw!");
+            client.onInterfaceAddressChange(mac);
         }
     }
 
@@ -892,179 +2386,9 @@
                     + String.valueOf(HexEncoding.encode(clusterId)));
         }
 
-        int interested = 0;
         for (int i = 0; i < mClients.size(); ++i) {
             WifiNanClientState client = mClients.valueAt(i);
-            interested += client.onClusterChange(flag, clusterId);
-        }
-
-        if (interested == 0) {
-            Log.e(TAG, "onClusterChange: event received but no listeners registered for this "
-                    + "event - should be disabled from fw!");
-        }
-    }
-
-    private void onPublishSuccessLocal(short transactionId, int publishId) {
-        if (VDBG) {
-            Log.v(TAG, "onPublishSuccess: transactionId=" + transactionId + ", publishId="
-                    + publishId);
-        }
-
-        TransactionInfoBase info = getAndRemovePendingResponseTransactionInfo(transactionId);
-        if (info == null) {
-            Log.e(TAG, "onPublishSuccess(): no info registered for transactionId=" + transactionId);
-            return;
-        }
-        if (!(info instanceof TransactionInfoSession)) {
-            Log.e(TAG, "onPublishSuccess: invalid info structure stored for transactionId="
-                    + transactionId);
-            return;
-        }
-        TransactionInfoSession infoSession = (TransactionInfoSession) info;
-
-        infoSession.mSession.onPublishSuccess(publishId);
-    }
-
-    private void onPublishFailLocal(short transactionId, int status) {
-        if (VDBG) {
-            Log.v(TAG, "onPublishFail: transactionId=" + transactionId + ", status=" + status);
-        }
-
-        TransactionInfoBase info = getAndRemovePendingResponseTransactionInfo(transactionId);
-        if (info == null) {
-            Log.e(TAG, "onPublishFail(): no info registered for transactionId=" + transactionId);
-            return;
-        }
-        if (!(info instanceof TransactionInfoSession)) {
-            Log.e(TAG, "onPublishFail: invalid info structure stored for transactionId="
-                    + transactionId);
-            return;
-        }
-        TransactionInfoSession infoSession = (TransactionInfoSession) info;
-
-        infoSession.mSession.onPublishFail(status);
-    }
-
-    private void onPublishTerminatedLocal(int publishId, int status) {
-        if (VDBG) {
-            Log.v(TAG, "onPublishTerminated: publishId=" + publishId + ", status=" + status);
-        }
-
-        WifiNanSessionState session = getNanSessionStateForPubSubId(publishId);
-        if (session == null) {
-            Log.e(TAG, "onPublishTerminated: no session found for publishId=" + publishId);
-            return;
-        }
-
-        session.onPublishTerminated(status);
-    }
-
-    private void onSubscribeSuccessLocal(short transactionId, int subscribeId) {
-        if (VDBG) {
-            Log.v(TAG, "onSubscribeSuccess: transactionId=" + transactionId + ", subscribeId="
-                    + subscribeId);
-        }
-
-        TransactionInfoBase info = getAndRemovePendingResponseTransactionInfo(transactionId);
-        if (info == null) {
-            Log.e(TAG,
-                    "onSubscribeSuccess(): no info registered for transactionId=" + transactionId);
-            return;
-        }
-        if (!(info instanceof TransactionInfoSession)) {
-            Log.e(TAG, "onSubscribeSuccess: invalid info structure stored for transactionId="
-                    + transactionId);
-            return;
-        }
-        TransactionInfoSession infoSession = (TransactionInfoSession) info;
-
-        infoSession.mSession.onSubscribeSuccess(subscribeId);
-    }
-
-    private void onSubscribeFailLocal(short transactionId, int status) {
-        if (VDBG) {
-            Log.v(TAG, "onSubscribeFail: transactionId=" + transactionId + ", status=" + status);
-        }
-
-        TransactionInfoBase info = getAndRemovePendingResponseTransactionInfo(transactionId);
-        if (info == null) {
-            Log.e(TAG, "onSubscribeFail(): no info registered for transactionId=" + transactionId);
-            return;
-        }
-        if (!(info instanceof TransactionInfoSession)) {
-            Log.e(TAG, "onSubscribeFail: invalid info structure stored for transactionId="
-                    + transactionId);
-            return;
-        }
-        TransactionInfoSession infoSession = (TransactionInfoSession) info;
-
-        infoSession.mSession.onSubscribeFail(status);
-    }
-
-    private void onSubscribeTerminatedLocal(int subscribeId, int status) {
-        if (VDBG) {
-            Log.v(TAG, "onPublishTerminated: subscribeId=" + subscribeId + ", status=" + status);
-        }
-
-        WifiNanSessionState session = getNanSessionStateForPubSubId(subscribeId);
-        if (session == null) {
-            Log.e(TAG, "onSubscribeTerminated: no session found for subscribeId=" + subscribeId);
-            return;
-        }
-
-        session.onSubscribeTerminated(status);
-    }
-
-    private void onMessageSendSuccessLocal(short transactionId) {
-        if (VDBG) {
-            Log.v(TAG, "onMessageSendSuccess: transactionId=" + transactionId);
-        }
-
-        TransactionInfoBase info = getAndRemovePendingResponseTransactionInfo(transactionId);
-        if (info == null) {
-            Log.e(TAG, "onMessageSendSuccess(): no info registered for transactionId="
-                    + transactionId);
-            return;
-        }
-        if (!(info instanceof TransactionInfoMessage)) {
-            Log.e(TAG, "onMessageSendSuccess: invalid info structure stored for transactionId="
-                    + transactionId);
-            return;
-        }
-        TransactionInfoMessage infoMessage = (TransactionInfoMessage) info;
-
-        infoMessage.mSession.onMessageSendSuccess(infoMessage.mMessageId);
-    }
-
-    private void onMessageSendFailLocal(short transactionId, int status) {
-        if (VDBG) {
-            Log.v(TAG, "onMessageSendFail: transactionId=" + transactionId + ", status=" + status);
-        }
-
-        TransactionInfoBase info = getAndRemovePendingResponseTransactionInfo(transactionId);
-        if (info == null) {
-            Log.e(TAG,
-                    "onMessageSendFail(): no info registered for transactionId=" + transactionId);
-            return;
-        }
-        if (!(info instanceof TransactionInfoMessage)) {
-            Log.e(TAG, "onMessageSendFail: invalid info structure stored for transactionId="
-                    + transactionId);
-            return;
-        }
-        TransactionInfoMessage infoMessage = (TransactionInfoMessage) info;
-
-        infoMessage.mSession.onMessageSendFail(infoMessage.mMessageId, status);
-    }
-
-    private void onUnknownTransactionLocal(int responseType, short transactionId, int status) {
-        Log.e(TAG, "onUnknownTransaction: responseType=" + responseType + ", transactionId="
-                + transactionId + ", status=" + status);
-
-        TransactionInfoBase info = getAndRemovePendingResponseTransactionInfo(transactionId);
-        if (info == null) {
-            Log.e(TAG, "onUnknownTransaction(): no info registered for transactionId="
-                    + transactionId);
+            client.onClusterChange(flag, clusterId, mCurrentDiscoveryInterfaceMac);
         }
     }
 
@@ -1073,65 +2397,123 @@
             int matchFilterLength) {
         if (VDBG) {
             Log.v(TAG, "onMatch: pubSubId=" + pubSubId + ", requestorInstanceId="
-                    + requestorInstanceId + ", peerMac="
+                    + requestorInstanceId + ", peerDiscoveryMac="
                     + String.valueOf(HexEncoding.encode(peerMac)) + ", serviceSpecificInfoLength="
-                    + serviceSpecificInfoLength + ", serviceSpecificInfo=" + serviceSpecificInfo
-                    + ", matchFilterLength=" + matchFilterLength + ", matchFilter=" + matchFilter);
+                    + serviceSpecificInfoLength + ", serviceSpecificInfo="
+                    + Arrays.toString(serviceSpecificInfo) + ", matchFilterLength="
+                    + matchFilterLength + ", matchFilter=" + Arrays.toString(matchFilter));
         }
 
-        WifiNanSessionState session = getNanSessionStateForPubSubId(pubSubId);
-        if (session == null) {
+        Pair<WifiNanClientState, WifiNanSessionState> data = getClientSessionForPubSubId(pubSubId);
+        if (data == null) {
             Log.e(TAG, "onMatch: no session found for pubSubId=" + pubSubId);
             return;
         }
 
-        session.onMatch(requestorInstanceId, peerMac, serviceSpecificInfo,
+        data.second.onMatch(requestorInstanceId, peerMac, serviceSpecificInfo,
                 serviceSpecificInfoLength, matchFilter, matchFilterLength);
     }
 
+    private void onSessionTerminatedLocal(int pubSubId, boolean isPublish, int reason) {
+        if (VDBG) {
+            Log.v(TAG, "onSessionTerminatedLocal: pubSubId=" + pubSubId + ", isPublish=" + isPublish
+                    + ", reason=" + reason);
+        }
+
+        Pair<WifiNanClientState, WifiNanSessionState> data = getClientSessionForPubSubId(pubSubId);
+        if (data == null) {
+            Log.e(TAG, "onSessionTerminatedLocal: no session found for pubSubId=" + pubSubId);
+            return;
+        }
+
+        try {
+            data.second.getCallback().onSessionTerminated(reason);
+        } catch (RemoteException e) {
+            Log.w(TAG,
+                    "onSessionTerminatedLocal onSessionTerminated(): RemoteException (FYI): " + e);
+        }
+        data.first.removeSession(data.second.getSessionId());
+    }
+
     private void onMessageReceivedLocal(int pubSubId, int requestorInstanceId, byte[] peerMac,
             byte[] message, int messageLength) {
         if (VDBG) {
             Log.v(TAG,
-                    "onMessageReceived: pubSubId=" + pubSubId + ", requestorInstanceId="
-                            + requestorInstanceId + ", peerMac="
+                    "onMessageReceivedLocal: pubSubId=" + pubSubId + ", requestorInstanceId="
+                            + requestorInstanceId + ", peerDiscoveryMac="
                             + String.valueOf(HexEncoding.encode(peerMac)) + ", messageLength="
                             + messageLength);
         }
 
-        WifiNanSessionState session = getNanSessionStateForPubSubId(pubSubId);
-        if (session == null) {
-            Log.e(TAG, "onMessageReceived: no session found for pubSubId=" + pubSubId);
+        Pair<WifiNanClientState, WifiNanSessionState> data = getClientSessionForPubSubId(pubSubId);
+        if (data == null) {
+            Log.e(TAG, "onMessageReceivedLocal: no session found for pubSubId=" + pubSubId);
             return;
         }
 
-        session.onMessageReceived(requestorInstanceId, peerMac, message, messageLength);
+        data.second.onMessageReceived(requestorInstanceId, peerMac, message, messageLength);
     }
 
-    private ConfigRequest mergeConfigRequests() {
+    private void onNanDownLocal() {
         if (VDBG) {
-            Log.v(TAG, "mergeConfigRequests(): mClients=[" + mClients + "]");
+            Log.v(TAG, "onNanDown");
         }
 
-        if (mClients.size() == 0) {
+        mClients.clear();
+        mCurrentNanConfiguration = null;
+        mSm.onNanDownCleanupSendQueueState();
+        mDataPathMgr.onNanDownCleanupDataPaths();
+        mCurrentDiscoveryInterfaceMac = ALL_ZERO_MAC;
+    }
+
+    /*
+     * Utilities
+     */
+
+    private Pair<WifiNanClientState, WifiNanSessionState> getClientSessionForPubSubId(
+            int pubSubId) {
+        for (int i = 0; i < mClients.size(); ++i) {
+            WifiNanClientState client = mClients.valueAt(i);
+            WifiNanSessionState session = client.getNanSessionStateForPubSubId(pubSubId);
+            if (session != null) {
+                return new Pair<>(client, session);
+            }
+        }
+
+        return null;
+    }
+
+    private ConfigRequest mergeConfigRequests(ConfigRequest configRequest) {
+        if (VDBG) {
+            Log.v(TAG, "mergeConfigRequests(): mClients=[" + mClients + "], configRequest="
+                    + configRequest);
+        }
+
+        if (mClients.size() == 0 && configRequest == null) {
             Log.e(TAG, "mergeConfigRequests: invalid state - called with 0 clients registered!");
             return null;
         }
 
-        if (mClients.size() == 1) {
-            return mClients.valueAt(0).getConfigRequest();
-        }
-
         // TODO: continue working on merge algorithm:
         // - if any request 5g: enable
         // - maximal master preference
         // - cluster range covering all requests: assume that [0,max] is a
         // non-request
+        // - if any request identity change: enable
         boolean support5gBand = false;
         int masterPreference = 0;
         boolean clusterIdValid = false;
         int clusterLow = 0;
         int clusterHigh = ConfigRequest.CLUSTER_ID_MAX;
+        boolean identityChange = false;
+        if (configRequest != null) {
+            support5gBand = configRequest.mSupport5gBand;
+            masterPreference = configRequest.mMasterPreference;
+            clusterIdValid = true;
+            clusterLow = configRequest.mClusterLow;
+            clusterHigh = configRequest.mClusterHigh;
+            identityChange = configRequest.mEnableIdentityChangeCallback;
+        }
         for (int i = 0; i < mClients.size(); ++i) {
             ConfigRequest cr = mClients.valueAt(i).getConfigRequest();
 
@@ -1151,22 +2533,56 @@
                 }
                 clusterIdValid = true;
             }
-        }
-        ConfigRequest.Builder builder = new ConfigRequest.Builder();
-        builder.setSupport5gBand(support5gBand).setMasterPreference(masterPreference)
-                .setClusterLow(clusterLow).setClusterHigh(clusterHigh);
 
-        return builder.build();
+            if (cr.mEnableIdentityChangeCallback) {
+                identityChange = true;
+            }
+        }
+        return new ConfigRequest.Builder().setSupport5gBand(support5gBand)
+                .setMasterPreference(masterPreference).setClusterLow(clusterLow)
+                .setClusterHigh(clusterHigh).setEnableIdentityChangeCallback(identityChange)
+                .build();
     }
 
+    private static String messageToString(Message msg) {
+        StringBuilder sb = new StringBuilder();
+
+        String s = sSmToString.get(msg.what);
+        if (s == null) {
+            s = "<unknown>";
+        }
+        sb.append(s).append("/");
+
+        if (msg.what == MESSAGE_TYPE_NOTIFICATION || msg.what == MESSAGE_TYPE_COMMAND
+                || msg.what == MESSAGE_TYPE_RESPONSE) {
+            s = sSmToString.get(msg.arg1);
+            if (s == null) {
+                s = "<unknown>";
+            }
+            sb.append(s);
+        }
+
+        if (msg.what == MESSAGE_TYPE_RESPONSE || msg.what == MESSAGE_TYPE_RESPONSE_TIMEOUT) {
+            sb.append(" (Transaction ID=").append(msg.arg2).append(")");
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * Dump the internal state of the class.
+     */
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("NanStateManager:");
         pw.println("  mClients: [" + mClients + "]");
-        pw.println("  mPendingResponses: [" + mPendingResponses + "]");
+        pw.println("  mUsageEnabled: " + mUsageEnabled);
         pw.println("  mCapabilities: [" + mCapabilities + "]");
-        pw.println("  mNextTransactionId: " + mNextTransactionId);
+        pw.println("  mCurrentNanConfiguration: " + mCurrentNanConfiguration);
         for (int i = 0; i < mClients.size(); ++i) {
             mClients.valueAt(i).dump(fd, pw, args);
         }
+        mSm.dump(fd, pw, args);
+        mRtt.dump(fd, pw, args);
+        mDataPathMgr.dump(fd, pw, args);
     }
 }
diff --git a/service/java/com/android/server/wifi/scanner/SupplicantWifiScannerImpl.java b/service/java/com/android/server/wifi/scanner/SupplicantWifiScannerImpl.java
index 3f93c90..4d93e8f 100644
--- a/service/java/com/android/server/wifi/scanner/SupplicantWifiScannerImpl.java
+++ b/service/java/com/android/server/wifi/scanner/SupplicantWifiScannerImpl.java
@@ -68,7 +68,7 @@
     private final ChannelHelper mChannelHelper;
     private final Clock mClock;
 
-    private Object mSettingsLock = new Object();
+    private final Object mSettingsLock = new Object();
 
     // Next scan settings to apply when the previous scan completes
     private WifiNative.ScanSettings mPendingBackgroundScanSettings = null;
@@ -341,7 +341,7 @@
             ChannelCollection allFreqs = mChannelHelper.createChannelCollection();
             Set<Integer> hiddenNetworkIdSet = new HashSet<Integer>();
             final LastScanSettings newScanSettings =
-                    new LastScanSettings(mClock.elapsedRealtime());
+                    new LastScanSettings(mClock.getElapsedSinceBootMillis());
 
             // Update scan settings if there is a pending scan
             if (!mBackgroundScanPaused) {
@@ -397,7 +397,7 @@
                     mNextBackgroundScanPeriod++;
                     mBackgroundScanPeriodPending = false;
                     mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                            mClock.elapsedRealtime() + mBackgroundScanSettings.base_period_ms,
+                            mClock.getElapsedSinceBootMillis() + mBackgroundScanSettings.base_period_ms,
                             BACKGROUND_PERIOD_ALARM_TAG, mScanPeriodListener, mEventHandler);
                 }
             }
@@ -443,7 +443,7 @@
                     }
                     mLastScanSettings = newScanSettings;
                     mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                            mClock.elapsedRealtime() + SCAN_TIMEOUT_MS,
+                            mClock.getElapsedSinceBootMillis() + SCAN_TIMEOUT_MS,
                             TIMEOUT_ALARM_TAG, mScanTimeoutListener, mEventHandler);
                 } else {
                     Log.e(TAG, "Failed to start scan, freqs=" + freqs);
@@ -1042,7 +1042,7 @@
                 return true;
             }
 
-            mLastPnoChangeTimeStamp = mClock.elapsedRealtime();
+            mLastPnoChangeTimeStamp = mClock.getElapsedSinceBootMillis();
             if (mWifiNative.setPnoScan(enable)) {
                 Log.d(TAG, "Changed PNO state from " + mCurrentPnoState + " to " + enable);
                 mCurrentPnoState = enable;
@@ -1074,14 +1074,14 @@
             boolean isSuccess = true;
             mExpectedPnoState = enable;
             if (!mWaitForTimer) {
-                long timeDifference = mClock.elapsedRealtime() - mLastPnoChangeTimeStamp;
+                long timeDifference = mClock.getElapsedSinceBootMillis() - mLastPnoChangeTimeStamp;
                 if (timeDifference >= MINIMUM_PNO_GAP_MS) {
                     isSuccess = updatePnoState(enable);
                 } else {
                     long alarmTimeout = MINIMUM_PNO_GAP_MS - timeDifference;
                     Log.d(TAG, "Start PNO timer with delay " + alarmTimeout);
                     mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                            mClock.elapsedRealtime() + alarmTimeout, PNO_DEBOUNCER_ALARM_TAG,
+                            mClock.getElapsedSinceBootMillis() + alarmTimeout, PNO_DEBOUNCER_ALARM_TAG,
                             mAlarmListener, mEventHandler);
                     mWaitForTimer = true;
                 }
diff --git a/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java b/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
index 6ae2237..fdf3dfa 100644
--- a/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
+++ b/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
@@ -2284,7 +2284,7 @@
                         long unchangedDelay = settings.unchangedSampleSize * settings.periodInMs;
                         mAlarmManager.cancel(mTimeoutIntent);
                         mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                                mClock.elapsedRealtime() + unchangedDelay,
+                                mClock.getElapsedSinceBootMillis() + unchangedDelay,
                                 mTimeoutIntent);
                         break;
                     case WifiScanner.CMD_SCAN_RESULT:
@@ -2295,7 +2295,7 @@
                                     STATIONARY_SCAN_PERIOD_MS);
                             mWifiChangeDetected = false;
                             mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                                    mClock.elapsedRealtime() + MOVING_STATE_TIMEOUT_MS,
+                                    mClock.getElapsedSinceBootMillis() + MOVING_STATE_TIMEOUT_MS,
                                     mTimeoutIntent);
                             mScanResultsPending = false;
                         }
diff --git a/service/java/com/android/server/wifi/util/ScanDetailUtil.java b/service/java/com/android/server/wifi/util/ScanDetailUtil.java
deleted file mode 100644
index a83900e..0000000
--- a/service/java/com/android/server/wifi/util/ScanDetailUtil.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wifi.util;
-
-import android.net.wifi.ScanResult;
-
-import com.android.server.wifi.ScanDetail;
-import com.android.server.wifi.hotspot2.NetworkDetail;
-
-/**
- * Utility for converting a ScanResult to a ScanDetail.
- * Only fields that are supported in ScanResult are copied.
- */
-public class ScanDetailUtil {
-    private ScanDetailUtil() { /* not constructable */ }
-
-    /**
-     * This method should only be used when the informationElements field in the provided scan
-     * result is filled in with the IEs from the beacon.
-     */
-    public static ScanDetail toScanDetail(ScanResult scanResult) {
-        NetworkDetail networkDetail = new NetworkDetail(scanResult.BSSID,
-                scanResult.informationElements, scanResult.anqpLines, scanResult.frequency);
-        return new ScanDetail(scanResult, networkDetail, null);
-    }
-}
diff --git a/service/java/com/android/server/wifi/util/ScanResultUtil.java b/service/java/com/android/server/wifi/util/ScanResultUtil.java
new file mode 100644
index 0000000..3f1e98f
--- /dev/null
+++ b/service/java/com/android/server/wifi/util/ScanResultUtil.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.util;
+
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+
+import com.android.server.wifi.ScanDetail;
+import com.android.server.wifi.WifiConfigurationUtil;
+import com.android.server.wifi.hotspot2.NetworkDetail;
+
+/**
+ * Scan result utility for any {@link ScanResult} related operations.
+ * Currently contains:
+ *   > Helper method for converting a ScanResult to a ScanDetail.
+ *     Only fields that are supported in ScanResult are copied.
+ *   > Helper methods to identify the encryption of a ScanResult.
+ */
+public class ScanResultUtil {
+    private ScanResultUtil() { /* not constructable */ }
+
+    /**
+     * This method should only be used when the informationElements field in the provided scan
+     * result is filled in with the IEs from the beacon.
+     */
+    public static ScanDetail toScanDetail(ScanResult scanResult) {
+        NetworkDetail networkDetail = new NetworkDetail(scanResult.BSSID,
+                scanResult.informationElements, scanResult.anqpLines, scanResult.frequency);
+        return new ScanDetail(scanResult, networkDetail, null);
+    }
+
+    /**
+     * Helper method to check if the provided |scanResult| corresponds to a PSK network or not.
+     * This checks if the provided capabilities string contains PSK encryption type or not.
+     */
+    public static boolean isScanResultForPskNetwork(ScanResult scanResult) {
+        return scanResult.capabilities.contains("PSK");
+    }
+
+    /**
+     * Helper method to check if the provided |scanResult| corresponds to a EAP network or not.
+     * This checks if the provided capabilities string contains EAP encryption type or not.
+     */
+    public static boolean isScanResultForEapNetwork(ScanResult scanResult) {
+        return scanResult.capabilities.contains("EAP");
+    }
+
+    /**
+     * Helper method to check if the provided |scanResult| corresponds to a WEP network or not.
+     * This checks if the provided capabilities string contains WEP encryption type or not.
+     */
+    public static boolean isScanResultForWepNetwork(ScanResult scanResult) {
+        return scanResult.capabilities.contains("WEP");
+    }
+
+    /**
+     * Helper method to check if the provided |scanResult| corresponds to an open network or not.
+     * This checks if the provided capabilities string does not contain either of WEP, PSK or EAP
+     * encryption types or not.
+     */
+    public static boolean isScanResultForOpenNetwork(ScanResult scanResult) {
+        return !(isScanResultForWepNetwork(scanResult) || isScanResultForPskNetwork(scanResult)
+                || isScanResultForEapNetwork(scanResult));
+    }
+
+    /**
+     * Helper method to check if the provided |scanResult| and |config| have the same
+     * encryption type.
+     */
+    public static boolean doesScanResultEncryptionMatchWithNetwork(
+            ScanResult scanResult, WifiConfiguration config) {
+        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;
+    }
+
+}
diff --git a/service/java/com/android/server/wifi/util/XmlUtil.java b/service/java/com/android/server/wifi/util/XmlUtil.java
new file mode 100644
index 0000000..dffe602
--- /dev/null
+++ b/service/java/com/android/server/wifi/util/XmlUtil.java
@@ -0,0 +1,1027 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.util;
+
+import android.net.IpConfiguration;
+import android.net.IpConfiguration.IpAssignment;
+import android.net.IpConfiguration.ProxySettings;
+import android.net.LinkAddress;
+import android.net.NetworkUtils;
+import android.net.ProxyInfo;
+import android.net.RouteInfo;
+import android.net.StaticIpConfiguration;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
+import android.net.wifi.WifiEnterpriseConfig;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.util.BitSet;
+import java.util.HashMap;
+
+/**
+ * Utils for manipulating XML data. This is essentially a wrapper over XmlUtils provided by core.
+ * The utility provides methods to write/parse section headers and write/parse values.
+ * This utility is designed for formatting the XML into the following format:
+ * <Document Header>
+ *  <Section 1 Header>
+ *   <Value 1>
+ *   <Value 2>
+ *   ...
+ *   <Sub Section 1 Header>
+ *    <Value 1>
+ *    <Value 2>
+ *    ...
+ *   </Sub Section 1 Header>
+ *  </Section 1 Header>
+ * </Document Header>
+ */
+public class XmlUtil {
+    private static final String TAG = "WifiXmlUtil";
+
+    /**
+     * Ensure that the XML stream is at a start tag or the end of document.
+     *
+     * @throws XmlPullParserException if parsing errors occur.
+     */
+    private static void gotoStartTag(XmlPullParser in)
+            throws XmlPullParserException, IOException {
+        int type = in.getEventType();
+        while (type != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) {
+            type = in.next();
+        }
+    }
+
+    /**
+     * Ensure that the XML stream is at an end tag or the end of document.
+     *
+     * @throws XmlPullParserException if parsing errors occur.
+     */
+    private static void gotoEndTag(XmlPullParser in)
+            throws XmlPullParserException, IOException {
+        int type = in.getEventType();
+        while (type != XmlPullParser.END_TAG && type != XmlPullParser.END_DOCUMENT) {
+            type = in.next();
+        }
+    }
+
+    /**
+     * Start processing the XML stream at the document header.
+     *
+     * @param in         XmlPullParser instance pointing to the XML stream.
+     * @param headerName expected name for the start tag.
+     * @throws XmlPullParserException if parsing errors occur.
+     */
+    public static void gotoDocumentStart(XmlPullParser in, String headerName)
+            throws XmlPullParserException, IOException {
+        XmlUtils.beginDocument(in, headerName);
+    }
+
+    /**
+     * Move the XML stream to the next section header or indicate if there are no more sections.
+     * The provided outerDepth is used to find sub sections within that depth.
+     *
+     * Use this to move across sections if the ordering of sections are variable. The returned name
+     * can be used to decide what section is next.
+     *
+     * @param in         XmlPullParser instance pointing to the XML stream.
+     * @param headerName An array of one string, used to return the name of the next section.
+     * @param outerDepth Find section within this depth.
+     * @return {@code true} if a next section is found, {@code false} if there are no more sections.
+     * @throws XmlPullParserException if parsing errors occur.
+     */
+    public static boolean gotoNextSectionOrEnd(
+            XmlPullParser in, String[] headerName, int outerDepth)
+            throws XmlPullParserException, IOException {
+        if (XmlUtils.nextElementWithin(in, outerDepth)) {
+            headerName[0] = in.getName();
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Move the XML stream to the next section header or indicate if there are no more sections.
+     * If a section, exists ensure that the name matches the provided name.
+     * The provided outerDepth is used to find sub sections within that depth.
+     *
+     * Use this to move across repeated sections until the end.
+     *
+     * @param in           XmlPullParser instance pointing to the XML stream.
+     * @param expectedName expected name for the section header.
+     * @param outerDepth   Find section within this depth.
+     * @return {@code true} if a next section is found, {@code false} if there are no more sections.
+     * @throws XmlPullParserException if the section header name does not match |expectedName|,
+     *                                or if parsing errors occur.
+     */
+    public static boolean gotoNextSectionWithNameOrEnd(
+            XmlPullParser in, String expectedName, int outerDepth)
+            throws XmlPullParserException, IOException {
+        String[] headerName = new String[1];
+        if (gotoNextSectionOrEnd(in, headerName, outerDepth)) {
+            if (headerName[0].equals(expectedName)) {
+                return true;
+            }
+            throw new XmlPullParserException(
+                    "Next section name does not match expected name: " + expectedName);
+        }
+        return false;
+    }
+
+    /**
+     * Move the XML stream to the next section header and ensure that the name matches the provided
+     * name.
+     * The provided outerDepth is used to find sub sections within that depth.
+     *
+     * Use this to move across sections if the ordering of sections are fixed.
+     *
+     * @param in           XmlPullParser instance pointing to the XML stream.
+     * @param expectedName expected name for the section header.
+     * @param outerDepth   Find section within this depth.
+     * @throws XmlPullParserException if the section header name does not match |expectedName|,
+     *                                there are no more sections or if parsing errors occur.
+     */
+    public static void gotoNextSectionWithName(
+            XmlPullParser in, String expectedName, int outerDepth)
+            throws XmlPullParserException, IOException {
+        if (!gotoNextSectionWithNameOrEnd(in, expectedName, outerDepth)) {
+            throw new XmlPullParserException("Section not found. Expected: " + expectedName);
+        }
+    }
+
+    /**
+     * Checks if the stream is at the end of a section of values. This moves the stream to next tag
+     * and checks if it finds an end tag at the specified depth.
+     *
+     * @param in           XmlPullParser instance pointing to the XML stream.
+     * @param sectionDepth depth of the start tag of this section. Used to match the end tag.
+     * @return {@code true} if a end tag at the provided depth is found, {@code false} otherwise
+     * @throws XmlPullParserException if parsing errors occur.
+     */
+    public static boolean isNextSectionEnd(XmlPullParser in, int sectionDepth)
+            throws XmlPullParserException, IOException {
+        return !XmlUtils.nextElementWithin(in, sectionDepth);
+    }
+
+    /**
+     * Read the current value in the XML stream using core XmlUtils and stores the retrieved
+     * value name in the string provided. This method reads the value contained in current start
+     * tag.
+     * Note: Because there could be genuine null values being read from the XML, this method raises
+     * an exception to indicate errors.
+     *
+     * @param in        XmlPullParser instance pointing to the XML stream.
+     * @param valueName An array of one string, used to return the name attribute
+     *                  of the value's tag.
+     * @return value retrieved from the XML stream.
+     * @throws XmlPullParserException if parsing errors occur.
+     */
+    public static Object readCurrentValue(XmlPullParser in, String[] valueName)
+            throws XmlPullParserException, IOException {
+        Object value = XmlUtils.readValueXml(in, valueName);
+        // XmlUtils.readValue does not always move the stream to the end of the tag. So, move
+        // it to the end tag before returning from here.
+        gotoEndTag(in);
+        return value;
+    }
+
+    /**
+     * Read the next value in the XML stream using core XmlUtils and ensure that it matches the
+     * provided name. This method moves the stream to the next start tag and reads the value
+     * contained in it.
+     * Note: Because there could be genuine null values being read from the XML, this method raises
+     * an exception to indicate errors.
+     *
+     * @param in XmlPullParser instance pointing to the XML stream.
+     * @return value retrieved from the XML stream.
+     * @throws XmlPullParserException if the value read does not match |expectedName|,
+     *                                or if parsing errors occur.
+     */
+    public static Object readNextValueWithName(XmlPullParser in, String expectedName)
+            throws XmlPullParserException, IOException {
+        String[] valueName = new String[1];
+        XmlUtils.nextElement(in);
+        Object value = readCurrentValue(in, valueName);
+        if (valueName[0].equals(expectedName)) {
+            return value;
+        }
+        throw new XmlPullParserException(
+                "Value not found. Expected: " + expectedName + ", but got: " + valueName[0]);
+    }
+
+    /**
+     * Write the XML document start with the provided document header name.
+     *
+     * @param out        XmlSerializer instance pointing to the XML stream.
+     * @param headerName name for the start tag.
+     */
+    public static void writeDocumentStart(XmlSerializer out, String headerName)
+            throws IOException {
+        out.startDocument(null, true);
+        out.startTag(null, headerName);
+    }
+
+    /**
+     * Write the XML document end with the provided document header name.
+     *
+     * @param out        XmlSerializer instance pointing to the XML stream.
+     * @param headerName name for the end tag.
+     */
+    public static void writeDocumentEnd(XmlSerializer out, String headerName)
+            throws IOException {
+        out.endTag(null, headerName);
+        out.endDocument();
+    }
+
+    /**
+     * Write a section start header tag with the provided section name.
+     *
+     * @param out        XmlSerializer instance pointing to the XML stream.
+     * @param headerName name for the start tag.
+     */
+    public static void writeNextSectionStart(XmlSerializer out, String headerName)
+            throws IOException {
+        out.startTag(null, headerName);
+    }
+
+    /**
+     * Write a section end header tag with the provided section name.
+     *
+     * @param out        XmlSerializer instance pointing to the XML stream.
+     * @param headerName name for the end tag.
+     */
+    public static void writeNextSectionEnd(XmlSerializer out, String headerName)
+            throws IOException {
+        out.endTag(null, headerName);
+    }
+
+    /**
+     * Write the value with the provided name in the XML stream using core XmlUtils.
+     *
+     * @param out   XmlSerializer instance pointing to the XML stream.
+     * @param name  name of the value.
+     * @param value value to be written.
+     */
+    public static void writeNextValue(XmlSerializer out, String name, Object value)
+            throws XmlPullParserException, IOException {
+        XmlUtils.writeValueXml(value, name, out);
+    }
+
+    /**
+     * Utility class to serialize and deseriaize {@link WifiConfiguration} object to XML &
+     * vice versa.
+     * This is used by both {@link com.android.server.wifi.WifiConfigStore} &
+     * {@link com.android.server.wifi.WifiBackupRestore} modules.
+     * The |writeConfigurationToXml| has 2 versions, one for backup and one for config store.
+     * There is only 1 version of |parseXmlToConfiguration| for both backup & config store.
+     * The parse method is written so that any element added/deleted in future revisions can
+     * be easily handled.
+     */
+    public static class WifiConfigurationXmlUtil {
+        /**
+         * List of XML tags corresponding to WifiConfiguration object elements.
+         */
+        public static final String XML_TAG_SSID = "SSID";
+        public static final String XML_TAG_BSSID = "BSSID";
+        public static final String XML_TAG_CONFIG_KEY = "ConfigKey";
+        public static final String XML_TAG_PRE_SHARED_KEY = "PreSharedKey";
+        public static final String XML_TAG_WEP_KEYS = "WEPKeys";
+        public static final String XML_TAG_WEP_TX_KEY_INDEX = "WEPTxKeyIndex";
+        public static final String XML_TAG_HIDDEN_SSID = "HiddenSSID";
+        public static final String XML_TAG_ALLOWED_KEY_MGMT = "AllowedKeyMgmt";
+        public static final String XML_TAG_ALLOWED_PROTOCOLS = "AllowedProtocols";
+        public static final String XML_TAG_ALLOWED_AUTH_ALGOS = "AllowedAuthAlgos";
+        public static final String XML_TAG_SHARED = "Shared";
+        public static final String XML_TAG_FQDN = "FQDN";
+        public static final String XML_TAG_PROVIDER_FRIENDLY_NAME = "ProviderFriendlyName";
+        public static final String XML_TAG_LINKED_NETWORKS_LIST = "LinkedNetworksList";
+        public static final String XML_TAG_DEFAULT_GW_MAC_ADDRESS = "DefaultGwMacAddress";
+        public static final String XML_TAG_VALIDATED_INTERNET_ACCESS = "ValidatedInternetAccess";
+        public static final String XML_TAG_NO_INTERNET_ACCESS_EXPECTED = "NoInternetAccessExpected";
+        public static final String XML_TAG_USER_APPROVED = "UserApproved";
+        public static final String XML_TAG_METERED_HINT = "MeteredHint";
+        public static final String XML_TAG_USE_EXTERNAL_SCORES = "UseExternalScores";
+        public static final String XML_TAG_NUM_ASSOCIATION = "NumAssociation";
+        public static final String XML_TAG_CREATOR_UID = "CreatorUid";
+        public static final String XML_TAG_CREATOR_NAME = "CreatorName";
+        public static final String XML_TAG_CREATION_TIME = "CreationTime";
+        public static final String XML_TAG_LAST_UPDATE_UID = "LastUpdateUid";
+        public static final String XML_TAG_LAST_UPDATE_NAME = "LastUpdateName";
+        public static final String XML_TAG_LAST_CONNECT_UID = "LastConnectUid";
+
+        /**
+         * Write WepKeys to the XML stream.
+         * WepKeys array is intialized in WifiConfiguration constructor, but all of the elements
+         * are set to null. User may chose to set any one of the key elements in WifiConfiguration.
+         * XmlUtils serialization doesn't handle this array of nulls well .
+         * So, write empty strings if some of the keys are not initialized and null if all of
+         * the elements are empty.
+         */
+        private static void writeWepKeysToXml(XmlSerializer out, String[] wepKeys)
+                throws XmlPullParserException, IOException {
+            String[] wepKeysToWrite = new String[wepKeys.length];
+            boolean hasWepKey = false;
+            for (int i = 0; i < wepKeys.length; i++) {
+                if (wepKeys[i] == null) {
+                    wepKeysToWrite[i] = new String();
+                } else {
+                    wepKeysToWrite[i] = wepKeys[i];
+                    hasWepKey = true;
+                }
+            }
+            if (hasWepKey) {
+                XmlUtil.writeNextValue(out, XML_TAG_WEP_KEYS, wepKeysToWrite);
+            } else {
+                XmlUtil.writeNextValue(out, XML_TAG_WEP_KEYS, null);
+            }
+        }
+
+        /**
+         * Write the Configuration data elements that are common for backup & config store to the
+         * XML stream.
+         *
+         * @param out           XmlSerializer instance pointing to the XML stream.
+         * @param configuration WifiConfiguration object to be serialized.
+         */
+        public static void writeCommonElementsToXml(
+                XmlSerializer out, WifiConfiguration configuration)
+                throws XmlPullParserException, IOException {
+            XmlUtil.writeNextValue(out, XML_TAG_CONFIG_KEY, configuration.configKey());
+            XmlUtil.writeNextValue(out, XML_TAG_SSID, configuration.SSID);
+            XmlUtil.writeNextValue(out, XML_TAG_BSSID, configuration.BSSID);
+            XmlUtil.writeNextValue(out, XML_TAG_PRE_SHARED_KEY, configuration.preSharedKey);
+            writeWepKeysToXml(out, configuration.wepKeys);
+            XmlUtil.writeNextValue(out, XML_TAG_WEP_TX_KEY_INDEX, configuration.wepTxKeyIndex);
+            XmlUtil.writeNextValue(out, XML_TAG_HIDDEN_SSID, configuration.hiddenSSID);
+            XmlUtil.writeNextValue(
+                    out, XML_TAG_ALLOWED_KEY_MGMT,
+                    configuration.allowedKeyManagement.toByteArray());
+            XmlUtil.writeNextValue(
+                    out, XML_TAG_ALLOWED_PROTOCOLS,
+                    configuration.allowedProtocols.toByteArray());
+            XmlUtil.writeNextValue(
+                    out, XML_TAG_ALLOWED_AUTH_ALGOS,
+                    configuration.allowedAuthAlgorithms.toByteArray());
+            XmlUtil.writeNextValue(out, XML_TAG_SHARED, configuration.shared);
+        }
+
+        /**
+         * Write the Configuration data elements for backup from the provided Configuration to the
+         * XML stream.
+         * Note: This is a subset of the elements serialized for config store.
+         *
+         * @param out           XmlSerializer instance pointing to the XML stream.
+         * @param configuration WifiConfiguration object to be serialized.
+         */
+        public static void writeToXmlForBackup(XmlSerializer out, WifiConfiguration configuration)
+                throws XmlPullParserException, IOException {
+            writeCommonElementsToXml(out, configuration);
+        }
+
+        /**
+         * Write the Configuration data elements for config store from the provided Configuration
+         * to the XML stream.
+         *
+         * @param out           XmlSerializer instance pointing to the XML stream.
+         * @param configuration WifiConfiguration object to be serialized.
+         */
+        public static void writeToXmlForConfigStore(
+                XmlSerializer out, WifiConfiguration configuration)
+                throws XmlPullParserException, IOException {
+            writeCommonElementsToXml(out, configuration);
+            XmlUtil.writeNextValue(out, XML_TAG_FQDN, configuration.FQDN);
+            XmlUtil.writeNextValue(
+                    out, XML_TAG_PROVIDER_FRIENDLY_NAME, configuration.providerFriendlyName);
+            XmlUtil.writeNextValue(
+                    out, XML_TAG_LINKED_NETWORKS_LIST, configuration.linkedConfigurations);
+            XmlUtil.writeNextValue(
+                    out, XML_TAG_DEFAULT_GW_MAC_ADDRESS, configuration.defaultGwMacAddress);
+            XmlUtil.writeNextValue(
+                    out, XML_TAG_VALIDATED_INTERNET_ACCESS, configuration.validatedInternetAccess);
+            XmlUtil.writeNextValue(
+                    out, XML_TAG_NO_INTERNET_ACCESS_EXPECTED,
+                    configuration.noInternetAccessExpected);
+            XmlUtil.writeNextValue(out, XML_TAG_USER_APPROVED, configuration.userApproved);
+            XmlUtil.writeNextValue(out, XML_TAG_METERED_HINT, configuration.meteredHint);
+            XmlUtil.writeNextValue(
+                    out, XML_TAG_USE_EXTERNAL_SCORES, configuration.useExternalScores);
+            XmlUtil.writeNextValue(out, XML_TAG_NUM_ASSOCIATION, configuration.numAssociation);
+            XmlUtil.writeNextValue(out, XML_TAG_CREATOR_UID, configuration.creatorUid);
+            XmlUtil.writeNextValue(out, XML_TAG_CREATOR_NAME, configuration.creatorName);
+            XmlUtil.writeNextValue(out, XML_TAG_CREATION_TIME, configuration.creationTime);
+            XmlUtil.writeNextValue(out, XML_TAG_LAST_UPDATE_UID, configuration.lastUpdateUid);
+            XmlUtil.writeNextValue(out, XML_TAG_LAST_UPDATE_NAME, configuration.lastUpdateName);
+            XmlUtil.writeNextValue(out, XML_TAG_LAST_CONNECT_UID, configuration.lastConnectUid);
+        }
+
+        /**
+         * Populate wepKeys array elements only if they were non-empty in the backup data.
+         *
+         * @throws XmlPullParserException if parsing errors occur.
+         */
+        private static void populateWepKeysFromXmlValue(Object value, String[] wepKeys)
+                throws XmlPullParserException, IOException {
+            String[] wepKeysInData = (String[]) value;
+            if (wepKeysInData == null) {
+                return;
+            }
+            if (wepKeysInData.length != wepKeys.length) {
+                throw new XmlPullParserException(
+                        "Invalid Wep Keys length: " + wepKeysInData.length);
+            }
+            for (int i = 0; i < wepKeys.length; i++) {
+                if (wepKeysInData[i].isEmpty()) {
+                    wepKeys[i] = null;
+                } else {
+                    wepKeys[i] = wepKeysInData[i];
+                }
+            }
+        }
+
+        /**
+         * Parses the configuration data elements from the provided XML stream to a
+         * WifiConfiguration object.
+         * Note: This is used for parsing both backup data and config store data. Looping through
+         * the tags make it easy to add or remove elements in the future versions if needed.
+         *
+         * @param in            XmlPullParser instance pointing to the XML stream.
+         * @param outerTagDepth depth of the outer tag in the XML document.
+         * @return Pair<Config key, WifiConfiguration object> if parsing is successful,
+         * null otherwise.
+         */
+        public static Pair<String, WifiConfiguration> parseFromXml(
+                XmlPullParser in, int outerTagDepth)
+                throws XmlPullParserException, IOException {
+            WifiConfiguration configuration = new WifiConfiguration();
+            String configKeyInData = null;
+
+            // Loop through and parse out all the elements from the stream within this section.
+            while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) {
+                String[] valueName = new String[1];
+                Object value = XmlUtil.readCurrentValue(in, valueName);
+                if (valueName[0] == null) {
+                    throw new XmlPullParserException("Missing value name");
+                }
+                switch (valueName[0]) {
+                    case XML_TAG_CONFIG_KEY:
+                        configKeyInData = (String) value;
+                        break;
+                    case XML_TAG_SSID:
+                        configuration.SSID = (String) value;
+                        break;
+                    case XML_TAG_BSSID:
+                        configuration.BSSID = (String) value;
+                        break;
+                    case XML_TAG_PRE_SHARED_KEY:
+                        configuration.preSharedKey = (String) value;
+                        break;
+                    case XML_TAG_WEP_KEYS:
+                        populateWepKeysFromXmlValue(value, configuration.wepKeys);
+                        break;
+                    case XML_TAG_WEP_TX_KEY_INDEX:
+                        configuration.wepTxKeyIndex = (int) value;
+                        break;
+                    case XML_TAG_HIDDEN_SSID:
+                        configuration.hiddenSSID = (boolean) value;
+                        break;
+                    case XML_TAG_ALLOWED_KEY_MGMT:
+                        byte[] allowedKeyMgmt = (byte[]) value;
+                        configuration.allowedKeyManagement = BitSet.valueOf(allowedKeyMgmt);
+                        break;
+                    case XML_TAG_ALLOWED_PROTOCOLS:
+                        byte[] allowedProtocols = (byte[]) value;
+                        configuration.allowedProtocols = BitSet.valueOf(allowedProtocols);
+                        break;
+                    case XML_TAG_ALLOWED_AUTH_ALGOS:
+                        byte[] allowedAuthAlgorithms = (byte[]) value;
+                        configuration.allowedAuthAlgorithms = BitSet.valueOf(allowedAuthAlgorithms);
+                        break;
+                    case XML_TAG_SHARED:
+                        configuration.shared = (boolean) value;
+                        break;
+                    case XML_TAG_FQDN:
+                        configuration.FQDN = (String) value;
+                        break;
+                    case XML_TAG_PROVIDER_FRIENDLY_NAME:
+                        configuration.providerFriendlyName = (String) value;
+                        break;
+                    case XML_TAG_LINKED_NETWORKS_LIST:
+                        configuration.linkedConfigurations = (HashMap<String, Integer>) value;
+                        break;
+                    case XML_TAG_DEFAULT_GW_MAC_ADDRESS:
+                        configuration.defaultGwMacAddress = (String) value;
+                        break;
+                    case XML_TAG_VALIDATED_INTERNET_ACCESS:
+                        configuration.validatedInternetAccess = (boolean) value;
+                        break;
+                    case XML_TAG_NO_INTERNET_ACCESS_EXPECTED:
+                        configuration.noInternetAccessExpected = (boolean) value;
+                        break;
+                    case XML_TAG_USER_APPROVED:
+                        configuration.userApproved = (int) value;
+                        break;
+                    case XML_TAG_METERED_HINT:
+                        configuration.meteredHint = (boolean) value;
+                        break;
+                    case XML_TAG_USE_EXTERNAL_SCORES:
+                        configuration.useExternalScores = (boolean) value;
+                        break;
+                    case XML_TAG_NUM_ASSOCIATION:
+                        configuration.numAssociation = (int) value;
+                        break;
+                    case XML_TAG_CREATOR_UID:
+                        configuration.creatorUid = (int) value;
+                        break;
+                    case XML_TAG_CREATOR_NAME:
+                        configuration.creatorName = (String) value;
+                        break;
+                    case XML_TAG_CREATION_TIME:
+                        configuration.creationTime = (String) value;
+                        break;
+                    case XML_TAG_LAST_UPDATE_UID:
+                        configuration.lastUpdateUid = (int) value;
+                        break;
+                    case XML_TAG_LAST_UPDATE_NAME:
+                        configuration.lastUpdateName = (String) value;
+                        break;
+                    case XML_TAG_LAST_CONNECT_UID:
+                        configuration.lastConnectUid = (int) value;
+                        break;
+                    default:
+                        throw new XmlPullParserException(
+                                "Unknown value name found: " + valueName[0]);
+                }
+            }
+            return Pair.create(configKeyInData, configuration);
+        }
+    }
+
+    /**
+     * Utility class to serialize and deseriaize {@link IpConfiguration} object to XML & vice versa.
+     * This is used by both {@link com.android.server.wifi.WifiConfigStore} &
+     * {@link com.android.server.wifi.WifiBackupRestore} modules.
+     */
+    public static class IpConfigurationXmlUtil {
+
+        /**
+         * List of XML tags corresponding to IpConfiguration object elements.
+         */
+        public static final String XML_TAG_IP_ASSIGNMENT = "IpAssignment";
+        public static final String XML_TAG_LINK_ADDRESS = "LinkAddress";
+        public static final String XML_TAG_LINK_PREFIX_LENGTH = "LinkPrefixLength";
+        public static final String XML_TAG_GATEWAY_ADDRESS = "GatewayAddress";
+        public static final String XML_TAG_DNS_SERVER_ADDRESSES = "DNSServers";
+        public static final String XML_TAG_PROXY_SETTINGS = "ProxySettings";
+        public static final String XML_TAG_PROXY_HOST = "ProxyHost";
+        public static final String XML_TAG_PROXY_PORT = "ProxyPort";
+        public static final String XML_TAG_PROXY_PAC_FILE = "ProxyPac";
+        public static final String XML_TAG_PROXY_EXCLUSION_LIST = "ProxyExclusionList";
+
+        /**
+         * Write the static IP configuration data elements to XML stream.
+         */
+        private static void writeStaticIpConfigurationToXml(
+                XmlSerializer out, StaticIpConfiguration staticIpConfiguration)
+                throws XmlPullParserException, IOException {
+            if (staticIpConfiguration.ipAddress != null) {
+                XmlUtil.writeNextValue(
+                        out, XML_TAG_LINK_ADDRESS,
+                        staticIpConfiguration.ipAddress.getAddress().getHostAddress());
+                XmlUtil.writeNextValue(
+                        out, XML_TAG_LINK_PREFIX_LENGTH,
+                        staticIpConfiguration.ipAddress.getPrefixLength());
+            } else {
+                XmlUtil.writeNextValue(
+                        out, XML_TAG_LINK_ADDRESS, null);
+                XmlUtil.writeNextValue(
+                        out, XML_TAG_LINK_PREFIX_LENGTH, null);
+            }
+            if (staticIpConfiguration.gateway != null) {
+                XmlUtil.writeNextValue(
+                        out, XML_TAG_GATEWAY_ADDRESS,
+                        staticIpConfiguration.gateway.getHostAddress());
+            } else {
+                XmlUtil.writeNextValue(
+                        out, XML_TAG_GATEWAY_ADDRESS, null);
+
+            }
+            if (staticIpConfiguration.dnsServers != null) {
+                // Create a string array of DNS server addresses
+                String[] dnsServers = new String[staticIpConfiguration.dnsServers.size()];
+                int dnsServerIdx = 0;
+                for (InetAddress inetAddr : staticIpConfiguration.dnsServers) {
+                    dnsServers[dnsServerIdx++] = inetAddr.getHostAddress();
+                }
+                XmlUtil.writeNextValue(
+                        out, XML_TAG_DNS_SERVER_ADDRESSES, dnsServers);
+            } else {
+                XmlUtil.writeNextValue(
+                        out, XML_TAG_DNS_SERVER_ADDRESSES, null);
+            }
+        }
+
+        /**
+         * Write the IP configuration data elements from the provided Configuration to the XML
+         * stream.
+         *
+         * @param out             XmlSerializer instance pointing to the XML stream.
+         * @param ipConfiguration IpConfiguration object to be serialized.
+         */
+        public static void writeToXml(XmlSerializer out, IpConfiguration ipConfiguration)
+                throws XmlPullParserException, IOException {
+            // Write IP assignment settings
+            XmlUtil.writeNextValue(out, XML_TAG_IP_ASSIGNMENT,
+                    ipConfiguration.ipAssignment.toString());
+            switch (ipConfiguration.ipAssignment) {
+                case STATIC:
+                    writeStaticIpConfigurationToXml(
+                            out, ipConfiguration.getStaticIpConfiguration());
+                    break;
+                default:
+                    break;
+            }
+
+            // Write proxy settings
+            XmlUtil.writeNextValue(
+                    out, XML_TAG_PROXY_SETTINGS,
+                    ipConfiguration.proxySettings.toString());
+            switch (ipConfiguration.proxySettings) {
+                case STATIC:
+                    XmlUtil.writeNextValue(
+                            out, XML_TAG_PROXY_HOST,
+                            ipConfiguration.httpProxy.getHost());
+                    XmlUtil.writeNextValue(
+                            out, XML_TAG_PROXY_PORT,
+                            ipConfiguration.httpProxy.getPort());
+                    XmlUtil.writeNextValue(
+                            out, XML_TAG_PROXY_EXCLUSION_LIST,
+                            ipConfiguration.httpProxy.getExclusionListAsString());
+                    break;
+                case PAC:
+                    XmlUtil.writeNextValue(
+                            out, XML_TAG_PROXY_PAC_FILE,
+                            ipConfiguration.httpProxy.getPacFileUrl().toString());
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        /**
+         * Parse out the static IP configuration from the XML stream.
+         */
+        private static StaticIpConfiguration parseStaticIpConfigurationFromXml(XmlPullParser in)
+                throws XmlPullParserException, IOException {
+            StaticIpConfiguration staticIpConfiguration = new StaticIpConfiguration();
+
+            String linkAddressString =
+                    (String) XmlUtil.readNextValueWithName(in, XML_TAG_LINK_ADDRESS);
+            Integer linkPrefixLength =
+                    (Integer) XmlUtil.readNextValueWithName(in, XML_TAG_LINK_PREFIX_LENGTH);
+            if (linkAddressString != null && linkPrefixLength != null) {
+                LinkAddress linkAddress = new LinkAddress(
+                        NetworkUtils.numericToInetAddress(linkAddressString),
+                        linkPrefixLength);
+                if (linkAddress.getAddress() instanceof Inet4Address) {
+                    staticIpConfiguration.ipAddress = linkAddress;
+                } else {
+                    Log.w(TAG, "Non-IPv4 address: " + linkAddress);
+                }
+            }
+            String gatewayAddressString =
+                    (String) XmlUtil.readNextValueWithName(in, XML_TAG_GATEWAY_ADDRESS);
+            if (gatewayAddressString != null) {
+                LinkAddress dest = null;
+                InetAddress gateway =
+                        NetworkUtils.numericToInetAddress(gatewayAddressString);
+                RouteInfo route = new RouteInfo(dest, gateway);
+                if (route.isIPv4Default()) {
+                    staticIpConfiguration.gateway = gateway;
+                } else {
+                    Log.w(TAG, "Non-IPv4 default route: " + route);
+                }
+            }
+            String[] dnsServerAddressesString =
+                    (String[]) XmlUtil.readNextValueWithName(in, XML_TAG_DNS_SERVER_ADDRESSES);
+            if (dnsServerAddressesString != null) {
+                for (String dnsServerAddressString : dnsServerAddressesString) {
+                    InetAddress dnsServerAddress =
+                            NetworkUtils.numericToInetAddress(dnsServerAddressString);
+                    staticIpConfiguration.dnsServers.add(dnsServerAddress);
+                }
+            }
+            return staticIpConfiguration;
+        }
+
+        /**
+         * Parses the IP configuration data elements from the provided XML stream to an
+         * IpConfiguration object.
+         *
+         * @param in            XmlPullParser instance pointing to the XML stream.
+         * @param outerTagDepth depth of the outer tag in the XML document.
+         * @return IpConfiguration object if parsing is successful, null otherwise.
+         */
+        public static IpConfiguration parseFromXml(XmlPullParser in, int outerTagDepth)
+                throws XmlPullParserException, IOException {
+            IpConfiguration ipConfiguration = new IpConfiguration();
+
+            // Parse out the IP assignment info first.
+            String ipAssignmentString =
+                    (String) XmlUtil.readNextValueWithName(in, XML_TAG_IP_ASSIGNMENT);
+            IpAssignment ipAssignment = IpAssignment.valueOf(ipAssignmentString);
+            ipConfiguration.setIpAssignment(ipAssignment);
+            switch (ipAssignment) {
+                case STATIC:
+                    ipConfiguration.setStaticIpConfiguration(parseStaticIpConfigurationFromXml(in));
+                    break;
+                case DHCP:
+                case UNASSIGNED:
+                    break;
+                default:
+                    throw new XmlPullParserException("Unknown ip assignment type: " + ipAssignment);
+            }
+
+            // Parse out the proxy settings next.
+            String proxySettingsString =
+                    (String) XmlUtil.readNextValueWithName(in, XML_TAG_PROXY_SETTINGS);
+            ProxySettings proxySettings = ProxySettings.valueOf(proxySettingsString);
+            ipConfiguration.setProxySettings(proxySettings);
+            switch (proxySettings) {
+                case STATIC:
+                    String proxyHost =
+                            (String) XmlUtil.readNextValueWithName(in, XML_TAG_PROXY_HOST);
+                    int proxyPort =
+                            (int) XmlUtil.readNextValueWithName(in, XML_TAG_PROXY_PORT);
+                    String proxyExclusionList =
+                            (String) XmlUtil.readNextValueWithName(
+                                    in, XML_TAG_PROXY_EXCLUSION_LIST);
+                    ipConfiguration.setHttpProxy(
+                            new ProxyInfo(proxyHost, proxyPort, proxyExclusionList));
+                    break;
+                case PAC:
+                    String proxyPacFile =
+                            (String) XmlUtil.readNextValueWithName(in, XML_TAG_PROXY_PAC_FILE);
+                    ipConfiguration.setHttpProxy(new ProxyInfo(proxyPacFile));
+                    break;
+                case NONE:
+                case UNASSIGNED:
+                    break;
+                default:
+                    throw new XmlPullParserException(
+                            "Unknown proxy settings type: " + proxySettings);
+            }
+            return ipConfiguration;
+        }
+    }
+
+    /**
+     * Utility class to serialize and deseriaize {@link NetworkSelectionStatus} object to XML &
+     * vice versa. This is used by {@link com.android.server.wifi.WifiConfigStore} module.
+     */
+    public static class NetworkSelectionStatusXmlUtil {
+
+        /**
+         * List of XML tags corresponding to NetworkSelectionStatus object elements.
+         */
+        public static final String XML_TAG_SELECTION_STATUS = "SelectionStatus";
+        public static final String XML_TAG_DISABLE_REASON = "DisableReason";
+        public static final String XML_TAG_CONNECT_CHOICE = "ConnectChoice";
+        public static final String XML_TAG_CONNECT_CHOICE_TIMESTAMP = "ConnectChoiceTimeStamp";
+        public static final String XML_TAG_HAS_EVER_CONNECTED = "HasEverConnected";
+
+        /**
+         * Write the NetworkSelectionStatus data elements from the provided status to the XML
+         * stream.
+         *
+         * @param out    XmlSerializer instance pointing to the XML stream.
+         * @param status NetworkSelectionStatus object to be serialized.
+         */
+        public static void writeToXml(XmlSerializer out, NetworkSelectionStatus status)
+                throws XmlPullParserException, IOException {
+            // Don't persist blacklists across reboots. So, if the status is temporarily disabled,
+            // store the status as enabled. This will ensure that when the device reboots, it is
+            // still considered for network selection.
+            int selectionStatus = status.getNetworkSelectionStatus();
+            if (selectionStatus == NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED) {
+                selectionStatus = NetworkSelectionStatus.NETWORK_SELECTION_ENABLED;
+            }
+            XmlUtil.writeNextValue(out, XML_TAG_SELECTION_STATUS, selectionStatus);
+            XmlUtil.writeNextValue(
+                    out, XML_TAG_DISABLE_REASON, status.getNetworkSelectionDisableReason());
+            XmlUtil.writeNextValue(out, XML_TAG_CONNECT_CHOICE, status.getConnectChoice());
+            XmlUtil.writeNextValue(
+                    out, XML_TAG_CONNECT_CHOICE_TIMESTAMP, status.getConnectChoiceTimestamp());
+            XmlUtil.writeNextValue(out, XML_TAG_HAS_EVER_CONNECTED, status.getHasEverConnected());
+        }
+
+        /**
+         * Parses the NetworkSelectionStatus data elements from the provided XML stream to a
+         * NetworkSelectionStatus object.
+         *
+         * @param in            XmlPullParser instance pointing to the XML stream.
+         * @param outerTagDepth depth of the outer tag in the XML document.
+         * @return NetworkSelectionStatus object if parsing is successful, null otherwise.
+         */
+        public static NetworkSelectionStatus parseFromXml(XmlPullParser in, int outerTagDepth)
+                throws XmlPullParserException, IOException {
+            NetworkSelectionStatus status = new NetworkSelectionStatus();
+
+            // Loop through and parse out all the elements from the stream within this section.
+            while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) {
+                String[] valueName = new String[1];
+                Object value = XmlUtil.readCurrentValue(in, valueName);
+                if (valueName[0] == null) {
+                    throw new XmlPullParserException("Missing value name");
+                }
+                switch (valueName[0]) {
+                    case XML_TAG_SELECTION_STATUS:
+                        status.setNetworkSelectionStatus((int) value);
+                        break;
+                    case XML_TAG_DISABLE_REASON:
+                        status.setNetworkSelectionDisableReason((int) value);
+                        break;
+                    case XML_TAG_CONNECT_CHOICE:
+                        status.setConnectChoice((String) value);
+                        break;
+                    case XML_TAG_CONNECT_CHOICE_TIMESTAMP:
+                        status.setConnectChoiceTimestamp((long) value);
+                        break;
+                    case XML_TAG_HAS_EVER_CONNECTED:
+                        status.setHasEverConnected((boolean) value);
+                        break;
+                    default:
+                        throw new XmlPullParserException(
+                                "Unknown value name found: " + valueName[0]);
+                }
+            }
+            return status;
+        }
+    }
+
+    /**
+     * Utility class to serialize and deseriaize {@link WifiEnterpriseConfig} object to XML &
+     * vice versa. This is used by {@link com.android.server.wifi.WifiConfigStore} module.
+     */
+    public static class WifiEnterpriseConfigXmlUtil {
+
+        /**
+         * List of XML tags corresponding to WifiEnterpriseConfig object elements.
+         */
+        public static final String XML_TAG_IDENTITY = "Identity";
+        public static final String XML_TAG_ANON_IDENTITY = "AnonIdentity";
+        public static final String XML_TAG_PASSWORD = "Password";
+        public static final String XML_TAG_CLIENT_CERT = "ClientCert";
+        public static final String XML_TAG_CA_CERT = "CaCert";
+        public static final String XML_TAG_SUBJECT_MATCH = "SubjectMatch";
+        public static final String XML_TAG_ENGINE = "Engine";
+        public static final String XML_TAG_ENGINE_ID = "EngineId";
+        public static final String XML_TAG_PRIVATE_KEY_ID = "PrivateKeyId";
+        public static final String XML_TAG_ALT_SUBJECT_MATCH = "AltSubjectMatch";
+        public static final String XML_TAG_DOM_SUFFIX_MATCH = "DomSuffixMatch";
+        public static final String XML_TAG_CA_PATH = "CaPath";
+        public static final String XML_TAG_EAP_METHOD = "EapMethod";
+        public static final String XML_TAG_PHASE2_METHOD = "Phase2Method";
+
+        /**
+         * Write the WifiEnterpriseConfig data elements from the provided config to the XML
+         * stream.
+         *
+         * @param out              XmlSerializer instance pointing to the XML stream.
+         * @param enterpriseConfig WifiEnterpriseConfig object to be serialized.
+         */
+        public static void writeToXml(XmlSerializer out, WifiEnterpriseConfig enterpriseConfig)
+                throws XmlPullParserException, IOException {
+            XmlUtil.writeNextValue(out, XML_TAG_IDENTITY,
+                    enterpriseConfig.getFieldValue(WifiEnterpriseConfig.IDENTITY_KEY, ""));
+            XmlUtil.writeNextValue(out, XML_TAG_ANON_IDENTITY,
+                    enterpriseConfig.getFieldValue(WifiEnterpriseConfig.ANON_IDENTITY_KEY, ""));
+            XmlUtil.writeNextValue(out, XML_TAG_PASSWORD,
+                    enterpriseConfig.getFieldValue(WifiEnterpriseConfig.PASSWORD_KEY, ""));
+            XmlUtil.writeNextValue(out, XML_TAG_CLIENT_CERT,
+                    enterpriseConfig.getFieldValue(WifiEnterpriseConfig.CLIENT_CERT_KEY, ""));
+            XmlUtil.writeNextValue(out, XML_TAG_CA_CERT,
+                    enterpriseConfig.getFieldValue(WifiEnterpriseConfig.CA_CERT_KEY, ""));
+            XmlUtil.writeNextValue(out, XML_TAG_SUBJECT_MATCH,
+                    enterpriseConfig.getFieldValue(WifiEnterpriseConfig.SUBJECT_MATCH_KEY, ""));
+            XmlUtil.writeNextValue(out, XML_TAG_ENGINE,
+                    enterpriseConfig.getFieldValue(WifiEnterpriseConfig.ENGINE_KEY, ""));
+            XmlUtil.writeNextValue(out, XML_TAG_ENGINE_ID,
+                    enterpriseConfig.getFieldValue(WifiEnterpriseConfig.ENGINE_ID_KEY, ""));
+            XmlUtil.writeNextValue(out, XML_TAG_PRIVATE_KEY_ID,
+                    enterpriseConfig.getFieldValue(WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY, ""));
+            XmlUtil.writeNextValue(out, XML_TAG_ALT_SUBJECT_MATCH,
+                    enterpriseConfig.getFieldValue(WifiEnterpriseConfig.ALTSUBJECT_MATCH_KEY, ""));
+            XmlUtil.writeNextValue(out, XML_TAG_DOM_SUFFIX_MATCH,
+                    enterpriseConfig.getFieldValue(WifiEnterpriseConfig.DOM_SUFFIX_MATCH_KEY, ""));
+            XmlUtil.writeNextValue(out, XML_TAG_CA_PATH,
+                    enterpriseConfig.getFieldValue(WifiEnterpriseConfig.CA_PATH_KEY, ""));
+            XmlUtil.writeNextValue(out, XML_TAG_EAP_METHOD, enterpriseConfig.getEapMethod());
+            XmlUtil.writeNextValue(out, XML_TAG_PHASE2_METHOD, enterpriseConfig.getPhase2Method());
+        }
+
+        /**
+         * Parses the data elements from the provided XML stream to a WifiEnterpriseConfig object.
+         *
+         * @param in            XmlPullParser instance pointing to the XML stream.
+         * @param outerTagDepth depth of the outer tag in the XML document.
+         * @return WifiEnterpriseConfig object if parsing is successful, null otherwise.
+         */
+        public static WifiEnterpriseConfig parseFromXml(XmlPullParser in, int outerTagDepth)
+                throws XmlPullParserException, IOException {
+            WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+
+            // Loop through and parse out all the elements from the stream within this section.
+            while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) {
+                String[] valueName = new String[1];
+                Object value = XmlUtil.readCurrentValue(in, valueName);
+                if (valueName[0] == null) {
+                    throw new XmlPullParserException("Missing value name");
+                }
+                switch (valueName[0]) {
+                    case XML_TAG_IDENTITY:
+                        enterpriseConfig.setFieldValue(
+                                WifiEnterpriseConfig.IDENTITY_KEY, (String) value);
+                        break;
+                    case XML_TAG_ANON_IDENTITY:
+                        enterpriseConfig.setFieldValue(
+                                WifiEnterpriseConfig.ANON_IDENTITY_KEY, (String) value);
+                        break;
+                    case XML_TAG_PASSWORD:
+                        enterpriseConfig.setFieldValue(
+                                WifiEnterpriseConfig.PASSWORD_KEY, (String) value);
+                        break;
+                    case XML_TAG_CLIENT_CERT:
+                        enterpriseConfig.setFieldValue(
+                                WifiEnterpriseConfig.CLIENT_CERT_KEY, (String) value);
+                        break;
+                    case XML_TAG_CA_CERT:
+                        enterpriseConfig.setFieldValue(
+                                WifiEnterpriseConfig.CA_CERT_KEY, (String) value);
+                        break;
+                    case XML_TAG_SUBJECT_MATCH:
+                        enterpriseConfig.setFieldValue(
+                                WifiEnterpriseConfig.SUBJECT_MATCH_KEY, (String) value);
+                        break;
+                    case XML_TAG_ENGINE:
+                        enterpriseConfig.setFieldValue(
+                                WifiEnterpriseConfig.ENGINE_KEY, (String) value);
+                        break;
+                    case XML_TAG_ENGINE_ID:
+                        enterpriseConfig.setFieldValue(
+                                WifiEnterpriseConfig.ENGINE_ID_KEY, (String) value);
+                        break;
+                    case XML_TAG_PRIVATE_KEY_ID:
+                        enterpriseConfig.setFieldValue(
+                                WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY, (String) value);
+                        break;
+                    case XML_TAG_ALT_SUBJECT_MATCH:
+                        enterpriseConfig.setFieldValue(
+                                WifiEnterpriseConfig.ALTSUBJECT_MATCH_KEY, (String) value);
+                        break;
+                    case XML_TAG_DOM_SUFFIX_MATCH:
+                        enterpriseConfig.setFieldValue(
+                                WifiEnterpriseConfig.DOM_SUFFIX_MATCH_KEY, (String) value);
+                        break;
+                    case XML_TAG_CA_PATH:
+                        enterpriseConfig.setFieldValue(
+                                WifiEnterpriseConfig.CA_PATH_KEY, (String) value);
+                        break;
+                    case XML_TAG_EAP_METHOD:
+                        enterpriseConfig.setEapMethod((int) value);
+                        break;
+                    case XML_TAG_PHASE2_METHOD:
+                        enterpriseConfig.setPhase2Method((int) value);
+                        break;
+                    default:
+                        throw new XmlPullParserException(
+                                "Unknown value name found: " + valueName[0]);
+                }
+            }
+            return enterpriseConfig;
+        }
+    }
+}
+
diff --git a/service/jni/com_android_server_wifi_WifiNative.cpp b/service/jni/com_android_server_wifi_WifiNative.cpp
index 71e3971..1467503 100644
--- a/service/jni/com_android_server_wifi_WifiNative.cpp
+++ b/service/jni/com_android_server_wifi_WifiNative.cpp
@@ -16,33 +16,41 @@
 
 #define LOG_TAG "wifi"
 
-#include "jni.h"
-#include "JniConstants.h"
-#include <ScopedUtfChars.h>
-#include <ScopedBytes.h>
-#include <utils/misc.h>
-#include <utils/Log.h>
-#include <utils/String16.h>
 #include <ctype.h>
 #include <stdlib.h>
-#include <sys/socket.h>
 #include <sys/klog.h>
-#include <linux/if.h>
+#include <sys/socket.h>
+/* We need linux/if_arp.h for ARPHRD_ETHER.  Sadly, it forward declares
+   struct sockaddr and must be included after sys/socket.h. */
 #include <linux/if_arp.h>
 
 #include <algorithm>
 #include <limits>
 #include <vector>
 
-#include "wifi.h"
-#include "wifi_hal.h"
+#include <hardware_legacy/rtt.h>
+#include <hardware_legacy/wifi.h>
+#include <hardware_legacy/wifi_hal.h>
+#include <log/log.h>
+#include <nativehelper/JniConstants.h>
+#include <nativehelper/ScopedBytes.h>
+#include <nativehelper/ScopedUtfChars.h>
+#include <nativehelper/jni.h>
+#include <utils/String16.h>
+#include <utils/misc.h>
+#include <wifi_system/hal_tool.h>
+#include <wifi_system/interface_tool.h>
+#include <wifi_system/wifi.h>
+
 #include "jni_helper.h"
-#include "rtt.h"
-#include "wifi_hal_stub.h"
+
 #define REPLY_BUF_SIZE (4096 + 1)         // wpa_supplicant's maximum size + 1 for nul
 #define EVENT_BUF_SIZE 2048
 #define WAKE_REASON_TYPE_MAX 10
 
+using android::wifi_system::HalTool;
+using android::wifi_system::InterfaceTool;
+
 namespace android {
 
 extern "C"
@@ -64,7 +72,7 @@
     }
 
     --reply_len; // Ensure we have room to add NUL termination.
-    if (::wifi_command(command.c_str(), reply, &reply_len) != 0) {
+    if (wifi_system::wifi_command(command.c_str(), reply, &reply_len) != 0) {
         return false;
     }
 
@@ -124,28 +132,28 @@
 
 static jboolean android_net_wifi_startSupplicant(JNIEnv* env, jclass, jboolean p2pSupported)
 {
-    return (::wifi_start_supplicant(p2pSupported) == 0);
+    return (wifi_system::wifi_start_supplicant(p2pSupported) == 0);
 }
 
 static jboolean android_net_wifi_killSupplicant(JNIEnv* env, jclass, jboolean p2pSupported)
 {
-    return (::wifi_stop_supplicant(p2pSupported) == 0);
+    return (wifi_system::wifi_stop_supplicant(p2pSupported) == 0);
 }
 
 static jboolean android_net_wifi_connectToSupplicant(JNIEnv* env, jclass)
 {
-    return (::wifi_connect_to_supplicant() == 0);
+    return (wifi_system::wifi_connect_to_supplicant() == 0);
 }
 
 static void android_net_wifi_closeSupplicantConnection(JNIEnv* env, jclass)
 {
-    ::wifi_close_supplicant_connection();
+    wifi_system::wifi_close_supplicant_connection();
 }
 
 static jstring android_net_wifi_waitForEvent(JNIEnv* env, jclass)
 {
     char buf[EVENT_BUF_SIZE];
-    int nread = ::wifi_wait_for_event(buf, sizeof buf);
+    int nread = wifi_system::wifi_wait_for_event(buf, sizeof buf);
     if (nread > 0) {
         return env->NewStringUTF(buf);
     } else {
@@ -237,96 +245,38 @@
     return scanResult;
 }
 
-int set_iface_flags(const char *ifname, bool dev_up) {
-    struct ifreq ifr;
-    int ret;
-    int sock = socket(PF_INET, SOCK_DGRAM, 0);
-    if (sock < 0) {
-        ALOGD("Bad socket: %d\n", sock);
-        return -errno;
-    }
-
-    //ALOGD("setting interface %s flags (%s)\n", ifname, dev_up ? "UP" : "DOWN");
-
-    memset(&ifr, 0, sizeof(ifr));
-    strlcpy(ifr.ifr_name, ifname, IFNAMSIZ);
-
-    //ALOGD("reading old value\n");
-
-    if (ioctl(sock, SIOCGIFFLAGS, &ifr) != 0) {
-      ret = errno ? -errno : -999;
-      ALOGE("Could not read interface %s flags: %d\n", ifname, errno);
-      close(sock);
-      return ret;
-    } else {
-      //ALOGD("writing new value\n");
-    }
-
-    if (dev_up) {
-      if (ifr.ifr_flags & IFF_UP) {
-        // ALOGD("interface %s is already up\n", ifname);
-        close(sock);
-        return 0;
-      }
-      ifr.ifr_flags |= IFF_UP;
-    } else {
-      if (!(ifr.ifr_flags & IFF_UP)) {
-        // ALOGD("interface %s is already down\n", ifname);
-        close(sock);
-        return 0;
-      }
-      ifr.ifr_flags &= ~IFF_UP;
-    }
-
-    if (ioctl(sock, SIOCSIFFLAGS, &ifr) != 0) {
-      ALOGE("Could not set interface %s flags: %d\n", ifname, errno);
-      ret = errno ? -errno : -999;
-      close(sock);
-      return ret;
-    } else {
-      ALOGD("set interface %s flags (%s)\n", ifname, dev_up ? "UP" : "DOWN");
-    }
-    close(sock);
-    return 0;
-}
-
 static jboolean android_net_wifi_set_interface_up(JNIEnv* env, jclass cls, jboolean up) {
-    return (set_iface_flags("wlan0", (bool)up) == 0);
+    InterfaceTool if_tool;
+    return if_tool.SetWifiUpState((bool)up);
 }
 
 static jboolean android_net_wifi_startHal(JNIEnv* env, jclass cls) {
+    InterfaceTool if_tool;
     JNIHelper helper(env);
     wifi_handle halHandle = getWifiHandle(helper, cls);
-    if (halHandle == NULL) {
-
-        if(init_wifi_stub_hal_func_table(&hal_fn) != 0 ) {
-            ALOGE("Can not initialize the basic function pointer table");
-            return false;
-        }
-
-        wifi_error res = init_wifi_vendor_hal_func_table(&hal_fn);
-        if (res != WIFI_SUCCESS) {
-            ALOGE("Can not initialize the vendor function pointer table");
-	    return false;
-        }
-
-        int ret = set_iface_flags("wlan0", true);
-        if(ret != 0) {
-            return false;
-        }
-
-        res = hal_fn.wifi_initialize(&halHandle);
-        if (res == WIFI_SUCCESS) {
-            helper.setStaticLongField(cls, WifiHandleVarName, (jlong)halHandle);
-            ALOGD("Did set static halHandle = %p", halHandle);
-        }
-        env->GetJavaVM(&mVM);
-        mCls = (jclass) env->NewGlobalRef(cls);
-        ALOGD("halHandle = %p, mVM = %p, mCls = %p", halHandle, mVM, mCls);
-        return res == WIFI_SUCCESS;
-    } else {
-        return (set_iface_flags("wlan0", true) == 0);
+    if (halHandle != NULL) {
+        return if_tool.SetWifiUpState(true);
     }
+
+    HalTool hal_tool;
+    if (!hal_tool.InitFunctionTable(&hal_fn)) {
+        return false;
+    }
+
+    if(!if_tool.SetWifiUpState(true)) {
+        ALOGE("Failed to set WiFi interface up");
+        return false;
+    }
+
+    const bool was_started = hal_fn.wifi_initialize(&halHandle) == WIFI_SUCCESS;
+    if (was_started) {
+        helper.setStaticLongField(cls, WifiHandleVarName, (jlong)halHandle);
+        ALOGD("Did set static halHandle = %p", halHandle);
+    }
+    env->GetJavaVM(&mVM);
+    mCls = (jclass) env->NewGlobalRef(cls);
+    ALOGD("halHandle = %p, mVM = %p, mCls = %p", halHandle, mVM, mCls);
+    return was_started;
 }
 
 void android_net_wifi_hal_cleaned_up_handler(wifi_handle handle) {
@@ -359,7 +309,8 @@
     JNIHelper helper(env);
     wifi_handle halHandle = getWifiHandle(helper, cls);
     hal_fn.wifi_event_loop(halHandle);
-    set_iface_flags("wlan0", false);
+    InterfaceTool if_tool;
+    if_tool.SetWifiUpState(false);
 }
 
 static int android_net_wifi_getInterfaces(JNIEnv *env, jclass cls) {
@@ -1388,7 +1339,8 @@
 }
 
 static jboolean android_net_wifi_is_get_channels_for_band_supported(JNIEnv *env, jclass cls){
-    return (hal_fn.wifi_get_valid_channels == wifi_get_valid_channels_stub);
+    HalTool hal_tool;
+    return hal_tool.CanGetValidChannels(&hal_fn);
 }
 
 static jintArray android_net_wifi_getValidChannels(JNIEnv *env, jclass cls,
diff --git a/service/jni/com_android_server_wifi_nan_WifiNanNative.cpp b/service/jni/com_android_server_wifi_nan_WifiNanNative.cpp
index cae441a..83c2490 100644
--- a/service/jni/com_android_server_wifi_nan_WifiNanNative.cpp
+++ b/service/jni/com_android_server_wifi_nan_WifiNanNative.cpp
@@ -16,19 +16,20 @@
 
 #define LOG_TAG "wifinan"
 
-#include "jni.h"
-#include "JniConstants.h"
-#include <ScopedUtfChars.h>
-#include <ScopedBytes.h>
-#include <utils/misc.h>
-#include <utils/Log.h>
-#include <utils/String16.h>
 #include <ctype.h>
 #include <stdlib.h>
 #include <sys/socket.h>
-#include <linux/if.h>
-#include "wifi.h"
-#include "wifi_hal.h"
+
+#include <hardware_legacy/wifi_hal.h>
+#include <log/log.h>
+#include <nativehelper/JniConstants.h>
+#include <nativehelper/ScopedBytes.h>
+#include <nativehelper/ScopedUtfChars.h>
+#include <nativehelper/jni.h>
+#include <utils/String16.h>
+#include <utils/misc.h>
+#include <wifi_system/wifi.h>
+
 #include "jni_helper.h"
 
 namespace android {
@@ -98,6 +99,8 @@
                          (int) msg->body.nan_capabilities.max_ndp_sessions);
       helper.setIntField(data, "maxAppInfoLen",
                          (int) msg->body.nan_capabilities.max_app_info_len);
+      helper.setIntField(data, "maxQueuedTransmitMessages",
+                         (int) msg->body.nan_capabilities.max_queued_transmit_followup_msgs);
 
       helper.reportEvent(
           mCls, "onNanNotifyResponseCapabilities",
@@ -105,6 +108,11 @@
           (short) id, (int) msg->status, (int) msg->value, data.get());
       break;
     }
+    case NAN_DP_INITIATOR_RESPONSE:
+      helper.reportEvent(mCls, "onNanNotifyResponseDataPathInitiate", "(SIII)V", (short) id,
+                         (int) msg->status, (int) msg->value,
+                         msg->body.data_request_response.ndp_instance_id);
+      break;
     default:
       helper.reportEvent(mCls, "onNanNotifyResponse", "(SIII)V", (short) id,
                          (int) msg->response_type, (int) msg->status,
@@ -208,6 +216,63 @@
     ALOGD("OnNanEventSdfPayload");
 }
 
+static void OnNanEventDataRequest(NanDataPathRequestInd* event) {
+  ALOGD("OnNanEventDataRequest");
+  JNIHelper helper(mVM);
+
+  JNIObject<jbyteArray> peerBytes = helper.newByteArray(6);
+  helper.setByteArrayRegion(peerBytes, 0, 6,
+                            (jbyte *)event->peer_disc_mac_addr);
+
+  JNIObject<jbyteArray> msgBytes =
+      helper.newByteArray(event->app_info.ndp_app_info_len);
+  helper.setByteArrayRegion(msgBytes, 0, event->app_info.ndp_app_info_len,
+                            (jbyte *)event->app_info.ndp_app_info);
+
+  helper.reportEvent(mCls, "onDataPathRequest", "(I[BI[BI)V",
+                     event->service_instance_id, peerBytes.get(),
+                     event->ndp_instance_id, msgBytes.get(),
+                     event->app_info.ndp_app_info_len);
+}
+
+static void OnNanEventDataConfirm(NanDataPathConfirmInd* event) {
+  ALOGD("OnNanEventDataConfirm");
+  JNIHelper helper(mVM);
+
+  JNIObject<jbyteArray> peerBytes = helper.newByteArray(6);
+  helper.setByteArrayRegion(peerBytes, 0, 6, (jbyte *)event->peer_ndi_mac_addr);
+
+  JNIObject<jbyteArray> msgBytes =
+      helper.newByteArray(event->app_info.ndp_app_info_len);
+  helper.setByteArrayRegion(msgBytes, 0, event->app_info.ndp_app_info_len,
+                            (jbyte *)event->app_info.ndp_app_info);
+
+  helper.reportEvent(
+      mCls, "onDataPathConfirm", "(I[BZI[BI)V", event->ndp_instance_id,
+      peerBytes.get(), event->rsp_code == NAN_DP_REQUEST_ACCEPT,
+      event->reason_code, msgBytes.get(), event->app_info.ndp_app_info_len);
+}
+
+static void OnNanEventDataEnd(NanDataPathEndInd* event) {
+  ALOGD("OnNanEventDataEnd");
+  JNIHelper helper(mVM);
+
+  for (int i = 0; i < event->num_ndp_instances; ++i) {
+    helper.reportEvent(mCls, "onDataPathEnd", "(I)V",
+                       event->ndp_instance_id[i]);
+  }
+}
+
+static void OnNanEventTransmitFollowup(NanTransmitFollowupInd* event) {
+  ALOGD("OnNanEventTransmitFollowup: transaction_id=%d, reason=%d", event->id,
+        event->reason);
+
+  JNIHelper helper(mVM);
+
+  helper.reportEvent(mCls, "onTransmitFollowupEvent", "(SI)V",
+                     (short) event->id, (int) event->reason);
+}
+
 static jint android_net_wifi_nan_register_handler(JNIEnv *env, jclass cls,
                                                   jclass wifi_native_cls,
                                                   jint iface) {
@@ -217,6 +282,7 @@
     ALOGD("android_net_wifi_nan_register_handler handle=%p", handle);
 
     NanCallbackHandler handlers;
+    memset(&handlers, 0, sizeof(NanCallbackHandler));
     handlers.NotifyResponse = OnNanNotifyResponse;
     handlers.EventPublishTerminated = OnNanEventPublishTerminated;
     handlers.EventMatch = OnNanEventMatch;
@@ -227,6 +293,10 @@
     handlers.EventDisabled = OnNanEventDisabled;
     handlers.EventTca = OnNanEventTca;
     handlers.EventBeaconSdfPayload = OnNanEventBeaconSdfPayload;
+    handlers.EventDataRequest = OnNanEventDataRequest;
+    handlers.EventDataConfirm = OnNanEventDataConfirm;
+    handlers.EventDataEnd = OnNanEventDataEnd;
+    handlers.EventTransmitFollowup = OnNanEventTransmitFollowup;
 
     if (mVM == NULL) {
         env->GetJavaVM(&mVM);
@@ -260,6 +330,27 @@
     return hal_fn.wifi_nan_enable_request(transaction_id, handle, &msg);
 }
 
+static jint android_net_wifi_nan_config_request(JNIEnv *env, jclass cls,
+                                                jshort transaction_id,
+                                                jclass wifi_native_cls,
+                                                jint iface,
+                                                jobject config_request) {
+    JNIHelper helper(env);
+    wifi_interface_handle handle = getIfaceHandle(helper, wifi_native_cls, iface);
+
+    ALOGD("android_net_wifi_nan_config_request handle=%p, id=%d",
+          handle, transaction_id);
+
+    NanConfigRequest msg;
+    memset(&msg, 0, sizeof(NanConfigRequest));
+
+    /* configurable settings */
+    msg.config_master_pref = 1;
+    msg.master_pref = helper.getIntField(config_request, "mMasterPreference");
+
+    return hal_fn.wifi_nan_config_request(transaction_id, handle, &msg);
+}
+
 static jint android_net_wifi_nan_get_capabilities(JNIEnv *env, jclass cls,
                                                   jshort transaction_id,
                                                   jclass wifi_native_cls,
@@ -291,8 +382,7 @@
                                          jint publish_id,
                                          jclass wifi_native_cls,
                                          jint iface,
-                                         jobject publish_data,
-                                         jobject publish_settings) {
+                                         jobject publish_config) {
     JNIHelper helper(env);
     wifi_interface_handle handle = getIfaceHandle(helper, wifi_native_cls, iface);
 
@@ -310,47 +400,47 @@
     /* configurable settings */
     msg.publish_id = publish_id;
 
-    JNIObject<jstring> objStr1 = helper.getStringField(publish_data, "mServiceName");
-    if (objStr1 == NULL) {
-        ALOGE("Error accessing mServiceName field");
+    size_t service_name_len;
+    helper.getByteArrayField(publish_config, "mServiceName", msg.service_name,
+                             &service_name_len, NAN_MAX_SERVICE_NAME_LEN);
+    if (service_name_len > NAN_MAX_SERVICE_NAME_LEN) {
+        ALOGE("Length of service name field larger than max allowed");
         return 0;
     }
-    ScopedUtfChars chars1(env, objStr1);
-    const char *serviceName = chars1.c_str();
-    if (serviceName == NULL) {
-        ALOGE("Error getting mServiceName");
-        return 0;
-    }
-    msg.service_name_len = strlen(serviceName);
-    strcpy((char*)msg.service_name, serviceName);
+    msg.service_name_len = service_name_len;
 
-    msg.service_specific_info_len = helper.getIntField(publish_data, "mServiceSpecificInfoLength");
+    msg.service_specific_info_len = helper.getIntField(publish_config, "mServiceSpecificInfoLength");
     if (msg.service_specific_info_len != 0) {
-        helper.getByteArrayField(publish_data, "mServiceSpecificInfo",
+        helper.getByteArrayField(publish_config, "mServiceSpecificInfo",
                              msg.service_specific_info, msg.service_specific_info_len);
     }
 
 
-    msg.tx_match_filter_len = helper.getIntField(publish_data, "mTxFilterLength");
+    msg.tx_match_filter_len = helper.getIntField(publish_config, "mTxFilterLength");
     if (msg.tx_match_filter_len != 0) {
-        helper.getByteArrayField(publish_data, "mTxFilter",
+        helper.getByteArrayField(publish_config, "mTxFilter",
                              msg.tx_match_filter, msg.tx_match_filter_len);
     }
 
-    msg.rx_match_filter_len = helper.getIntField(publish_data, "mRxFilterLength");
+    msg.rx_match_filter_len = helper.getIntField(publish_config, "mRxFilterLength");
     if (msg.rx_match_filter_len != 0) {
-        helper.getByteArrayField(publish_data, "mRxFilter",
+        helper.getByteArrayField(publish_config, "mRxFilter",
                              msg.rx_match_filter, msg.rx_match_filter_len);
     }
 
-    msg.publish_type = (NanPublishType)helper.getIntField(publish_settings, "mPublishType");
-    msg.publish_count = helper.getIntField(publish_settings, "mPublishCount");
-    msg.ttl = helper.getIntField(publish_settings, "mTtlSec");
+    msg.publish_type = (NanPublishType)helper.getIntField(publish_config, "mPublishType");
+    msg.publish_count = helper.getIntField(publish_config, "mPublishCount");
+    msg.ttl = helper.getIntField(publish_config, "mTtlSec");
 
     msg.tx_type = NAN_TX_TYPE_BROADCAST;
     if (msg.publish_type != NAN_PUBLISH_TYPE_UNSOLICITED)
       msg.tx_type = NAN_TX_TYPE_UNICAST;
 
+    msg.recv_indication_cfg = 0;
+    if (!helper.getBoolField(publish_config, "mEnableTerminateNotification")) {
+      msg.recv_indication_cfg |= 0x1;
+    }
+
     return hal_fn.wifi_nan_publish_request(transaction_id, handle, &msg);
 }
 
@@ -359,8 +449,7 @@
                                            jint subscribe_id,
                                            jclass wifi_native_cls,
                                            jint iface,
-                                           jobject subscribe_data,
-                                           jobject subscribe_settings) {
+                                           jobject subscribe_config) {
     JNIHelper helper(env);
     wifi_interface_handle handle = getIfaceHandle(helper, wifi_native_cls, iface);
 
@@ -375,7 +464,6 @@
     msg.serviceResponseInclude = NAN_SRF_INCLUDE_RESPOND;
     msg.useServiceResponseFilter = NAN_DO_NOT_USE_SRF;
     msg.ssiRequiredForMatchIndication = NAN_SSI_NOT_REQUIRED_IN_MATCH_IND;
-    msg.subscribe_match_indicator = NAN_MATCH_ALG_MATCH_ONCE;
     msg.rssi_threshold_flag = 0;
     msg.connmap = 0;
     msg.num_intf_addr_present = 0;
@@ -383,41 +471,43 @@
     /* configurable settings */
     msg.subscribe_id = subscribe_id;
 
-    JNIObject<jstring> objStr1 = helper.getStringField(subscribe_data, "mServiceName");
-    if (objStr1 == NULL) {
-        ALOGE("Error accessing mServiceName field");
+    size_t service_name_len;
+    helper.getByteArrayField(subscribe_config, "mServiceName", msg.service_name,
+                             &service_name_len, NAN_MAX_SERVICE_NAME_LEN);
+    if (service_name_len > NAN_MAX_SERVICE_NAME_LEN) {
+        ALOGE("Length of service name field larger than max allowed");
         return 0;
     }
-    ScopedUtfChars chars1(env, objStr1);
-    const char *serviceName = chars1.c_str();
-    if (serviceName == NULL) {
-        ALOGE("Error getting mServiceName");
-        return 0;
-    }
-    msg.service_name_len = strlen(serviceName);
-    strcpy((char*)msg.service_name, serviceName);
+    msg.service_name_len = service_name_len;
 
-    msg.service_specific_info_len = helper.getIntField(subscribe_data, "mServiceSpecificInfoLength");
+    msg.service_specific_info_len = helper.getIntField(subscribe_config, "mServiceSpecificInfoLength");
     if (msg.service_specific_info_len != 0) {
-        helper.getByteArrayField(subscribe_data, "mServiceSpecificInfo",
+        helper.getByteArrayField(subscribe_config, "mServiceSpecificInfo",
                              msg.service_specific_info, msg.service_specific_info_len);
     }
 
-    msg.tx_match_filter_len = helper.getIntField(subscribe_data, "mTxFilterLength");
+    msg.tx_match_filter_len = helper.getIntField(subscribe_config, "mTxFilterLength");
     if (msg.tx_match_filter_len != 0) {
-        helper.getByteArrayField(subscribe_data, "mTxFilter",
+        helper.getByteArrayField(subscribe_config, "mTxFilter",
                              msg.tx_match_filter, msg.tx_match_filter_len);
     }
 
-    msg.rx_match_filter_len = helper.getIntField(subscribe_data, "mRxFilterLength");
+    msg.rx_match_filter_len = helper.getIntField(subscribe_config, "mRxFilterLength");
     if (msg.rx_match_filter_len != 0) {
-        helper.getByteArrayField(subscribe_data, "mRxFilter",
+        helper.getByteArrayField(subscribe_config, "mRxFilter",
                              msg.rx_match_filter, msg.rx_match_filter_len);
     }
 
-    msg.subscribe_type = (NanSubscribeType)helper.getIntField(subscribe_settings, "mSubscribeType");
-    msg.subscribe_count = helper.getIntField(subscribe_settings, "mSubscribeCount");
-    msg.ttl = helper.getIntField(subscribe_settings, "mTtlSec");
+    msg.subscribe_type = (NanSubscribeType)helper.getIntField(subscribe_config, "mSubscribeType");
+    msg.subscribe_count = helper.getIntField(subscribe_config, "mSubscribeCount");
+    msg.ttl = helper.getIntField(subscribe_config, "mTtlSec");
+    msg.subscribe_match_indicator = (NanMatchAlg) helper.getIntField(
+      subscribe_config, "mMatchStyle");
+
+    msg.recv_indication_cfg = 0;
+    if (!helper.getBoolField(subscribe_config, "mEnableTerminateNotification")) {
+      msg.recv_indication_cfg |= 0x1;
+    }
 
     return hal_fn.wifi_nan_subscribe_request(transaction_id, handle, &msg);
 }
@@ -493,6 +583,142 @@
     return hal_fn.wifi_nan_subscribe_cancel_request(transaction_id, handle, &msg);
 }
 
+static jint android_net_wifi_nan_create_nan_network_interface(
+    JNIEnv *env, jclass cls, jshort transaction_id, jclass wifi_native_cls,
+    jint iface, jstring interface_name) {
+  JNIHelper helper(env);
+  wifi_interface_handle handle = getIfaceHandle(helper, wifi_native_cls, iface);
+
+  ALOGD("android_net_wifi_nan_create_nan_network_interface handle=%p, id=%d",
+        handle, transaction_id);
+
+  ScopedUtfChars chars(env, interface_name);
+  if (chars.c_str() == NULL) {
+    return 0;
+  }
+
+  return hal_fn.wifi_nan_data_interface_create(transaction_id, handle,
+                                               (char*) chars.c_str());
+}
+
+static jint android_net_wifi_nan_delete_nan_network_interface(
+    JNIEnv *env, jclass cls, jshort transaction_id, jclass wifi_native_cls,
+    jint iface, jstring interface_name) {
+  JNIHelper helper(env);
+  wifi_interface_handle handle = getIfaceHandle(helper, wifi_native_cls, iface);
+
+  ALOGD("android_net_wifi_nan_delete_nan_network_interface handle=%p, id=%d",
+        handle, transaction_id);
+
+  ScopedUtfChars chars(env, interface_name);
+  if (chars.c_str() == NULL) {
+    return 0;
+  }
+
+  return hal_fn.wifi_nan_data_interface_delete(transaction_id, handle,
+                                               (char*) chars.c_str());
+}
+
+static jint android_net_wifi_nan_initiate_nan_data_path(
+    JNIEnv *env, jclass cls, jshort transaction_id, jclass wifi_native_cls,
+    jint iface, jint pub_sub_id, jint channel_request_type, jint channel,
+    jbyteArray peer, jstring interface_name, jbyteArray message,
+    jint message_length) {
+  JNIHelper helper(env);
+  wifi_interface_handle handle = getIfaceHandle(helper, wifi_native_cls, iface);
+
+  ALOGD("android_net_wifi_nan_initiate_nan_data_path handle=%p, id=%d", handle,
+        transaction_id);
+
+  NanDataPathInitiatorRequest msg;
+  memset(&msg, 0, sizeof(NanDataPathInitiatorRequest));
+
+  msg.service_instance_id = pub_sub_id;
+  msg.channel_request_type = (NanDataPathChannelCfg) channel_request_type;
+  msg.channel = channel;
+
+  ScopedBytesRO peerBytes(env, peer);
+  memcpy(msg.peer_disc_mac_addr, (byte *)peerBytes.get(), 6);
+
+  ScopedUtfChars chars(env, interface_name);
+  if (chars.c_str() == NULL) {
+    return 0;
+  }
+  strcpy(msg.ndp_iface, chars.c_str());
+
+  // TODO: b/26564544: add security configuration
+  msg.ndp_cfg.security_cfg = NAN_DP_CONFIG_NO_SECURITY;
+
+  // TODO: b/29065317: add QoS configuration
+  msg.ndp_cfg.qos_cfg = NAN_DP_CONFIG_NO_QOS;
+
+  msg.app_info.ndp_app_info_len = message_length;
+
+  ScopedBytesRO messageBytes(env, message);
+  memcpy(msg.app_info.ndp_app_info, (byte *)messageBytes.get(), message_length);
+
+  return hal_fn.wifi_nan_data_request_initiator(transaction_id, handle, &msg);
+}
+
+static jint android_net_wifi_nan_respond_nan_data_path_request(
+    JNIEnv *env, jclass cls, jshort transaction_id, jclass wifi_native_cls,
+    jint iface, jboolean accept, jint ndp_id, jstring interface_name,
+    jbyteArray message, jint message_length) {
+  JNIHelper helper(env);
+  wifi_interface_handle handle = getIfaceHandle(helper, wifi_native_cls, iface);
+
+  ALOGD("android_net_wifi_nan_respond_nan_data_path_request handle=%p, id=%d",
+        handle, transaction_id);
+
+  NanDataPathIndicationResponse msg;
+  memset(&msg, 0, sizeof(NanDataPathIndicationResponse));
+
+  msg.ndp_instance_id = ndp_id;
+
+  ScopedUtfChars chars(env, interface_name);
+  if (chars.c_str() == NULL) {
+    return 0;
+  }
+  strcpy(msg.ndp_iface, chars.c_str());
+
+  // TODO: b/26564544: add security configuration
+  msg.ndp_cfg.security_cfg = NAN_DP_CONFIG_NO_SECURITY;
+
+  // TODO: b/29065317: add QoS configuration
+  msg.ndp_cfg.qos_cfg = NAN_DP_CONFIG_NO_QOS;
+
+  msg.app_info.ndp_app_info_len = message_length;
+
+  ScopedBytesRO messageBytes(env, message);
+  memcpy(msg.app_info.ndp_app_info, (byte *)messageBytes.get(), message_length);
+
+  msg.rsp_code = accept ? NAN_DP_REQUEST_ACCEPT : NAN_DP_REQUEST_REJECT;
+
+  return hal_fn.wifi_nan_data_indication_response(transaction_id, handle, &msg);
+}
+
+static jint android_net_wifi_nan_end_nan_data_path(JNIEnv *env, jclass cls,
+                                                   jshort transaction_id,
+                                                   jclass wifi_native_cls,
+                                                   jint iface, jint ndp_id) {
+  JNIHelper helper(env);
+  wifi_interface_handle handle = getIfaceHandle(helper, wifi_native_cls, iface);
+
+  ALOGD("android_net_wifi_nan_end_nan_data_path handle=%p, id=%d", handle,
+        transaction_id);
+
+  NanDataPathEndRequest* msg = (NanDataPathEndRequest*)malloc(sizeof(NanDataPathEndRequest) + sizeof(NanDataPathId));
+
+  msg->num_ndp_instances = 1;
+  msg->ndp_instance_id[0] = ndp_id;
+
+  jint status = hal_fn.wifi_nan_data_end(transaction_id, handle, msg);
+
+  free(msg);
+
+  return status;
+}
+
 // ----------------------------------------------------------------------------
 
 /*
@@ -501,16 +727,21 @@
 
 static JNINativeMethod gWifiNanMethods[] = {
     /* name, signature, funcPtr */
-
-    {"initNanHandlersNative", "(Ljava/lang/Object;I)I", (void*)android_net_wifi_nan_register_handler },
-    {"getCapabilitiesNative", "(SLjava/lang/Object;I)I", (void*)android_net_wifi_nan_get_capabilities },
-    {"enableAndConfigureNative", "(SLjava/lang/Object;ILandroid/net/wifi/nan/ConfigRequest;)I", (void*)android_net_wifi_nan_enable_request },
-    {"disableNative", "(SLjava/lang/Object;I)I", (void*)android_net_wifi_nan_disable_request },
-    {"publishNative", "(SILjava/lang/Object;ILandroid/net/wifi/nan/PublishData;Landroid/net/wifi/nan/PublishSettings;)I", (void*)android_net_wifi_nan_publish },
-    {"subscribeNative", "(SILjava/lang/Object;ILandroid/net/wifi/nan/SubscribeData;Landroid/net/wifi/nan/SubscribeSettings;)I", (void*)android_net_wifi_nan_subscribe },
-    {"sendMessageNative", "(SLjava/lang/Object;III[B[BI)I", (void*)android_net_wifi_nan_send_message },
-    {"stopPublishNative", "(SLjava/lang/Object;II)I", (void*)android_net_wifi_nan_stop_publish },
-    {"stopSubscribeNative", "(SLjava/lang/Object;II)I", (void*)android_net_wifi_nan_stop_subscribe },
+    {"initNanHandlersNative", "(Ljava/lang/Class;I)I", (void*)android_net_wifi_nan_register_handler },
+    {"getCapabilitiesNative", "(SLjava/lang/Class;I)I", (void*)android_net_wifi_nan_get_capabilities },
+    {"enableAndConfigureNative", "(SLjava/lang/Class;ILandroid/net/wifi/nan/ConfigRequest;)I", (void*)android_net_wifi_nan_enable_request },
+    {"updateConfigurationNative", "(SLjava/lang/Class;ILandroid/net/wifi/nan/ConfigRequest;)I", (void*)android_net_wifi_nan_config_request },
+    {"disableNative", "(SLjava/lang/Class;I)I", (void*)android_net_wifi_nan_disable_request },
+    {"publishNative", "(SILjava/lang/Class;ILandroid/net/wifi/nan/PublishConfig;)I", (void*)android_net_wifi_nan_publish },
+    {"subscribeNative", "(SILjava/lang/Class;ILandroid/net/wifi/nan/SubscribeConfig;)I", (void*)android_net_wifi_nan_subscribe },
+    {"sendMessageNative", "(SLjava/lang/Class;III[B[BI)I", (void*)android_net_wifi_nan_send_message },
+    {"stopPublishNative", "(SLjava/lang/Class;II)I", (void*)android_net_wifi_nan_stop_publish },
+    {"stopSubscribeNative", "(SLjava/lang/Class;II)I", (void*)android_net_wifi_nan_stop_subscribe },
+    {"createNanNetworkInterfaceNative", "(SLjava/lang/Class;ILjava/lang/String;)I", (void*)android_net_wifi_nan_create_nan_network_interface },
+    {"deleteNanNetworkInterfaceNative", "(SLjava/lang/Class;ILjava/lang/String;)I", (void*)android_net_wifi_nan_delete_nan_network_interface },
+    {"initiateDataPathNative", "(SLjava/lang/Class;IIII[BLjava/lang/String;[BI)I", (void*)android_net_wifi_nan_initiate_nan_data_path },
+    {"respondToDataPathRequestNative", "(SLjava/lang/Class;IZILjava/lang/String;[BI)I", (void*)android_net_wifi_nan_respond_nan_data_path_request },
+    {"endDataPathNative", "(SLjava/lang/Class;II)I", (void*)android_net_wifi_nan_end_nan_data_path },
 };
 
 /* User to register native functions */
diff --git a/service/jni/jni_helper.cpp b/service/jni/jni_helper.cpp
index c9b4edd..52f6bf2 100644
--- a/service/jni/jni_helper.cpp
+++ b/service/jni/jni_helper.cpp
@@ -16,15 +16,15 @@
 
 #define LOG_TAG "wifi"
 
-#include "jni.h"
-#include <ScopedUtfChars.h>
-#include <utils/misc.h>
 #include <android_runtime/AndroidRuntime.h>
+#include <hardware_legacy/wifi_hal.h>
+#include <nativehelper/ScopedUtfChars.h>
+#include <nativehelper/jni.h>
 #include <utils/Log.h>
 #include <utils/String16.h>
+#include <utils/misc.h>
+#include <wifi_system/wifi.h>
 
-#include "wifi.h"
-#include "wifi_hal.h"
 #include "jni_helper.h"
 
 namespace android {
@@ -241,7 +241,7 @@
     return value;
 }
 
-void JNIHelper::getByteArrayField(jobject obj, const char *name, byte* buf, int size) {
+void JNIHelper::getByteArrayField(jobject obj, const char *name, byte *buf, size_t size) {
     JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
     jfieldID field = mEnv->GetFieldID(cls, name, "[B");
     if (field == 0) {
@@ -265,6 +265,37 @@
     mEnv->ReleaseByteArrayElements(array, elem, 0);
 }
 
+void JNIHelper::getByteArrayField(jobject obj, const char *name, byte *buf, size_t *size, int max_size) {
+    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
+    jfieldID field = mEnv->GetFieldID(cls, name, "[B");
+    if (field == 0) {
+        THROW(*this, "Error in accessing field definition");
+        return;
+    }
+
+    jbyteArray byteArray = (jbyteArray)mEnv->GetObjectField(obj, field);
+    JNIObject<jbyteArray> array(*this, byteArray);
+    if (array == NULL) {
+        THROW(*this, "Error in accessing array");
+        return;
+    }
+
+    *size = getArrayLength(byteArray);
+    int use_size = *size;
+    if (use_size > max_size) {
+        use_size = max_size;
+    }
+
+    jbyte *elem = mEnv->GetByteArrayElements(array, 0);
+    if (elem == NULL) {
+        THROW(*this, "Error in accessing index element");
+        return;
+    }
+
+    memcpy(buf, elem, use_size);
+    mEnv->ReleaseByteArrayElements(array, elem, 0);
+}
+
 jlong JNIHelper::getStaticLongArrayField(jobject obj, const char *name, int index)
 {
     JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
@@ -514,6 +545,7 @@
     jmethodID methodID = mEnv->GetStaticMethodID(cls, method, signature);
     if (methodID == 0) {
         ALOGE("Error in getting method ID");
+        va_end(params);
         return;
     }
 
@@ -535,6 +567,7 @@
     jmethodID methodID = mEnv->GetMethodID(cls, method, signature);
     if (methodID == 0) {
         ALOGE("Error in getting method ID");
+        va_end(params);
         return;
     }
 
@@ -555,6 +588,7 @@
     jmethodID methodID = mEnv->GetStaticMethodID(cls, method, signature);
     if (methodID == 0) {
         ALOGE("Error in getting method ID");
+        va_end(params);
         return false;
     }
 
@@ -562,6 +596,7 @@
     if (mEnv->ExceptionCheck()) {
         mEnv->ExceptionDescribe();
         mEnv->ExceptionClear();
+        va_end(params);
         return false;
     }
 
@@ -582,18 +617,21 @@
     JNIObject<jclass> cls(*this, mEnv->FindClass(className));
     if (cls == NULL) {
         ALOGE("Error in finding class %s", className);
+        va_end(params);
         return JNIObject<jobject>(*this, NULL);
     }
 
     jmethodID constructor = mEnv->GetMethodID(cls, "<init>", signature);
     if (constructor == 0) {
         ALOGE("Error in constructor ID for %s", className);
+        va_end(params);
         return JNIObject<jobject>(*this, NULL);
     }
 
     JNIObject<jobject> obj(*this, mEnv->NewObjectV(cls, constructor, params));
     if (obj == NULL) {
         ALOGE("Could not create new object of %s", className);
+        va_end(params);
         return JNIObject<jobject>(*this, NULL);
     }
 
diff --git a/service/jni/jni_helper.h b/service/jni/jni_helper.h
index bb65f1c..f665fa7 100644
--- a/service/jni/jni_helper.h
+++ b/service/jni/jni_helper.h
@@ -82,7 +82,8 @@
     bool getStringFieldValue(jobject obj, const char *name, char *buf, int size);
     JNIObject<jobject> getObjectField(jobject obj, const char *name, const char *type);
     JNIObject<jobjectArray> getArrayField(jobject obj, const char *name, const char *type);
-    void getByteArrayField(jobject obj, const char *name, byte* buf, int size);
+    void getByteArrayField(jobject obj, const char *name, byte *buf, size_t size);
+    void getByteArrayField(jobject obj, const char *name, byte *buf, size_t *size, int max_size);
     jlong getLongArrayField(jobject obj, const char *name, int index);
     JNIObject<jobject> getObjectArrayField(
             jobject obj, const char *name, const char *type, int index);
diff --git a/service/jni/wifi_hal_stub.h b/service/jni/wifi_hal_stub.h
deleted file mode 100644
index 6ec3d8e..0000000
--- a/service/jni/wifi_hal_stub.h
+++ /dev/null
@@ -1,167 +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.
- */
-
-#ifndef __WIFI_HAL_STUB_H__
-#define __WIFI_HAL_STUB_H__
-
-#ifdef __cplusplus
-extern "C"
-{
-#endif
-
-#include "wifi_hal.h"
-
-int init_wifi_stub_hal_func_table(wifi_hal_fn *hal_fn);
-
-/* declare all HAL stub API here*/
-wifi_error wifi_initialize_stub(wifi_handle *handle);
-void wifi_cleanup_stub(wifi_handle handle, wifi_cleaned_up_handler handler);
-void wifi_event_loop_stub(wifi_handle handle);
-void wifi_get_error_info_stub(wifi_error err, const char **msg);
-wifi_error wifi_get_supported_feature_set_stub(wifi_interface_handle handle, feature_set *set);
-wifi_error wifi_get_concurrency_matrix_stub(wifi_interface_handle handle, int set_size_max,
-        feature_set set[], int *set_size);
-wifi_error wifi_get_ifaces_stub(wifi_handle handle, int *num_ifaces, wifi_interface_handle **ifaces);
-wifi_error wifi_get_iface_name_stub(wifi_interface_handle iface, char *name, size_t size);
-wifi_error wifi_set_iface_event_handler_stub(wifi_request_id id, wifi_interface_handle iface,
-        wifi_event_handler eh);
-wifi_error wifi_reset_iface_event_handler_stub(wifi_request_id id, wifi_interface_handle iface);
-wifi_error wifi_set_nodfs_flag_stub(wifi_interface_handle handle, u32 nodfs);
-wifi_error wifi_set_scanning_mac_oui_stub(wifi_interface_handle handle, unsigned char *oui);
-wifi_error wifi_get_supported_channels_stub(wifi_handle handle, int *size, wifi_channel *list);
-wifi_error wifi_is_epr_supported_stub(wifi_handle handle);
-wifi_error wifi_start_gscan_stub(wifi_request_id id, wifi_interface_handle iface,
-        wifi_scan_cmd_params params, wifi_scan_result_handler handler);
-wifi_error wifi_stop_gscan_stub(wifi_request_id id, wifi_interface_handle iface);
-wifi_error wifi_get_cached_gscan_results_stub(wifi_interface_handle iface, byte flush,
-        int max, wifi_cached_scan_results *results, int *num);
-wifi_error wifi_set_bssid_hotlist_stub(wifi_request_id id, wifi_interface_handle iface,
-        wifi_bssid_hotlist_params params, wifi_hotlist_ap_found_handler handler);
-wifi_error wifi_reset_bssid_hotlist_stub(wifi_request_id id, wifi_interface_handle iface);
-wifi_error wifi_set_significant_change_handler_stub(wifi_request_id id, wifi_interface_handle iface,
-        wifi_significant_change_params params, wifi_significant_change_handler handler);
-wifi_error wifi_reset_significant_change_handler_stub(wifi_request_id id,
-        wifi_interface_handle iface);
-wifi_error wifi_get_gscan_capabilities_stub(wifi_interface_handle handle,
-        wifi_gscan_capabilities *capabilities);
-wifi_error wifi_set_link_stats_stub(wifi_interface_handle iface, wifi_link_layer_params params);
-wifi_error wifi_get_link_stats_stub(wifi_request_id id,
-         wifi_interface_handle iface, wifi_stats_result_handler handler);
-wifi_error wifi_clear_link_stats_stub(wifi_interface_handle iface,
-       u32 stats_clear_req_mask, u32 *stats_clear_rsp_mask, u8 stop_req, u8 *stop_rsp);
-wifi_error wifi_get_valid_channels_stub(wifi_interface_handle handle,
-         int band, int max_channels, wifi_channel *channels, int *num_channels);
-wifi_error wifi_rtt_range_request_stub(wifi_request_id id, wifi_interface_handle iface,
-         unsigned num_rtt_config, wifi_rtt_config rtt_config[], wifi_rtt_event_handler handler);
-wifi_error wifi_rtt_range_cancel_stub(wifi_request_id id,  wifi_interface_handle iface,
-         unsigned num_devices, mac_addr addr[]);
-wifi_error wifi_get_rtt_capabilities_stub(wifi_interface_handle iface,
-         wifi_rtt_capabilities *capabilities);
-wifi_error wifi_rtt_get_available_channel_stub(wifi_interface_handle iface,
-        wifi_channel_info* channel);
-wifi_error wifi_enable_responder_stub(wifi_request_id id, wifi_interface_handle iface,
-        wifi_channel_info channel_hint, unsigned max_duration_seconds,
-        wifi_channel_info* channel_used);
-wifi_error wifi_disable_responder_stub(wifi_request_id id, wifi_interface_handle iface);
-
-wifi_error wifi_set_nodfs_flag_stub(wifi_interface_handle iface, u32 nodfs);
-wifi_error wifi_start_logging_stub(wifi_interface_handle iface, u32 verbose_level, u32 flags,
-         u32 max_interval_sec, u32 min_data_size, char *buffer_name);
-wifi_error wifi_set_epno_list_stub(wifi_request_id id, wifi_interface_info *iface,
-        const wifi_epno_params *params, wifi_epno_handler handler);
-wifi_error wifi_reset_epno_list_stub(wifi_request_id id, wifi_interface_info *iface);
-wifi_error wifi_set_country_code_stub(wifi_interface_handle iface, const char *code);
-wifi_error wifi_get_firmware_memory_dump_stub( wifi_interface_handle iface,
-        wifi_firmware_memory_dump_handler handler);
-wifi_error wifi_set_log_handler_stub(wifi_request_id id, wifi_interface_handle iface,
-        wifi_ring_buffer_data_handler handler);
-wifi_error wifi_reset_log_handler_stub(wifi_request_id id, wifi_interface_handle iface);
-wifi_error wifi_set_alert_handler_stub(wifi_request_id id, wifi_interface_handle iface,
-        wifi_alert_handler handler);
-wifi_error wifi_reset_alert_handler_stub(wifi_request_id id, wifi_interface_handle iface);
-wifi_error wifi_get_firmware_version_stub(wifi_interface_handle iface, char *buffer,
-        int buffer_size);
-wifi_error wifi_get_ring_buffers_status_stub(wifi_interface_handle iface,
-        u32 *num_rings, wifi_ring_buffer_status *status);
-wifi_error wifi_get_logger_supported_feature_set_stub(wifi_interface_handle iface,
-        unsigned int *support);
-wifi_error wifi_get_ring_data_stub(wifi_interface_handle iface, char *ring_name);
-wifi_error wifi_enable_tdls_stub(wifi_interface_handle iface, mac_addr addr,
-        wifi_tdls_params *params, wifi_tdls_handler handler);
-wifi_error wifi_disable_tdls_stub(wifi_interface_handle iface, mac_addr addr);
-wifi_error wifi_get_tdls_status_stub(wifi_interface_handle iface, mac_addr addr,
-        wifi_tdls_status *status);
-wifi_error wifi_get_tdls_capabilities_stub(wifi_interface_handle iface,
-        wifi_tdls_capabilities *capabilities);
-wifi_error wifi_get_driver_version_stub(wifi_interface_handle iface, char *buffer,
-        int buffer_size);
- wifi_error wifi_set_country_code_stub(wifi_interface_handle iface, const char *code);
-wifi_error wifi_set_bssid_blacklist_stub(wifi_request_id id, wifi_interface_handle iface,
-        wifi_bssid_params params);
-wifi_error wifi_start_sending_offloaded_packet_stub(wifi_request_id id,
-        wifi_interface_handle iface, u8 *ip_packet, u16 ip_packet_len,
-        u8 *src_mac_addr, u8 *dst_mac_addr, u32 period_msec);
-wifi_error wifi_stop_sending_offloaded_packet_stub(wifi_request_id id, wifi_interface_handle iface);
-wifi_error wifi_get_wake_reason_stats_stub(wifi_interface_handle iface,
-                                        WLAN_DRIVER_WAKE_REASON_CNT *wifi_wake_reason_cnt);
-wifi_error wifi_configure_nd_offload_stub(wifi_interface_handle iface, u8 enable);
-wifi_error wifi_nan_enable_request_stub(transaction_id id,
-                              wifi_interface_handle iface,
-                              NanEnableRequest* msg);
-wifi_error wifi_nan_disable_request_stub(transaction_id id,
-                               wifi_interface_handle iface);
-wifi_error wifi_nan_publish_request_stub(transaction_id id,
-                               wifi_interface_handle iface,
-                               NanPublishRequest* msg);
-wifi_error wifi_nan_publish_cancel_request_stub(transaction_id id,
-                                      wifi_interface_handle iface,
-                                      NanPublishCancelRequest* msg);
-wifi_error wifi_nan_subscribe_request_stub(transaction_id id,
-                                 wifi_interface_handle iface,
-                                 NanSubscribeRequest* msg);
-wifi_error wifi_nan_subscribe_cancel_request_stub(transaction_id id,
-                                        wifi_interface_handle iface,
-                                        NanSubscribeCancelRequest* msg);
-wifi_error wifi_nan_transmit_followup_request_stub(transaction_id id,
-                                         wifi_interface_handle iface,
-                                         NanTransmitFollowupRequest* msg);
-wifi_error wifi_nan_stats_request_stub(transaction_id id,
-                             wifi_interface_handle iface,
-                             NanStatsRequest* msg);
-wifi_error wifi_nan_config_request_stub(transaction_id id,
-                              wifi_interface_handle iface,
-                              NanConfigRequest* msg);
-wifi_error wifi_nan_tca_request_stub(transaction_id id,
-                           wifi_interface_handle iface,
-                           NanTCARequest* msg);
-wifi_error wifi_nan_beacon_sdf_payload_request_stub(transaction_id id,
-                                         wifi_interface_handle iface,
-                                         NanBeaconSdfPayloadRequest* msg);
-wifi_error wifi_nan_register_handler_stub(wifi_interface_handle iface,
-                                NanCallbackHandler handlers);
-wifi_error wifi_nan_get_version_stub(wifi_handle handle,
-                           NanVersion* version);
-wifi_error wifi_nan_get_capabilities_stub(transaction_id id,
-                                wifi_interface_handle iface);
-wifi_error wifi_get_packet_filter_capabilities_stub(wifi_interface_handle handle,
-                                          u32 *version, u32 *max_len);
-wifi_error wifi_set_packet_filter_stub(wifi_interface_handle handle,
-                             const u8 *program, u32 len);
-
-#ifdef __cplusplus
-}
-#endif
-#endif //__WIFI_HAL_STUB_H__
diff --git a/service/lib/wifi_hal.cpp b/service/lib/wifi_hal.cpp
deleted file mode 100644
index 25bb373..0000000
--- a/service/lib/wifi_hal.cpp
+++ /dev/null
@@ -1,6 +0,0 @@
-#include <stdint.h>
-#include "wifi_hal.h"
-
-wifi_error init_wifi_vendor_hal_func_table(wifi_hal_fn *fn) {
-    return WIFI_ERROR_NOT_SUPPORTED;
-}
diff --git a/service/lib/wifi_hal_stub.cpp b/service/lib/wifi_hal_stub.cpp
deleted file mode 100644
index bd75b3d..0000000
--- a/service/lib/wifi_hal_stub.cpp
+++ /dev/null
@@ -1,464 +0,0 @@
-/*
- * Copyright 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.
- */
-
-#include <stdint.h>
-#include "wifi_hal.h"
-#include "wifi_hal_stub.h"
-
-wifi_error wifi_initialize_stub(wifi_handle *handle) {
-    return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-void wifi_cleanup_stub(wifi_handle handle, wifi_cleaned_up_handler handler) {
-}
-
-void wifi_event_loop_stub(wifi_handle handle) {
-
-}
-
-void wifi_get_error_info_stub(wifi_error err, const char **msg) {
-    *msg = NULL;
-}
-
-wifi_error wifi_get_supported_feature_set_stub(wifi_interface_handle handle, feature_set *set) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_get_concurrency_matrix_stub(wifi_interface_handle handle, int max_size,
-        feature_set *matrix, int *size) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_set_scanning_mac_oui_stub(wifi_interface_handle handle, unsigned char *oui_data) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-/* List of all supported channels, including 5GHz channels */
-wifi_error wifi_get_supported_channels_stub(wifi_handle handle, int *size, wifi_channel *list) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-/* Enhanced power reporting */
-wifi_error wifi_is_epr_supported_stub(wifi_handle handle) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-/* multiple interface support */
-wifi_error wifi_get_ifaces_stub(wifi_handle handle, int *num_ifaces, wifi_interface_handle **ifaces) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_get_iface_name_stub(wifi_interface_handle iface, char *name, size_t size) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_set_iface_event_handler_stub(wifi_request_id id,
-            wifi_interface_handle iface, wifi_event_handler eh) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_reset_iface_event_handler_stub(wifi_request_id id, wifi_interface_handle iface) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_start_gscan_stub(wifi_request_id id, wifi_interface_handle iface,
-        wifi_scan_cmd_params params, wifi_scan_result_handler handler) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_stop_gscan_stub(wifi_request_id id, wifi_interface_handle iface) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_get_cached_gscan_results_stub(wifi_interface_handle iface, byte flush,
-        int max, wifi_cached_scan_results *results, int *num) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_set_bssid_hotlist_stub(wifi_request_id id, wifi_interface_handle iface,
-        wifi_bssid_hotlist_params params, wifi_hotlist_ap_found_handler handler) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_reset_bssid_hotlist_stub(wifi_request_id id, wifi_interface_handle iface) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_set_significant_change_handler_stub(wifi_request_id id, wifi_interface_handle iface,
-        wifi_significant_change_params params, wifi_significant_change_handler handler) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_reset_significant_change_handler_stub(wifi_request_id id, wifi_interface_handle iface) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_get_gscan_capabilities_stub(wifi_interface_handle handle,
-        wifi_gscan_capabilities *capabilities) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_set_link_stats_stub(wifi_interface_handle iface, wifi_link_layer_params params) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_get_link_stats_stub(wifi_request_id id,
-        wifi_interface_handle iface, wifi_stats_result_handler handler) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_clear_link_stats_stub(wifi_interface_handle iface,
-      u32 stats_clear_req_mask, u32 *stats_clear_rsp_mask, u8 stop_req, u8 *stop_rsp) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_get_valid_channels_stub(wifi_interface_handle handle,
-        int band, int max_channels, wifi_channel *channels, int *num_channels) {
-    return WIFI_ERROR_UNINITIALIZED;
-}
-
-/* API to request RTT measurement */
-wifi_error wifi_rtt_range_request_stub(wifi_request_id id, wifi_interface_handle iface,
-        unsigned num_rtt_config, wifi_rtt_config rtt_config[], wifi_rtt_event_handler handler) {
-    return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-/* API to cancel RTT measurements */
-wifi_error wifi_rtt_range_cancel_stub(wifi_request_id id,  wifi_interface_handle iface,
-        unsigned num_devices, mac_addr addr[]) {
-    return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-/* API to get RTT capability */
-wifi_error wifi_get_rtt_capabilities_stub(wifi_interface_handle iface,
-        wifi_rtt_capabilities *capabilities)
-{
-    return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-/* API to enable RTT responder role */
-wifi_error wifi_enable_responder_stub(wifi_request_id id, wifi_interface_handle iface,
-        wifi_channel_info channel_hint, unsigned max_duration_seconds,
-        wifi_channel_info* channel_used) {
-    return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-/* API to disable RTT responder role */
-wifi_error wifi_disable_responder_stub(wifi_request_id id, wifi_interface_handle iface) {
-    return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-/* API to get available channel for RTT responder role */
-wifi_error wifi_rtt_get_available_channel_stub(wifi_interface_handle iface, wifi_channel_info* channel) {
-    return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_set_nodfs_flag_stub(wifi_interface_handle iface, u32 nodfs) {
-    return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_start_logging_stub(wifi_interface_handle iface, u32 verbose_level, u32 flags,
-        u32 max_interval_sec, u32 min_data_size, char *buffer_name) {
-            return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_set_epno_list_stub(int id, wifi_interface_info *iface,
-        const wifi_epno_params *params, wifi_epno_handler handler) {
-    return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_reset_epno_list_stub(int id, wifi_interface_info *iface) {
-    return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_set_country_code_stub(wifi_interface_handle iface, const char *code) {
-    return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_get_firmware_memory_dump_stub( wifi_interface_handle iface,
-        wifi_firmware_memory_dump_handler handler){
-    return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_set_log_handler_stub(wifi_request_id id, wifi_interface_handle iface,
-        wifi_ring_buffer_data_handler handler) {
-    return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_reset_log_handler_stub(wifi_request_id id, wifi_interface_handle iface) {
-    return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_set_alert_handler_stub(wifi_request_id id, wifi_interface_handle iface,
-        wifi_alert_handler handler) {
-    return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_reset_alert_handler_stub(wifi_request_id id, wifi_interface_handle iface) {
-    return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_get_firmware_version_stub( wifi_interface_handle iface, char *buffer,
-        int buffer_size) {
-    return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_get_ring_buffers_status_stub(wifi_interface_handle iface,
-        u32 *num_rings, wifi_ring_buffer_status *status) {
-    return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_get_logger_supported_feature_set_stub(wifi_interface_handle iface,
-        unsigned int *support) {
-    return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_get_ring_data_stub(wifi_interface_handle iface, char *ring_name) {
-    return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_get_driver_version_stub(wifi_interface_handle iface, char *buffer,
-        int buffer_size) {
-    return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_enable_tdls_stub(wifi_interface_handle iface, mac_addr addr,
-        wifi_tdls_params *params, wifi_tdls_handler handler) {
-    return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_disable_tdls_stub(wifi_interface_handle iface, mac_addr addr) {
-    return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_get_tdls_status_stub(wifi_interface_handle iface, mac_addr addr,
-        wifi_tdls_status *status) {
-    return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_get_tdls_capabilities_stub(wifi_interface_handle iface,
-        wifi_tdls_capabilities *capabilities) {
-    return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_set_bssid_blacklist_stub(wifi_request_id id, wifi_interface_handle iface,
-        wifi_bssid_params params) {
-      return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_start_sending_offloaded_packet_stub(wifi_request_id id,
-        wifi_interface_handle iface, u8 *ip_packet, u16 ip_packet_len,
-        u8 *src_mac_addr, u8 *dst_mac_addr, u32 period_msec) {
-    return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_stop_sending_offloaded_packet_stub(wifi_request_id id, wifi_interface_handle iface) {
-    return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_get_wake_reason_stats_stub(wifi_interface_handle iface,
-                                    WLAN_DRIVER_WAKE_REASON_CNT *wifi_wake_reason_cnt) {
-    return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_configure_nd_offload_stub(wifi_interface_handle iface, u8 enable) {
-    return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_get_driver_memory_dump_stub(wifi_interface_handle iface,
-    wifi_driver_memory_dump_callbacks callbacks)  {
-    return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_start_pkt_fate_monitoring_stub(wifi_interface_handle iface) {
-    return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_get_tx_pkt_fates_stub(wifi_interface_handle handle,
-    wifi_tx_report *tx_report_bufs, size_t n_requested_fates, size_t *n_provided_fates) {
-    return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_get_rx_pkt_fates_stub(wifi_interface_handle handle,
-    wifi_rx_report *rx_report_bufs, size_t n_requested_fates, size_t *n_provided_fates) {
-    return WIFI_ERROR_NOT_SUPPORTED;
-}
-wifi_error wifi_nan_enable_request_stub(transaction_id id,
-                              wifi_interface_handle iface,
-                              NanEnableRequest* msg) {
-  return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_nan_disable_request_stub(transaction_id id,
-                               wifi_interface_handle iface) {
-  return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_nan_publish_request_stub(transaction_id id,
-                               wifi_interface_handle iface,
-                               NanPublishRequest* msg) {
-  return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_nan_publish_cancel_request_stub(transaction_id id,
-                                      wifi_interface_handle iface,
-                                      NanPublishCancelRequest* msg) {
-  return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_nan_subscribe_request_stub(transaction_id id,
-                                 wifi_interface_handle iface,
-                                 NanSubscribeRequest* msg) {
-  return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_nan_subscribe_cancel_request_stub(transaction_id id,
-                                        wifi_interface_handle iface,
-                                        NanSubscribeCancelRequest* msg) {
-  return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_nan_transmit_followup_request_stub(transaction_id id,
-                                         wifi_interface_handle iface,
-                                         NanTransmitFollowupRequest* msg) {
-  return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_nan_stats_request_stub(transaction_id id,
-                             wifi_interface_handle iface,
-                             NanStatsRequest* msg) {
-  return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_nan_config_request_stub(transaction_id id,
-                              wifi_interface_handle iface,
-                              NanConfigRequest* msg) {
-  return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_nan_tca_request_stub(transaction_id id,
-                           wifi_interface_handle iface,
-                           NanTCARequest* msg) {
-  return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_nan_beacon_sdf_payload_request_stub(transaction_id id,
-                                         wifi_interface_handle iface,
-                                         NanBeaconSdfPayloadRequest* msg) {
-  return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_nan_register_handler_stub(wifi_interface_handle iface,
-                                NanCallbackHandler handlers) {
-  return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_nan_get_version_stub(wifi_handle handle,
-                           NanVersion* version) {
-  return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_nan_get_capabilities_stub(transaction_id id,
-                                wifi_interface_handle iface) {
-  return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_get_packet_filter_capabilities_stub(wifi_interface_handle handle,
-                                          u32 *version, u32 *max_len) {
-  return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-wifi_error wifi_set_packet_filter_stub(wifi_interface_handle handle,
-                             const u8 *program, u32 len) {
-  return WIFI_ERROR_NOT_SUPPORTED;
-}
-
-int init_wifi_stub_hal_func_table(wifi_hal_fn *hal_fn) {
-    if (hal_fn == NULL) {
-        return -1;
-    }
-    hal_fn->wifi_initialize = wifi_initialize_stub;
-    hal_fn->wifi_cleanup = wifi_cleanup_stub;
-    hal_fn->wifi_event_loop = wifi_event_loop_stub;
-    hal_fn->wifi_get_error_info = wifi_get_error_info_stub;
-    hal_fn->wifi_get_supported_feature_set = wifi_get_supported_feature_set_stub;
-    hal_fn->wifi_get_concurrency_matrix = wifi_get_concurrency_matrix_stub;
-    hal_fn->wifi_set_scanning_mac_oui =  wifi_set_scanning_mac_oui_stub;
-    hal_fn->wifi_get_supported_channels = wifi_get_supported_channels_stub;
-    hal_fn->wifi_is_epr_supported = wifi_is_epr_supported_stub;
-    hal_fn->wifi_get_ifaces = wifi_get_ifaces_stub;
-    hal_fn->wifi_get_iface_name = wifi_get_iface_name_stub;
-    hal_fn->wifi_reset_iface_event_handler = wifi_reset_iface_event_handler_stub;
-    hal_fn->wifi_start_gscan = wifi_start_gscan_stub;
-    hal_fn->wifi_stop_gscan = wifi_stop_gscan_stub;
-    hal_fn->wifi_get_cached_gscan_results = wifi_get_cached_gscan_results_stub;
-    hal_fn->wifi_set_bssid_hotlist = wifi_set_bssid_hotlist_stub;
-    hal_fn->wifi_reset_bssid_hotlist = wifi_reset_bssid_hotlist_stub;
-    hal_fn->wifi_set_significant_change_handler = wifi_set_significant_change_handler_stub;
-    hal_fn->wifi_reset_significant_change_handler = wifi_reset_significant_change_handler_stub;
-    hal_fn->wifi_get_gscan_capabilities = wifi_get_gscan_capabilities_stub;
-    hal_fn->wifi_set_link_stats = wifi_set_link_stats_stub;
-    hal_fn->wifi_get_link_stats = wifi_get_link_stats_stub;
-    hal_fn->wifi_clear_link_stats = wifi_clear_link_stats_stub;
-    hal_fn->wifi_get_valid_channels = wifi_get_valid_channels_stub;
-    hal_fn->wifi_rtt_range_request = wifi_rtt_range_request_stub;
-    hal_fn->wifi_rtt_range_cancel = wifi_rtt_range_cancel_stub;
-    hal_fn->wifi_get_rtt_capabilities = wifi_get_rtt_capabilities_stub;
-    hal_fn->wifi_start_logging = wifi_start_logging_stub;
-    hal_fn->wifi_set_epno_list = wifi_set_epno_list_stub;
-    hal_fn->wifi_set_country_code = wifi_set_country_code_stub;
-    hal_fn->wifi_enable_tdls = wifi_enable_tdls_stub;
-    hal_fn->wifi_disable_tdls = wifi_disable_tdls_stub;
-    hal_fn->wifi_get_tdls_status = wifi_get_tdls_status_stub;
-    hal_fn->wifi_get_tdls_capabilities = wifi_get_tdls_capabilities_stub;
-    hal_fn->wifi_set_nodfs_flag = wifi_set_nodfs_flag_stub;
-    hal_fn->wifi_get_firmware_memory_dump = wifi_get_firmware_memory_dump_stub;
-    hal_fn->wifi_set_log_handler = wifi_set_log_handler_stub;
-    hal_fn->wifi_reset_log_handler = wifi_reset_log_handler_stub;
-    hal_fn->wifi_set_alert_handler = wifi_set_alert_handler_stub;
-    hal_fn->wifi_reset_alert_handler = wifi_reset_alert_handler_stub;
-    hal_fn->wifi_get_firmware_version = wifi_get_firmware_version_stub;
-    hal_fn->wifi_get_ring_buffers_status = wifi_get_ring_buffers_status_stub;
-    hal_fn->wifi_get_logger_supported_feature_set = wifi_get_logger_supported_feature_set_stub;
-    hal_fn->wifi_get_ring_data = wifi_get_ring_data_stub;
-    hal_fn->wifi_get_driver_version = wifi_get_driver_version_stub;
-    hal_fn->wifi_set_bssid_blacklist = wifi_set_bssid_blacklist_stub;
-    hal_fn->wifi_start_sending_offloaded_packet = wifi_start_sending_offloaded_packet_stub;
-    hal_fn->wifi_stop_sending_offloaded_packet = wifi_stop_sending_offloaded_packet_stub;
-    hal_fn->wifi_get_wake_reason_stats = wifi_get_wake_reason_stats_stub;
-    hal_fn->wifi_configure_nd_offload = wifi_configure_nd_offload_stub;
-    hal_fn->wifi_get_driver_memory_dump = wifi_get_driver_memory_dump_stub;
-    hal_fn->wifi_start_pkt_fate_monitoring = wifi_start_pkt_fate_monitoring_stub;
-    hal_fn->wifi_get_tx_pkt_fates = wifi_get_tx_pkt_fates_stub;
-    hal_fn->wifi_get_rx_pkt_fates = wifi_get_rx_pkt_fates_stub;
-    hal_fn->wifi_nan_enable_request = wifi_nan_enable_request_stub;
-    hal_fn->wifi_nan_disable_request = wifi_nan_disable_request_stub;
-    hal_fn->wifi_nan_publish_request = wifi_nan_publish_request_stub;
-    hal_fn->wifi_nan_publish_cancel_request = wifi_nan_publish_cancel_request_stub;
-    hal_fn->wifi_nan_subscribe_request = wifi_nan_subscribe_request_stub;
-    hal_fn->wifi_nan_subscribe_cancel_request = wifi_nan_subscribe_cancel_request_stub;
-    hal_fn->wifi_nan_transmit_followup_request = wifi_nan_transmit_followup_request_stub;
-    hal_fn->wifi_nan_stats_request = wifi_nan_stats_request_stub;
-    hal_fn->wifi_nan_config_request = wifi_nan_config_request_stub;
-    hal_fn->wifi_nan_tca_request = wifi_nan_tca_request_stub;
-    hal_fn->wifi_nan_beacon_sdf_payload_request = wifi_nan_beacon_sdf_payload_request_stub;
-    hal_fn->wifi_nan_register_handler = wifi_nan_register_handler_stub;
-    hal_fn->wifi_nan_get_version = wifi_nan_get_version_stub;
-    hal_fn->wifi_get_packet_filter_capabilities = wifi_get_packet_filter_capabilities_stub;
-    hal_fn->wifi_set_packet_filter = wifi_set_packet_filter_stub;
-
-    return 0;
-}
diff --git a/tests/wifitests/Android.mk b/tests/wifitests/Android.mk
index e2489c4..c65a9d0 100644
--- a/tests/wifitests/Android.mk
+++ b/tests/wifitests/Android.mk
@@ -28,30 +28,29 @@
 	$(JNI_H_INCLUDE) \
 	$(LOCAL_PATH)/../../service/jni \
 	$(call include-path-for, libhardware)/hardware \
-	$(call include-path-for, libhardware_legacy)/hardware_legacy \
+	$(call include-path-for, libhardware_legacy) \
 	packages/apps/Test/connectivity/sl4n/rapidjson/include \
 	libcore/include
 
 LOCAL_SRC_FILES := \
 	jni/wifi_hal_mock.cpp
 
-ifdef INCLUDE_NAN_FEATURE
+ifeq ($(BOARD_HAS_NAN), true)
 LOCAL_SRC_FILES += \
 	jni/wifi_nan_hal_mock.cpp
 endif
 
 LOCAL_MODULE := libwifi-hal-mock
 
-LOCAL_STATIC_LIBRARIES += libwifi-hal
 LOCAL_SHARED_LIBRARIES += \
 	libnativehelper \
 	libcutils \
 	libutils \
 	libhardware \
-	libhardware_legacy \
 	libnl \
 	libdl \
-	libwifi-service
+	libwifi-service \
+	libwifi-system
 
 include $(BUILD_SHARED_LIBRARY)
 
@@ -63,7 +62,7 @@
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
 
-ifndef INCLUDE_NAN_FEATURE
+ifneq ($(BOARD_HAS_NAN), true)
 LOCAL_SRC_FILES := $(filter-out $(call all-java-files-under, \
           src/com/android/server/wifi/nan),$(LOCAL_SRC_FILES))
 endif
@@ -103,6 +102,7 @@
 LOCAL_STATIC_JAVA_LIBRARIES := \
 	android-support-test \
 	mockito-target \
+	frameworks-base-testutils \
 	services \
 	wifi-service \
 
@@ -114,20 +114,40 @@
 # These must be explicitly included because they are not normally accessible
 # from apps.
 LOCAL_JNI_SHARED_LIBRARIES := \
+	libcrypto \
 	libwifi-service \
-	libc++ \
-	libLLVM \
-	libutils \
-	libunwind \
-	libhardware_legacy \
-	libbase \
-	libhardware \
-	libnl \
-	libcutils \
-	libnetutils \
+	libEGL \
+	libGLESv2 \
+	libaudioutils \
 	libbacktrace \
-	libnativehelper \
+	libbase \
+	libbinder \
+	libc++ \
+	libcamera_client \
+	libcamera_metadata \
+	libcutils \
+	libexpat \
+	libgui \
+	libhardware \
+	libicui18n \
+	libicuuc \
 	liblzma \
+	libmedia \
+	libnativehelper \
+	libnbaio \
+	libnetutils \
+	libnl \
+	libpowermanager \
+	libsonivox \
+	libspeexresampler \
+	libstagefright_foundation \
+	libstdc++ \
+	libsync \
+	libwifi-hal \
+	libwifi-system \
+	libui \
+	libunwind \
+	libutils \
 
 ifdef WPA_SUPPLICANT_VERSION
 LOCAL_JNI_SHARED_LIBRARIES += libwpa_client
diff --git a/tests/wifitests/README.md b/tests/wifitests/README.md
index c68014e..8812f83 100644
--- a/tests/wifitests/README.md
+++ b/tests/wifitests/README.md
@@ -8,21 +8,22 @@
 The easiest way to run tests is simply run
 
 ```
-runtest frameworks-wifi
+frameworks/opt/net/wifi/tests/wifitests/runtests.sh
 ```
 
-`runtest` will build the test project and push the APK to the connected device. It will then run the
-tests on the device. See `runtest --help` for options to specify individual test classes or methods.
+`runtests.sh` will build the test project and all of its dependencies and push the APK to the
+connected device. It will then run the tests on the device.
 
-**WARNING:** You have to build the components under test (wifi-service, etc) first before you run
-runtest for changes there to take effect. You can use the following command from your build root to
-build the wifi service and run tests.
+See below for a few example of options to limit which tests are run.
+See the
+[AndroidJUnitRunner Documentation](https://developer.android.com/reference/android/support/test/runner/AndroidJUnitRunner.html)
+for more details on the supported options.
 
 ```
-mmma frameworks/opt/net/wifi/tests && runtest frameworks-wifi
+runtests.sh -e package com.android.server.wifi.util
+runtests.sh -e class com.android.server.wifi.WifiStateMachineTest
 ```
 
-
 If you manually build and push the test APK to the device you can run tests using
 
 ```
@@ -41,7 +42,7 @@
 ## Code Coverage
 If you would like to collect code coverage information you can run the `coverage.sh` script located
 in this directory. It will rebuild parts of your tree with coverage enabled and then run the tests,
-similar to runtest. If you have multiple devices connected to your machine make sure to set the
+similar to runtests.sh. If you have multiple devices connected to your machine make sure to set the
 `ANDROID_SERIAL` environment variable before running the script. You must supply an output directory
 for results. By default the results are generated as a set of HTML pages. For example, you can use
 the following from the root out your source tree to generate results in the wifi_coverage directory
diff --git a/tests/wifitests/jni/wifi_hal_mock.cpp b/tests/wifitests/jni/wifi_hal_mock.cpp
index 06b2d23..96c0dd5 100644
--- a/tests/wifitests/jni/wifi_hal_mock.cpp
+++ b/tests/wifitests/jni/wifi_hal_mock.cpp
@@ -25,8 +25,8 @@
 #include <ctype.h>
 #include <sys/socket.h>
 #include <linux/if.h>
-#include "wifi.h"
-#include "wifi_hal.h"
+#include "wifi_system/wifi.h"
+#include "hardware_legacy/wifi_hal.h"
 #include "jni_helper.h"
 #include "wifi_hal_mock.h"
 #include <sstream>
diff --git a/tests/wifitests/jni/wifi_hal_mock.h b/tests/wifitests/jni/wifi_hal_mock.h
index 2b9cad4..f316122 100644
--- a/tests/wifitests/jni/wifi_hal_mock.h
+++ b/tests/wifitests/jni/wifi_hal_mock.h
@@ -17,7 +17,7 @@
 #ifndef __WIFI_HAL_MOCK_H__
 #define __WIFI_HAL_MOCK_H__
 
-#include "wifi_hal.h"
+#include "hardware_legacy/wifi_hal.h"
 
 #include <rapidjson/document.h>
 #include <rapidjson/stringbuffer.h>
@@ -90,6 +90,21 @@
 wifi_error wifi_nan_get_version_mock(wifi_handle handle, NanVersion* version);
 wifi_error wifi_nan_get_capabilities_mock(transaction_id id,
                                 wifi_interface_handle iface);
+wifi_error wifi_nan_data_interface_create_mock(transaction_id id,
+                                               wifi_interface_handle iface,
+                                               char* iface_name);
+wifi_error wifi_nan_data_interface_delete_mock(transaction_id id,
+                                               wifi_interface_handle iface,
+                                               char* iface_name);
+wifi_error wifi_nan_data_request_initiator_mock(
+    transaction_id id, wifi_interface_handle iface,
+    NanDataPathInitiatorRequest* msg);
+wifi_error wifi_nan_data_indication_response_mock(
+    transaction_id id, wifi_interface_handle iface,
+    NanDataPathIndicationResponse* msg);
+wifi_error wifi_nan_data_end_mock(transaction_id id,
+                                  wifi_interface_handle iface,
+                                  NanDataPathEndRequest* msg);
 
 }  // namespace android
 
diff --git a/tests/wifitests/jni/wifi_nan_hal_mock.cpp b/tests/wifitests/jni/wifi_nan_hal_mock.cpp
index 022fadd..01da519 100644
--- a/tests/wifitests/jni/wifi_nan_hal_mock.cpp
+++ b/tests/wifitests/jni/wifi_nan_hal_mock.cpp
@@ -25,8 +25,8 @@
 #include <ctype.h>
 #include <sys/socket.h>
 #include <linux/if.h>
-#include "wifi.h"
-#include "wifi_hal.h"
+#include "wifi_system/wifi.h"
+#include "hardware_legacy/wifi_hal.h"
 #include "jni_helper.h"
 #include "wifi_hal_mock.h"
 #include <sstream>
@@ -161,6 +161,7 @@
                        msg->tx_match_filter_len);
   jsonW.put_int("rssi_threshold_flag", msg->rssi_threshold_flag);
   jsonW.put_int("connmap", msg->connmap);
+  jsonW.put_int("recv_indication_cfg", msg->recv_indication_cfg);
   std::string str = jsonW.to_string();
 
   JNIObject < jstring > json_write_string = helper.newStringUTF(str.c_str());
@@ -222,6 +223,7 @@
   jsonW.put_int("connmap", msg->connmap);
   jsonW.put_int("num_intf_addr_present", msg->num_intf_addr_present);
   // TODO: jsonW.put_byte_array("intf_addr", msg->intf_addr, NAN_MAX_SUBSCRIBE_MAX_ADDRESS * NAN_MAC_ADDR_LEN);
+  jsonW.put_int("recv_indication_cfg", msg->recv_indication_cfg);
   std::string str = jsonW.to_string();
 
   JNIObject < jstring > json_write_string = helper.newStringUTF(str.c_str());
@@ -286,8 +288,41 @@
 wifi_error wifi_nan_config_request_mock(transaction_id id,
                                         wifi_interface_handle iface,
                                         NanConfigRequest* msg) {
+  JNIHelper helper(mock_mVM);
+
   ALOGD("wifi_nan_config_request_mock");
-  return WIFI_ERROR_UNINITIALIZED;
+  HalMockJsonWriter jsonW;
+  jsonW.put_int("config_sid_beacon", msg->config_sid_beacon);
+  jsonW.put_int("sid_beacon", msg->sid_beacon);
+  jsonW.put_int("config_rssi_proximity", msg->config_rssi_proximity);
+  jsonW.put_int("rssi_proximity", msg->rssi_proximity);
+  jsonW.put_int("config_master_pref", msg->config_master_pref);
+  jsonW.put_int("master_pref", msg->master_pref);
+  jsonW.put_int("config_5g_rssi_close_proximity", msg->config_5g_rssi_close_proximity);
+  jsonW.put_int("rssi_close_proximity_5g_val", msg->rssi_close_proximity_5g_val);
+  jsonW.put_int("config_rssi_window_size", msg->config_rssi_window_size);
+  jsonW.put_int("rssi_window_size_val", msg->rssi_window_size_val);
+  jsonW.put_int("config_cluster_attribute_val", msg->config_cluster_attribute_val);
+  jsonW.put_int("config_scan_params", msg->config_scan_params);
+  // TODO: NanSocialChannelScanParams scan_params_val
+  jsonW.put_int("config_random_factor_force", msg->config_random_factor_force);
+  jsonW.put_int("random_factor_force_val", msg->random_factor_force_val);
+  jsonW.put_int("config_hop_count_force", msg->config_hop_count_force);
+  jsonW.put_int("hop_count_force_val", msg->hop_count_force_val);
+  jsonW.put_int("config_conn_capability", msg->config_conn_capability);
+  // TODO: NanTransmitPostConnectivityCapability conn_capability_val
+  jsonW.put_int("num_config_discovery_attr", msg->num_config_discovery_attr);
+  // TODO: NanTransmitPostDiscovery discovery_attr_val[NAN_MAX_POSTDISCOVERY_LEN]
+  jsonW.put_int("config_fam", msg->config_fam);
+  // TODO: NanFurtherAvailabilityMap fam_val
+  std::string str = jsonW.to_string();
+
+  JNIObject < jstring > json_write_string = helper.newStringUTF(str.c_str());
+
+  helper.callMethod(mock_mObj, "configHalMockNative", "(SLjava/lang/String;)V",
+                    (short) id, json_write_string.get());
+
+  return WIFI_SUCCESS;
 }
 
 wifi_error wifi_nan_tca_request_mock(transaction_id id,
@@ -327,6 +362,119 @@
   return WIFI_SUCCESS;
 }
 
+wifi_error wifi_nan_data_interface_create_mock(transaction_id id,
+                                               wifi_interface_handle iface,
+                                               char* iface_name) {
+  JNIHelper helper(mock_mVM);
+
+  ALOGD("wifi_nan_data_interface_create_mock");
+  HalMockJsonWriter jsonW;
+  jsonW.put_byte_array("iface_name", (u8*) iface_name, strlen(iface_name));
+  std::string str = jsonW.to_string();
+
+  JNIObject<jstring> json_write_string = helper.newStringUTF(str.c_str());
+
+  helper.callMethod(mock_mObj, "createNanNetworkInterfaceMockNative",
+                    "(SLjava/lang/String;)V", (short)id,
+                    json_write_string.get());
+
+  return WIFI_SUCCESS;
+}
+
+wifi_error wifi_nan_data_interface_delete_mock(transaction_id id,
+                                               wifi_interface_handle iface,
+                                               char* iface_name) {
+  JNIHelper helper(mock_mVM);
+
+  ALOGD("wifi_nan_data_interface_delete_mock");
+  HalMockJsonWriter jsonW;
+  jsonW.put_byte_array("iface_name", (u8*) iface_name, strlen(iface_name));
+  std::string str = jsonW.to_string();
+
+  JNIObject<jstring> json_write_string = helper.newStringUTF(str.c_str());
+
+  helper.callMethod(mock_mObj, "deleteNanNetworkInterfaceMockNative",
+                    "(SLjava/lang/String;)V", (short)id,
+                    json_write_string.get());
+
+  return WIFI_SUCCESS;
+}
+
+wifi_error wifi_nan_data_request_initiator_mock(
+    transaction_id id, wifi_interface_handle iface,
+    NanDataPathInitiatorRequest* msg) {
+  JNIHelper helper(mock_mVM);
+
+  ALOGD("wifi_nan_data_request_initiator_mock");
+  HalMockJsonWriter jsonW;
+  jsonW.put_int("service_instance_id", msg->service_instance_id);
+  jsonW.put_int("channel_request_type", (int)msg->channel_request_type);
+  jsonW.put_int("channel", (int)msg->channel);
+  jsonW.put_byte_array("peer_disc_mac_addr", msg->peer_disc_mac_addr, 6);
+  jsonW.put_byte_array("ndp_iface", (u8*) msg->ndp_iface, strlen(msg->ndp_iface));
+  jsonW.put_int("ndp_cfg.security_cfg", (int)msg->ndp_cfg.security_cfg);
+  jsonW.put_int("ndp_cfg.qos_cfg", (int)msg->ndp_cfg.qos_cfg);
+  jsonW.put_int("app_info.ndp_app_info_len", msg->app_info.ndp_app_info_len);
+  jsonW.put_byte_array("app_info.ndp_app_info", msg->app_info.ndp_app_info,
+                       msg->app_info.ndp_app_info_len);
+  std::string str = jsonW.to_string();
+
+  JNIObject<jstring> json_write_string = helper.newStringUTF(str.c_str());
+
+  helper.callMethod(mock_mObj, "initiateDataPathMockNative",
+                    "(SLjava/lang/String;)V", (short)id,
+                    json_write_string.get());
+
+  return WIFI_SUCCESS;
+}
+
+wifi_error wifi_nan_data_indication_response_mock(
+    transaction_id id, wifi_interface_handle iface,
+    NanDataPathIndicationResponse* msg) {
+  JNIHelper helper(mock_mVM);
+
+  ALOGD("wifi_nan_data_indication_response_mock");
+  HalMockJsonWriter jsonW;
+  jsonW.put_int("ndp_instance_id", msg->ndp_instance_id);
+  jsonW.put_byte_array("ndp_iface", (u8*) msg->ndp_iface, strlen(msg->ndp_iface));
+  jsonW.put_int("ndp_cfg.security_cfg", (int)msg->ndp_cfg.security_cfg);
+  jsonW.put_int("ndp_cfg.qos_cfg", (int)msg->ndp_cfg.qos_cfg);
+  jsonW.put_int("app_info.ndp_app_info_len", msg->app_info.ndp_app_info_len);
+  jsonW.put_byte_array("app_info.ndp_app_info", msg->app_info.ndp_app_info,
+                       msg->app_info.ndp_app_info_len);
+  jsonW.put_int("rsp_code", (int)msg->rsp_code);
+  std::string str = jsonW.to_string();
+
+  JNIObject<jstring> json_write_string = helper.newStringUTF(str.c_str());
+
+  helper.callMethod(mock_mObj, "respondToDataPathRequestMockNative",
+                    "(SLjava/lang/String;)V", (short)id,
+                    json_write_string.get());
+
+  return WIFI_SUCCESS;
+}
+
+wifi_error wifi_nan_data_end_mock(transaction_id id,
+                                  wifi_interface_handle iface,
+                                  NanDataPathEndRequest* msg) {
+  JNIHelper helper(mock_mVM);
+
+  ALOGD("wifi_nan_data_end_mock");
+  HalMockJsonWriter jsonW;
+  jsonW.put_int("num_ndp_instances", msg->num_ndp_instances);
+  if (msg->num_ndp_instances == 1) {
+    jsonW.put_int("ndp_instance_id", msg->ndp_instance_id[0]);
+  }
+  std::string str = jsonW.to_string();
+
+  JNIObject<jstring> json_write_string = helper.newStringUTF(str.c_str());
+
+  helper.callMethod(mock_mObj, "endDataPathMockNative", "(SLjava/lang/String;)V",
+                    (short)id, json_write_string.get());
+
+  return WIFI_SUCCESS;
+}
+
 // Callbacks
 
 extern "C" void Java_com_android_server_wifi_nan_WifiNanHalMock_callNotifyResponse(
@@ -374,6 +522,11 @@
         "body.nan_capabilities.max_ndp_sessions", &error);
     msg.body.nan_capabilities.max_app_info_len = jsonR.get_int(
         "body.nan_capabilities.max_app_info_len", &error);
+    msg.body.nan_capabilities.max_queued_transmit_followup_msgs = jsonR.get_int(
+        "body.nan_capabilities.max_queued_transmit_followup_msgs", &error);
+  } else if (msg.response_type == NAN_DP_INITIATOR_RESPONSE) {
+      msg.body.data_request_response.ndp_instance_id = jsonR.get_int(
+          "body.data_request_response.ndp_instance_id", &error);
   }
 
   if (error) {
@@ -541,8 +694,123 @@
   mCallbackHandlers.EventDisabled(&msg);
 }
 
+extern "C" void Java_com_android_server_wifi_nan_WifiNanHalMock_callTransmitFollowup(
+    JNIEnv* env, jclass clazz, jstring json_args_jstring) {
+  ScopedUtfChars chars(env, json_args_jstring);
+  HalMockJsonReader jsonR(chars.c_str());
+  bool error = false;
+
+  ALOGD("Java_com_android_server_wifi_nan_WifiNanHalMock_callTransmitFollowup: '%s'",
+        chars.c_str());
+
+  NanTransmitFollowupInd msg;
+  msg.id = (transaction_id) jsonR.get_int("id", &error);
+  msg.reason = (NanStatusType) jsonR.get_int("reason", &error);
+
+  if (error) {
+    ALOGE("Java_com_android_server_wifi_nan_WifiNanHalMock_callTransmitFollowup: "
+          "error parsing args");
+    return;
+  }
+
+  mCallbackHandlers.EventTransmitFollowup(&msg);
+}
+
+extern "C" void Java_com_android_server_wifi_nan_WifiNanHalMock_callDataPathRequest(
+    JNIEnv* env, jclass clazz, jstring json_args_jstring) {
+  ScopedUtfChars chars(env, json_args_jstring);
+  HalMockJsonReader jsonR(chars.c_str());
+  bool error = false;
+
+  ALOGD("Java_com_android_server_wifi_nan_WifiNanHalMock_callDataPathRequest: '%s'",
+        chars.c_str());
+
+  NanDataPathRequestInd msg;
+  msg.service_instance_id = jsonR.get_int("service_instance_id", &error);
+  jsonR.get_byte_array("peer_disc_mac_addr", &error, msg.peer_disc_mac_addr, 6);
+  msg.ndp_instance_id = (NanDataPathId)jsonR.get_int("ndp_instance_id", &error);
+  msg.ndp_cfg.security_cfg = (NanDataPathSecurityCfgStatus)jsonR.get_int(
+      "ndp_cfg.security_cfg", &error);
+  msg.ndp_cfg.qos_cfg =
+      (NanDataPathQosCfg)jsonR.get_int("ndp_cfg.qos_cfg", &error);
+  msg.app_info.ndp_app_info_len =
+      jsonR.get_int("app_info.ndp_app_info_len", &error);
+  jsonR.get_byte_array("app_info.ndp_app_info", &error,
+                       msg.app_info.ndp_app_info,
+                       msg.app_info.ndp_app_info_len);
+
+  if (error) {
+    ALOGE(
+        "Java_com_android_server_wifi_nan_WifiNanHalMock_callDataPathRequest: "
+        "error parsing args");
+    return;
+  }
+
+  mCallbackHandlers.EventDataRequest(&msg);
+}
+
+extern "C" void Java_com_android_server_wifi_nan_WifiNanHalMock_callDataPathConfirm(
+    JNIEnv* env, jclass clazz, jstring json_args_jstring) {
+  ScopedUtfChars chars(env, json_args_jstring);
+  HalMockJsonReader jsonR(chars.c_str());
+  bool error = false;
+
+  ALOGD("Java_com_android_server_wifi_nan_WifiNanHalMock_callDataPathConfirm: '%s'",
+        chars.c_str());
+
+  NanDataPathConfirmInd msg;
+  msg.ndp_instance_id = (NanDataPathId)jsonR.get_int("ndp_instance_id", &error);
+  jsonR.get_byte_array("peer_ndi_mac_addr", &error, msg.peer_ndi_mac_addr, 6);
+  msg.app_info.ndp_app_info_len =
+      jsonR.get_int("app_info.ndp_app_info_len", &error);
+  jsonR.get_byte_array("app_info.ndp_app_info", &error,
+                       msg.app_info.ndp_app_info,
+                       msg.app_info.ndp_app_info_len);
+  msg.rsp_code = (NanDataPathResponseCode)jsonR.get_int("rsp_code", &error);
+  msg.reason_code = (NanStatusType)jsonR.get_int("reason_code", &error);
+
+  if (error) {
+    ALOGE(
+        "Java_com_android_server_wifi_nan_WifiNanHalMock_callDataPathConfirm: "
+        "error parsing args");
+    return;
+  }
+
+  mCallbackHandlers.EventDataConfirm(&msg);
+}
+
+extern "C" void Java_com_android_server_wifi_nan_WifiNanHalMock_callDataPathEnd(
+    JNIEnv* env, jclass clazz, jstring json_args_jstring) {
+  ScopedUtfChars chars(env, json_args_jstring);
+  HalMockJsonReader jsonR(chars.c_str());
+  bool error = false;
+
+  ALOGD("Java_com_android_server_wifi_nan_WifiNanHalMock_callDataPathEnd: '%s'",
+        chars.c_str());
+
+  int num_ndp_instances = jsonR.get_int("num_ndp_instances", &error);
+
+  NanDataPathEndInd* msg = (NanDataPathEndInd*) malloc(sizeof(NanDataPathEndInd)
+                                                       + num_ndp_instances * sizeof(NanDataPathId));
+  msg->num_ndp_instances = num_ndp_instances;
+  for (int i = 0; i < num_ndp_instances; ++i) {
+    msg->ndp_instance_id[i] = jsonR.get_int("ndp_instance_id", &error) + i;
+  }
+
+  if (error) {
+    ALOGE(
+        "Java_com_android_server_wifi_nan_WifiNanHalMock_callDataPathEnd: "
+        "error parsing args");
+    return;
+  }
+
+  mCallbackHandlers.EventDataEnd(msg);
+
+  free(msg);
+}
+
 // TODO: Not currently used: add as needed
-//void (*EventUnMatch) (NanUnmatchInd* event);
+//void (*EventMatchExpired) (NanUnmatchInd* event);
 //void (*EventTca) (NanTCAInd* event);
 //void (*EventBeaconSdfPayload) (NanBeaconSdfPayloadInd* event);
 
@@ -569,6 +837,11 @@
   fn->wifi_nan_register_handler = wifi_nan_register_handler_mock;
   fn->wifi_nan_get_version = wifi_nan_get_version_mock;
   fn->wifi_nan_get_capabilities = wifi_nan_get_capabilities_mock;
+  fn->wifi_nan_data_interface_create = wifi_nan_data_interface_create_mock;
+  fn->wifi_nan_data_interface_delete = wifi_nan_data_interface_delete_mock;
+  fn->wifi_nan_data_request_initiator = wifi_nan_data_request_initiator_mock;
+  fn->wifi_nan_data_indication_response = wifi_nan_data_indication_response_mock;
+  fn->wifi_nan_data_end = wifi_nan_data_end_mock;
 
   return 0;
 }
diff --git a/tests/wifitests/runtests.sh b/tests/wifitests/runtests.sh
new file mode 100755
index 0000000..c0551a4
--- /dev/null
+++ b/tests/wifitests/runtests.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+
+if [ -z $ANDROID_BUILD_TOP ]; then
+  echo "You need to source and lunch before you can use this script"
+  exit 1
+fi
+
+echo "Running tests"
+
+set -e # fail early
+
+echo "+ mmma -j32 $ANDROID_BUILD_TOP/frameworks/opt/net/wifi/tests"
+# NOTE Don't actually run the command above since this shell doesn't inherit functions from the
+#      caller.
+make -j32 -C $ANDROID_BUILD_TOP -f build/core/main.mk MODULES-IN-frameworks-opt-net-wifi-tests
+
+set -x # print commands
+
+adb root
+adb wait-for-device
+
+adb install -r -g "$OUT/data/app/FrameworksWifiTests/FrameworksWifiTests.apk"
+
+adb shell am instrument -w "$@" 'com.android.server.wifi.test/android.support.test.runner.AndroidJUnitRunner'
diff --git a/tests/wifitests/src/android/net/wifi/WifiEnterpriseConfigTest.java b/tests/wifitests/src/android/net/wifi/WifiEnterpriseConfigTest.java
index 75480b5..814594d 100644
--- a/tests/wifitests/src/android/net/wifi/WifiEnterpriseConfigTest.java
+++ b/tests/wifitests/src/android/net/wifi/WifiEnterpriseConfigTest.java
@@ -19,6 +19,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertNull;
 
 import android.net.wifi.WifiEnterpriseConfig.Eap;
 import android.net.wifi.WifiEnterpriseConfig.Phase2;
@@ -50,6 +51,22 @@
     }
 
     @Test
+    public void testGetEmptyCaCertificate() {
+        // A newly-constructed WifiEnterpriseConfig object should have no CA certificate.
+        assertNull(mEnterpriseConfig.getCaCertificate());
+        assertNull(mEnterpriseConfig.getCaCertificates());
+        // Setting CA certificate to null explicitly.
+        mEnterpriseConfig.setCaCertificate(null);
+        assertNull(mEnterpriseConfig.getCaCertificate());
+        // Setting CA certificate to null using setCaCertificates().
+        mEnterpriseConfig.setCaCertificates(null);
+        assertNull(mEnterpriseConfig.getCaCertificates());
+        // Setting CA certificate to zero-length array.
+        mEnterpriseConfig.setCaCertificates(new X509Certificate[0]);
+        assertNull(mEnterpriseConfig.getCaCertificates());
+    }
+
+    @Test
     public void testSetGetSingleCaCertificate() {
         X509Certificate cert0 = FakeKeys.CA_CERT0;
         mEnterpriseConfig.setCaCertificate(cert0);
diff --git a/tests/wifitests/src/android/net/wifi/WifiScannerTest.java b/tests/wifitests/src/android/net/wifi/WifiScannerTest.java
index 5034c2a..a829eb9 100644
--- a/tests/wifitests/src/android/net/wifi/WifiScannerTest.java
+++ b/tests/wifitests/src/android/net/wifi/WifiScannerTest.java
@@ -30,10 +30,10 @@
 import android.net.wifi.WifiScanner.BssidListener;
 import android.os.Handler;
 import android.os.Message;
+import android.os.test.TestLooper;
 import android.test.suitebuilder.annotation.SmallTest;
 
-import com.android.server.wifi.BidirectionalAsyncChannelServer;
-import com.android.server.wifi.MockLooper;
+import com.android.internal.util.test.BidirectionalAsyncChannelServer;
 
 import org.junit.After;
 import org.junit.Before;
@@ -55,7 +55,7 @@
     private BssidListener mBssidListener;
 
     private WifiScanner mWifiScanner;
-    private MockLooper mLooper;
+    private TestLooper mLooper;
     private Handler mHandler;
 
     /**
@@ -64,7 +64,7 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mLooper = new MockLooper();
+        mLooper = new TestLooper();
         mHandler = mock(Handler.class);
         BidirectionalAsyncChannelServer server = new BidirectionalAsyncChannelServer(
                 mContext, mLooper.getLooper(), mHandler);
diff --git a/tests/wifitests/src/com/android/server/wifi/AnqpCacheTest.java b/tests/wifitests/src/com/android/server/wifi/AnqpCacheTest.java
index 1a96831..9b06d13 100644
--- a/tests/wifitests/src/com/android/server/wifi/AnqpCacheTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/AnqpCacheTest.java
@@ -113,7 +113,7 @@
     private void advanceTimeAndTrimCache(long howManyMillis) {
         mCurrentTimeMillis += howManyMillis;
         Log.d(TAG, "Time set to " + mCurrentTimeMillis);
-        when(mClock.currentTimeMillis()).thenReturn(mCurrentTimeMillis);
+        when(mClock.getWallClockMillis()).thenReturn(mCurrentTimeMillis);
         mCache.clear(false, true);
     }
 
diff --git a/tests/wifitests/src/com/android/server/wifi/BidirectionalAsyncChannel.java b/tests/wifitests/src/com/android/server/wifi/BidirectionalAsyncChannel.java
deleted file mode 100644
index 75c0f87..0000000
--- a/tests/wifitests/src/com/android/server/wifi/BidirectionalAsyncChannel.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.wifi;
-
-import static org.junit.Assert.assertEquals;
-
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Messenger;
-import android.util.Log;
-
-import com.android.internal.util.AsyncChannel;
-
-
-/**
- * Provides an AsyncChannel interface that implements the connection initiating half of a
- * bidirectional channel as described in {@link com.android.internal.util.AsyncChannel}.
- */
-public class BidirectionalAsyncChannel {
-    private static final String TAG = "BidirectionalAsyncChannel";
-
-    private AsyncChannel mChannel;
-    public enum ChannelState { DISCONNECTED, HALF_CONNECTED, CONNECTED, FAILURE };
-    private ChannelState mState = ChannelState.DISCONNECTED;
-
-    public void assertConnected() {
-        assertEquals("AsyncChannel was not fully connected", ChannelState.CONNECTED, mState);
-    }
-
-    public void connect(final Looper looper, final Messenger messenger,
-            final Handler incomingMessageHandler) {
-        assertEquals("AsyncChannel must be disconnected to connect",
-                ChannelState.DISCONNECTED, mState);
-        mChannel = new AsyncChannel();
-        Handler rawMessageHandler = new Handler(looper) {
-                @Override
-                public void handleMessage(Message msg) {
-                    switch (msg.what) {
-                    case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
-                        if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
-                            Log.d(TAG, "Successfully half connected " + this);
-                            mChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
-                            mState = ChannelState.HALF_CONNECTED;
-                        } else {
-                            Log.d(TAG, "Failed to connect channel " + this);
-                            mState = ChannelState.FAILURE;
-                            mChannel = null;
-                        }
-                        break;
-                    case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED:
-                        mState = ChannelState.CONNECTED;
-                        Log.d(TAG, "Channel fully connected" + this);
-                        break;
-                    case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
-                        mState = ChannelState.DISCONNECTED;
-                        mChannel = null;
-                        Log.d(TAG, "Channel disconnected" + this);
-                        break;
-                    default:
-                        incomingMessageHandler.handleMessage(msg);
-                        break;
-                    }
-                }
-            };
-        mChannel.connect(null, rawMessageHandler, messenger);
-    }
-
-    public void disconnect() {
-        assertEquals("AsyncChannel must be connected to disconnect",
-                ChannelState.CONNECTED, mState);
-        mChannel.sendMessage(AsyncChannel.CMD_CHANNEL_DISCONNECT);
-        mState = ChannelState.DISCONNECTED;
-        mChannel = null;
-    }
-
-    public void sendMessage(Message msg) {
-        assertEquals("AsyncChannel must be connected to send messages",
-                ChannelState.CONNECTED, mState);
-        mChannel.sendMessage(msg);
-    }
-}
diff --git a/tests/wifitests/src/com/android/server/wifi/BidirectionalAsyncChannelServer.java b/tests/wifitests/src/com/android/server/wifi/BidirectionalAsyncChannelServer.java
deleted file mode 100644
index 6cc0e90..0000000
--- a/tests/wifitests/src/com/android/server/wifi/BidirectionalAsyncChannelServer.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wifi;
-
-import android.content.Context;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Messenger;
-import android.util.Log;
-
-import com.android.internal.util.AsyncChannel;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Provides an interface for the server side implementation of a bidirectional channel as described
- * in {@link com.android.internal.util.AsyncChannel}.
- */
-public class BidirectionalAsyncChannelServer {
-
-    private static final String TAG = "BidirectionalAsyncChannelServer";
-
-    // Keeps track of incoming clients, which are identifiable by their messengers.
-    private final Map<Messenger, AsyncChannel> mClients = new HashMap<>();
-
-    private Messenger mMessenger;
-
-    public BidirectionalAsyncChannelServer(final Context context, final Looper looper,
-            final Handler messageHandler) {
-        Handler handler = new Handler(looper) {
-            @Override
-            public void handleMessage(Message msg) {
-                AsyncChannel channel = mClients.get(msg.replyTo);
-                switch (msg.what) {
-                    case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
-                        if (channel != null) {
-                            Log.d(TAG, "duplicate client connection: " + msg.sendingUid);
-                            channel.replyToMessage(msg,
-                                    AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
-                                    AsyncChannel.STATUS_FULL_CONNECTION_REFUSED_ALREADY_CONNECTED);
-                        } else {
-                            channel = new AsyncChannel();
-                            mClients.put(msg.replyTo, channel);
-                            channel.connected(context, this, msg.replyTo);
-                            channel.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
-                                    AsyncChannel.STATUS_SUCCESSFUL);
-                        }
-                        break;
-                    case AsyncChannel.CMD_CHANNEL_DISCONNECT:
-                        channel.disconnect();
-                        break;
-
-                    case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
-                        mClients.remove(msg.replyTo);
-                        break;
-
-                    default:
-                        messageHandler.handleMessage(msg);
-                        break;
-                }
-            }
-        };
-        mMessenger = new Messenger(handler);
-    }
-
-    public Messenger getMessenger() {
-        return mMessenger;
-    }
-
-}
diff --git a/tests/wifitests/src/com/android/server/wifi/ConfigurationMapTest.java b/tests/wifitests/src/com/android/server/wifi/ConfigurationMapTest.java
index 328feaf..c1e333b 100644
--- a/tests/wifitests/src/com/android/server/wifi/ConfigurationMapTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/ConfigurationMapTest.java
@@ -21,6 +21,7 @@
 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.WifiConfiguration;
 import android.os.UserHandle;
@@ -28,8 +29,6 @@
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.SparseArray;
 
-import com.android.server.wifi.MockAnswerUtil.AnswerWithArguments;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
diff --git a/tests/wifitests/src/com/android/server/wifi/HalMockUtils.java b/tests/wifitests/src/com/android/server/wifi/HalMockUtils.java
index 6e485f6..3235ff7 100644
--- a/tests/wifitests/src/com/android/server/wifi/HalMockUtils.java
+++ b/tests/wifitests/src/com/android/server/wifi/HalMockUtils.java
@@ -19,12 +19,11 @@
 import android.os.Bundle;
 import android.util.Log;
 
-import com.android.server.wifi.WifiNative;
-
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
+import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.util.Iterator;
 
@@ -43,14 +42,24 @@
 
     public static void initHalMockLibrary() throws Exception {
         /*
-         * Setting the Wi-Fi HAL handle and interface (array) to dummy
-         * values. Required to fake the init checking code to think that
-         * the HAL actually started.
-         *
-         * Note that values are not important since they are only used by
-         * the real HAL - which is mocked-out in this use-case.
+         * Setting the Wi-Fi HAL handle and interface (array) to dummy values.
+         * Need to install a default WifiNative (since another mock may have
+         * installed something else). Required to fake the init checking code to
+         * think that the HAL actually started. Note that values are not
+         * important since they are only used by the real HAL - which is
+         * mocked-out in this use-case.
          */
-        Field field = WifiNative.class.getDeclaredField("sWifiHalHandle");
+
+        Constructor<WifiNative> ctr = WifiNative.class.getDeclaredConstructor(String.class,
+                                                                              Boolean.TYPE);
+        ctr.setAccessible(true);
+        WifiNative wifiNativeInstance = ctr.newInstance("wlan0", true);
+
+        Field field = WifiNative.class.getDeclaredField("wlanNativeInterface");
+        field.setAccessible(true);
+        field.set(null, wifiNativeInstance);
+
+        field = WifiNative.class.getDeclaredField("sWifiHalHandle");
         field.setAccessible(true);
         long currentWifiHalHandle = field.getLong(null);
         if (DBG) Log.d(TAG, "currentWifiHalHandle=" + currentWifiHalHandle);
diff --git a/tests/wifitests/src/com/android/server/wifi/MockAlarmManager.java b/tests/wifitests/src/com/android/server/wifi/MockAlarmManager.java
deleted file mode 100644
index 02af281..0000000
--- a/tests/wifitests/src/com/android/server/wifi/MockAlarmManager.java
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wifi;
-
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.anyLong;
-import static org.mockito.Mockito.anyString;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-
-import android.app.AlarmManager;
-import android.os.Handler;
-
-import com.android.server.wifi.MockAnswerUtil.AnswerWithArguments;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * Creates an AlarmManager whose alarm dispatch can be controlled
- * Currently only supports alarm listeners
- *
- * Alarm listeners will be dispatched to the handler provided or will
- * be dispatched imediatly if they would have been sent to the main
- * looper (handler was null).
- */
-public class MockAlarmManager {
-    private final AlarmManager mAlarmManager;
-    private final List<PendingAlarm> mPendingAlarms;
-
-    public MockAlarmManager() throws Exception {
-        mPendingAlarms = new ArrayList<>();
-
-        mAlarmManager = mock(AlarmManager.class);
-        doAnswer(new SetListenerAnswer()).when(mAlarmManager).set(anyInt(), anyLong(), anyString(),
-                any(AlarmManager.OnAlarmListener.class), any(Handler.class));
-        doAnswer(new SetListenerAnswer()).when(mAlarmManager).setExact(anyInt(), anyLong(),
-                anyString(), any(AlarmManager.OnAlarmListener.class), any(Handler.class));
-        doAnswer(new CancelListenerAnswer())
-                .when(mAlarmManager).cancel(any(AlarmManager.OnAlarmListener.class));
-    }
-
-    public AlarmManager getAlarmManager() {
-        return mAlarmManager;
-    }
-
-    /**
-     * Dispatch a pending alarm with the given tag
-     * @return if any alarm was dispatched
-     */
-    public boolean dispatch(String tag) {
-        for (int i = 0; i < mPendingAlarms.size(); ++i) {
-            PendingAlarm alarm = mPendingAlarms.get(i);
-            if (Objects.equals(tag, alarm.getTag())) {
-                mPendingAlarms.remove(i);
-                alarm.dispatch();
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * @return if an alarm with the given tag is pending
-     */
-    public boolean isPending(String tag) {
-        for (int i = 0; i < mPendingAlarms.size(); ++i) {
-            PendingAlarm alarm = mPendingAlarms.get(i);
-            if (Objects.equals(tag, alarm.getTag())) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * @return trigger time of an pending alarm with the given tag
-     *         -1 if no pending alram with the given tag
-     */
-    public long getTriggerTimeMillis(String tag) {
-        for (int i = 0; i < mPendingAlarms.size(); ++i) {
-            PendingAlarm alarm = mPendingAlarms.get(i);
-            if (Objects.equals(tag, alarm.getTag())) {
-                return alarm.getTriggerTimeMillis();
-            }
-        }
-        return -1;
-    }
-
-    private static class PendingAlarm {
-        private final int mType;
-        private final long mTriggerAtMillis;
-        private final String mTag;
-        private final Runnable mCallback;
-
-        public PendingAlarm(int type, long triggerAtMillis, String tag, Runnable callback) {
-            mType = type;
-            mTriggerAtMillis = triggerAtMillis;
-            mTag = tag;
-            mCallback = callback;
-        }
-
-        public void dispatch() {
-            if (mCallback != null) {
-                mCallback.run();
-            }
-        }
-
-        public Runnable getCallback() {
-            return mCallback;
-        }
-
-        public String getTag() {
-            return mTag;
-        }
-
-        public long getTriggerTimeMillis() {
-            return mTriggerAtMillis;
-        }
-    }
-
-    private class SetListenerAnswer extends AnswerWithArguments {
-        public void answer(int type, long triggerAtMillis, String tag,
-                AlarmManager.OnAlarmListener listener, Handler handler) {
-            mPendingAlarms.add(new PendingAlarm(type, triggerAtMillis, tag,
-                            new AlarmListenerRunnable(listener, handler)));
-        }
-    }
-
-    private class CancelListenerAnswer extends AnswerWithArguments {
-        public void answer(AlarmManager.OnAlarmListener listener) {
-            Iterator<PendingAlarm> alarmItr = mPendingAlarms.iterator();
-            while (alarmItr.hasNext()) {
-                PendingAlarm alarm = alarmItr.next();
-                if (alarm.getCallback() instanceof AlarmListenerRunnable) {
-                    AlarmListenerRunnable alarmCallback =
-                            (AlarmListenerRunnable) alarm.getCallback();
-                    if (alarmCallback.getListener() == listener) {
-                        alarmItr.remove();
-                    }
-                }
-            }
-        }
-    }
-
-    private static class AlarmListenerRunnable implements Runnable {
-        private final AlarmManager.OnAlarmListener mListener;
-        private final Handler mHandler;
-        public AlarmListenerRunnable(AlarmManager.OnAlarmListener listener, Handler handler) {
-            mListener = listener;
-            mHandler = handler;
-        }
-
-        public Handler getHandler() {
-            return mHandler;
-        }
-
-        public AlarmManager.OnAlarmListener getListener() {
-            return mListener;
-        }
-
-        public void run() {
-            if (mHandler != null) {
-                mHandler.post(new Runnable() {
-                        public void run() {
-                            mListener.onAlarm();
-                        }
-                    });
-            } else { // normally gets dispatched in main looper
-                mListener.onAlarm();
-            }
-        }
-    }
-}
diff --git a/tests/wifitests/src/com/android/server/wifi/MockAnswerUtil.java b/tests/wifitests/src/com/android/server/wifi/MockAnswerUtil.java
deleted file mode 100644
index 1a3dfa1..0000000
--- a/tests/wifitests/src/com/android/server/wifi/MockAnswerUtil.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wifi;
-
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.Arrays;
-
-/**
- * Utilities for creating Answers for mock objects
- */
-public class MockAnswerUtil {
-
-    /**
-     * Answer that calls the method in the Answer called "answer" that matches the type signature of
-     * the method being answered. An error will be thrown at runtime if the signature does not match
-     * exactly.
-     */
-    public static class AnswerWithArguments implements Answer<Object> {
-        @Override
-        public final Object answer(InvocationOnMock invocation) throws Throwable {
-            Method method = invocation.getMethod();
-            try {
-                Method implementation = getClass().getMethod("answer", method.getParameterTypes());
-                if (!implementation.getReturnType().equals(method.getReturnType())) {
-                    throw new RuntimeException("Found answer method does not have expected return "
-                            + "type. Expected: " + method.getReturnType() + ", got "
-                            + implementation.getReturnType());
-                }
-                Object[] args = invocation.getArguments();
-                try {
-                    return implementation.invoke(this, args);
-                } catch (IllegalAccessException e) {
-                    throw new RuntimeException("Error invoking answer method", e);
-                } catch (InvocationTargetException e) {
-                    throw e.getCause();
-                }
-            } catch (NoSuchMethodException e) {
-                throw new RuntimeException("Could not find answer method with the expected args "
-                        + Arrays.toString(method.getParameterTypes()), e);
-            }
-        }
-    }
-
-}
diff --git a/tests/wifitests/src/com/android/server/wifi/MockLooper.java b/tests/wifitests/src/com/android/server/wifi/MockLooper.java
deleted file mode 100644
index 34d80a4..0000000
--- a/tests/wifitests/src/com/android/server/wifi/MockLooper.java
+++ /dev/null
@@ -1,283 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wifi;
-
-import static org.junit.Assert.assertTrue;
-
-import android.os.Looper;
-import android.os.Message;
-import android.os.MessageQueue;
-import android.os.SystemClock;
-import android.util.Log;
-
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-/**
- * Creates a looper whose message queue can be manipulated
- * This allows testing code that uses a looper to dispatch messages in a deterministic manner
- * Creating a MockLooper will also install it as the looper for the current thread
- */
-public class MockLooper {
-    protected final Looper mLooper;
-
-    private static final Constructor<Looper> LOOPER_CONSTRUCTOR;
-    private static final Field THREAD_LOCAL_LOOPER_FIELD;
-    private static final Field MESSAGE_QUEUE_MESSAGES_FIELD;
-    private static final Field MESSAGE_NEXT_FIELD;
-    private static final Field MESSAGE_WHEN_FIELD;
-    private static final Method MESSAGE_MARK_IN_USE_METHOD;
-    private static final String TAG = "MockLooper";
-
-    private AutoDispatchThread mAutoDispatchThread;
-
-    static {
-        try {
-            LOOPER_CONSTRUCTOR = Looper.class.getDeclaredConstructor(Boolean.TYPE);
-            LOOPER_CONSTRUCTOR.setAccessible(true);
-            THREAD_LOCAL_LOOPER_FIELD = Looper.class.getDeclaredField("sThreadLocal");
-            THREAD_LOCAL_LOOPER_FIELD.setAccessible(true);
-            MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages");
-            MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true);
-            MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next");
-            MESSAGE_NEXT_FIELD.setAccessible(true);
-            MESSAGE_WHEN_FIELD = Message.class.getDeclaredField("when");
-            MESSAGE_WHEN_FIELD.setAccessible(true);
-            MESSAGE_MARK_IN_USE_METHOD = Message.class.getDeclaredMethod("markInUse");
-            MESSAGE_MARK_IN_USE_METHOD.setAccessible(true);
-        } catch (NoSuchFieldException | NoSuchMethodException e) {
-            throw new RuntimeException("Failed to initialize MockLooper", e);
-        }
-    }
-
-
-    public MockLooper() {
-        try {
-            mLooper = LOOPER_CONSTRUCTOR.newInstance(false);
-
-            ThreadLocal<Looper> threadLocalLooper = (ThreadLocal<Looper>) THREAD_LOCAL_LOOPER_FIELD
-                    .get(null);
-            threadLocalLooper.set(mLooper);
-        } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
-            throw new RuntimeException("Reflection error constructing or accessing looper", e);
-        }
-    }
-
-    public Looper getLooper() {
-        return mLooper;
-    }
-
-    private Message getMessageLinkedList() {
-        try {
-            MessageQueue queue = mLooper.getQueue();
-            return (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(queue);
-        } catch (IllegalAccessException e) {
-            throw new RuntimeException("Access failed in MockLooper: get - MessageQueue.mMessages",
-                    e);
-        }
-    }
-
-    public void moveTimeForward(long milliSeconds) {
-        try {
-            Message msg = getMessageLinkedList();
-            while (msg != null) {
-                long updatedWhen = msg.getWhen() - milliSeconds;
-                if (updatedWhen < 0) {
-                    updatedWhen = 0;
-                }
-                MESSAGE_WHEN_FIELD.set(msg, updatedWhen);
-                msg = (Message) MESSAGE_NEXT_FIELD.get(msg);
-            }
-        } catch (IllegalAccessException e) {
-            throw new RuntimeException("Access failed in MockLooper: set - Message.when", e);
-        }
-    }
-
-    private Message messageQueueNext() {
-        try {
-            long now = SystemClock.uptimeMillis();
-
-            Message prevMsg = null;
-            Message msg = getMessageLinkedList();
-            if (msg != null && msg.getTarget() == null) {
-                // Stalled by a barrier. Find the next asynchronous message in
-                // the queue.
-                do {
-                    prevMsg = msg;
-                    msg = (Message) MESSAGE_NEXT_FIELD.get(msg);
-                } while (msg != null && !msg.isAsynchronous());
-            }
-            if (msg != null) {
-                if (now >= msg.getWhen()) {
-                    // Got a message.
-                    if (prevMsg != null) {
-                        MESSAGE_NEXT_FIELD.set(prevMsg, MESSAGE_NEXT_FIELD.get(msg));
-                    } else {
-                        MESSAGE_QUEUE_MESSAGES_FIELD.set(mLooper.getQueue(),
-                                MESSAGE_NEXT_FIELD.get(msg));
-                    }
-                    MESSAGE_NEXT_FIELD.set(msg, null);
-                    MESSAGE_MARK_IN_USE_METHOD.invoke(msg);
-                    return msg;
-                }
-            }
-        } catch (IllegalAccessException | InvocationTargetException e) {
-            throw new RuntimeException("Access failed in MockLooper", e);
-        }
-
-        return null;
-    }
-
-    /**
-     * @return true if there are pending messages in the message queue
-     */
-    public synchronized boolean isIdle() {
-        Message messageList = getMessageLinkedList();
-
-        return messageList != null && SystemClock.uptimeMillis() >= messageList.getWhen();
-    }
-
-    /**
-     * @return the next message in the Looper's message queue or null if there is none
-     */
-    public synchronized Message nextMessage() {
-        if (isIdle()) {
-            return messageQueueNext();
-        } else {
-            return null;
-        }
-    }
-
-    /**
-     * Dispatch the next message in the queue
-     * Asserts that there is a message in the queue
-     */
-    public synchronized void dispatchNext() {
-        assertTrue(isIdle());
-        Message msg = messageQueueNext();
-        if (msg == null) {
-            return;
-        }
-        msg.getTarget().dispatchMessage(msg);
-    }
-
-    /**
-     * Dispatch all messages currently in the queue
-     * Will not fail if there are no messages pending
-     * @return the number of messages dispatched
-     */
-    public synchronized int dispatchAll() {
-        int count = 0;
-        while (isIdle()) {
-            dispatchNext();
-            ++count;
-        }
-        return count;
-    }
-
-    /**
-     * Thread used to dispatch messages when the main thread is blocked waiting for a response.
-     */
-    private class AutoDispatchThread extends Thread {
-        private static final int MAX_LOOPS = 100;
-        private static final int LOOP_SLEEP_TIME_MS = 10;
-
-        private RuntimeException mAutoDispatchException = null;
-
-        /**
-         * Run method for the auto dispatch thread.
-         * The thread loops a maximum of MAX_LOOPS times with a 10ms sleep between loops.
-         * The thread continues looping and attempting to dispatch all messages until at
-         * least one message has been dispatched.
-         */
-        @Override
-        public void run() {
-            int dispatchCount = 0;
-            for (int i = 0; i < MAX_LOOPS; i++) {
-                try {
-                    dispatchCount = dispatchAll();
-                } catch (RuntimeException e) {
-                    mAutoDispatchException = e;
-                }
-                Log.d(TAG, "dispatched " + dispatchCount + " messages");
-                if (dispatchCount > 0) {
-                    return;
-                }
-                try {
-                    Thread.sleep(LOOP_SLEEP_TIME_MS);
-                } catch (InterruptedException e) {
-                    mAutoDispatchException = new IllegalStateException(
-                            "stopAutoDispatch called before any messages were dispatched.");
-                    return;
-                }
-            }
-            Log.e(TAG, "AutoDispatchThread did not dispatch any messages.");
-            mAutoDispatchException = new IllegalStateException(
-                    "MockLooper did not dispatch any messages before exiting.");
-        }
-
-        /**
-         * Method allowing the MockLooper to pass any exceptions thrown by the thread to be passed
-         * to the main thread.
-         *
-         * @return RuntimeException Exception created by stopping without dispatching a message
-         */
-        public RuntimeException getException() {
-            return mAutoDispatchException;
-        }
-    }
-
-    /**
-     * Create and start a new AutoDispatchThread if one is not already running.
-     */
-    public void startAutoDispatch() {
-        if (mAutoDispatchThread != null) {
-            throw new IllegalStateException(
-                    "startAutoDispatch called with the AutoDispatchThread already running.");
-        }
-        mAutoDispatchThread = new AutoDispatchThread();
-        mAutoDispatchThread.start();
-    }
-
-    /**
-     * If an AutoDispatchThread is currently running, stop and clean up.
-     */
-    public void stopAutoDispatch() {
-        if (mAutoDispatchThread != null) {
-            if (mAutoDispatchThread.isAlive()) {
-                mAutoDispatchThread.interrupt();
-            }
-            try {
-                mAutoDispatchThread.join();
-            } catch (InterruptedException e) {
-                // Catch exception from join.
-            }
-
-            RuntimeException e = mAutoDispatchThread.getException();
-            mAutoDispatchThread = null;
-            if (e != null) {
-                throw e;
-            }
-        } else {
-            // stopAutoDispatch was called when startAutoDispatch has not created a new thread.
-            throw new IllegalStateException(
-                    "stopAutoDispatch called without startAutoDispatch.");
-        }
-    }
-}
diff --git a/tests/wifitests/src/com/android/server/wifi/MockLooperTest.java b/tests/wifitests/src/com/android/server/wifi/MockLooperTest.java
deleted file mode 100644
index 74a73d6..0000000
--- a/tests/wifitests/src/com/android/server/wifi/MockLooperTest.java
+++ /dev/null
@@ -1,371 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wifi;
-
-import static org.hamcrest.core.IsEqual.equalTo;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ErrorCollector;
-import org.mockito.ArgumentCaptor;
-import org.mockito.InOrder;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Test MockLooperAbstractTime which provides control over "time". Note that
- * real-time is being used as well. Therefore small time increments are NOT
- * reliable. All tests are in "K" units (i.e. *1000).
- */
-
-@SmallTest
-public class MockLooperTest {
-    private MockLooper mMockLooper;
-    private Handler mHandler;
-    private Handler mHandlerSpy;
-
-    @Rule
-    public ErrorCollector collector = new ErrorCollector();
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
-        mMockLooper = new MockLooper();
-        mHandler = new Handler(mMockLooper.getLooper());
-        mHandlerSpy = spy(mHandler);
-    }
-
-    /**
-     * Basic test with no time stamps: dispatch 4 messages, check that all 4
-     * delivered (in correct order).
-     */
-    @Test
-    public void testNoTimeMovement() {
-        final int messageA = 1;
-        final int messageB = 2;
-        final int messageC = 3;
-
-        InOrder inOrder = inOrder(mHandlerSpy);
-        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
-
-        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
-        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
-        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB));
-        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageC));
-        mMockLooper.dispatchAll();
-
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what));
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("2: messageA", messageA, equalTo(messageCaptor.getValue().what));
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("3: messageB", messageB, equalTo(messageCaptor.getValue().what));
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("4: messageC", messageC, equalTo(messageCaptor.getValue().what));
-
-        inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class));
-    }
-
-    /**
-     * Test message sequence: A, B, C@5K, A@10K. Don't move time.
-     * <p>
-     * Expected: only get A, B
-     */
-    @Test
-    public void testDelayedDispatchNoTimeMove() {
-        final int messageA = 1;
-        final int messageB = 2;
-        final int messageC = 3;
-
-        InOrder inOrder = inOrder(mHandlerSpy);
-        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
-
-        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
-        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB));
-        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageC), 5000);
-        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageA), 10000);
-        mMockLooper.dispatchAll();
-
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what));
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("2: messageB", messageB, equalTo(messageCaptor.getValue().what));
-
-        inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class));
-    }
-
-    /**
-     * Test message sequence: A, B, C@5K, A@10K, Advance time by 5K.
-     * <p>
-     * Expected: only get A, B, C
-     */
-    @Test
-    public void testDelayedDispatchAdvanceTimeOnce() {
-        final int messageA = 1;
-        final int messageB = 2;
-        final int messageC = 3;
-
-        InOrder inOrder = inOrder(mHandlerSpy);
-        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
-
-        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
-        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB));
-        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageC), 5000);
-        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageA), 10000);
-        mMockLooper.moveTimeForward(5000);
-        mMockLooper.dispatchAll();
-
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what));
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("2: messageB", messageB, equalTo(messageCaptor.getValue().what));
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("3: messageC", messageC, equalTo(messageCaptor.getValue().what));
-
-        inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class));
-    }
-
-    /**
-     * Test message sequence: A, B, C@5K, Advance time by 4K, A@1K, B@2K Advance
-     * time by 1K.
-     * <p>
-     * Expected: get A, B, C, A
-     */
-    @Test
-    public void testDelayedDispatchAdvanceTimeTwice() {
-        final int messageA = 1;
-        final int messageB = 2;
-        final int messageC = 3;
-
-        InOrder inOrder = inOrder(mHandlerSpy);
-        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
-
-        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
-        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB));
-        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageC), 5000);
-        mMockLooper.moveTimeForward(4000);
-        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageA), 1000);
-        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageB), 2000);
-        mMockLooper.moveTimeForward(1000);
-        mMockLooper.dispatchAll();
-
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what));
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("2: messageB", messageB, equalTo(messageCaptor.getValue().what));
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("3: messageC", messageC, equalTo(messageCaptor.getValue().what));
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("4: messageA", messageA, equalTo(messageCaptor.getValue().what));
-
-        inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class));
-    }
-
-    /**
-     * Test message sequence: A, B, C@5K, Advance time by 4K, A@5K, B@2K Advance
-     * time by 3K.
-     * <p>
-     * Expected: get A, B, C, B
-     */
-    @Test
-    public void testDelayedDispatchReverseOrder() {
-        final int messageA = 1;
-        final int messageB = 2;
-        final int messageC = 3;
-
-        InOrder inOrder = inOrder(mHandlerSpy);
-        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
-
-        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
-        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB));
-        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageC), 5000);
-        mMockLooper.moveTimeForward(4000);
-        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageA), 5000);
-        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageB), 2000);
-        mMockLooper.moveTimeForward(3000);
-        mMockLooper.dispatchAll();
-
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what));
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("2: messageB", messageB, equalTo(messageCaptor.getValue().what));
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("3: messageC", messageC, equalTo(messageCaptor.getValue().what));
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("4: messageB", messageB, equalTo(messageCaptor.getValue().what));
-
-        inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class));
-    }
-
-    /**
-     * Test message sequence: A, B, C@5K, Advance time by 4K, dispatch all,
-     * A@5K, B@2K Advance time by 3K, dispatch all.
-     * <p>
-     * Expected: get A, B after first dispatch; then C, B after second dispatch
-     */
-    @Test
-    public void testDelayedDispatchAllMultipleTimes() {
-        final int messageA = 1;
-        final int messageB = 2;
-        final int messageC = 3;
-
-        InOrder inOrder = inOrder(mHandlerSpy);
-        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
-
-        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
-        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB));
-        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageC), 5000);
-        mMockLooper.moveTimeForward(4000);
-        mMockLooper.dispatchAll();
-
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what));
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("2: messageB", messageB, equalTo(messageCaptor.getValue().what));
-
-        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageA), 5000);
-        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageB), 2000);
-        mMockLooper.moveTimeForward(3000);
-        mMockLooper.dispatchAll();
-
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("3: messageC", messageC, equalTo(messageCaptor.getValue().what));
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("4: messageB", messageB, equalTo(messageCaptor.getValue().what));
-
-        inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class));
-    }
-
-    /**
-     * Test AutoDispatch for a single message.
-     * This test would ideally use the Channel sendMessageSynchronously.  At this time, the setup to
-     * get a working test channel is cumbersome.  Until this is fixed, we substitute with a
-     * sendMessage followed by a blocking call.  The main test thread blocks until the test handler
-     * receives the test message (messageA) and sets a boolean true.  Once the boolean is true, the
-     * main thread will exit the busy wait loop, stop autoDispatch and check the assert.
-     *
-     * Enable AutoDispatch, add message, block on message being handled and stop AutoDispatch.
-     * <p>
-     * Expected: handleMessage is called for messageA and stopAutoDispatch is called.
-     */
-    @Test
-    public void testAutoDispatchWithSingleMessage() {
-        final int mLoopSleepTimeMs = 5;
-
-        final int messageA = 1;
-
-        MockLooper mockLooper = new MockLooper();
-        class TestHandler extends Handler {
-            public volatile boolean handledMessage = false;
-            TestHandler(Looper looper) {
-                super(looper);
-            }
-
-            @Override
-            public void handleMessage(Message msg) {
-                if (msg.what == messageA) {
-                    handledMessage = true;
-                }
-            }
-        }
-
-        TestHandler testHandler = new TestHandler(mockLooper.getLooper());
-        mockLooper.startAutoDispatch();
-        testHandler.sendMessage(testHandler.obtainMessage(messageA));
-        while (!testHandler.handledMessage) {
-            // Block until message is handled
-            try {
-                Thread.sleep(mLoopSleepTimeMs);
-            } catch (InterruptedException e) {
-                // Interrupted while sleeping.
-            }
-        }
-        mockLooper.stopAutoDispatch();
-        assertTrue("TestHandler should have received messageA", testHandler.handledMessage);
-    }
-
-    /**
-     * Test starting AutoDispatch while already running throws IllegalStateException
-     * Enable AutoDispatch two times in a row.
-     * <p>
-     * Expected: catch IllegalStateException on second call.
-     */
-    @Test(expected = IllegalStateException.class)
-    public void testRepeatedStartAutoDispatchThrowsException() {
-        mMockLooper.startAutoDispatch();
-        mMockLooper.startAutoDispatch();
-    }
-
-    /**
-     * Test stopping AutoDispatch without previously starting throws IllegalStateException
-     * Stop AutoDispatch
-     * <p>
-     * Expected: catch IllegalStateException on second call.
-     */
-    @Test(expected = IllegalStateException.class)
-    public void testStopAutoDispatchWithoutStartThrowsException() {
-        mMockLooper.stopAutoDispatch();
-    }
-
-    /**
-     * Test AutoDispatch exits and does not dispatch a later message.
-     * Start and stop AutoDispatch then add a message.
-     * <p>
-     * Expected: After AutoDispatch is stopped, dispatchAll will return 1.
-     */
-    @Test
-    public void testAutoDispatchStopsCleanlyWithoutDispatchingAMessage() {
-        final int messageA = 1;
-
-        InOrder inOrder = inOrder(mHandlerSpy);
-        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
-
-        mMockLooper.startAutoDispatch();
-        try {
-            mMockLooper.stopAutoDispatch();
-        } catch (IllegalStateException e) {
-            //  Stopping without a dispatch will throw an exception.
-        }
-
-        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
-        assertEquals("One message should be dispatched", 1, mMockLooper.dispatchAll());
-    }
-
-    /**
-     * Test AutoDispatch throws an exception when no messages are dispatched.
-     * Start and stop AutoDispatch
-     * <p>
-     * Expected: Exception is thrown with the stopAutoDispatch call.
-     */
-    @Test(expected = IllegalStateException.class)
-    public void testAutoDispatchThrowsExceptionWhenNoMessagesDispatched() {
-        mMockLooper.startAutoDispatch();
-        mMockLooper.stopAutoDispatch();
-    }
-}
diff --git a/tests/wifitests/src/com/android/server/wifi/MockWifiMonitor.java b/tests/wifitests/src/com/android/server/wifi/MockWifiMonitor.java
index 8b68a11..6786504 100644
--- a/tests/wifitests/src/com/android/server/wifi/MockWifiMonitor.java
+++ b/tests/wifitests/src/com/android/server/wifi/MockWifiMonitor.java
@@ -23,12 +23,11 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 
+import android.app.test.MockAnswerUtil.AnswerWithArguments;
 import android.os.Handler;
 import android.os.Message;
 import android.util.SparseArray;
 
-import com.android.server.wifi.MockAnswerUtil.AnswerWithArguments;
-
 import java.lang.reflect.Field;
 import java.util.HashMap;
 import java.util.Map;
diff --git a/tests/wifitests/src/com/android/server/wifi/RttServiceTest.java b/tests/wifitests/src/com/android/server/wifi/RttServiceTest.java
index 177705c..157164a 100644
--- a/tests/wifitests/src/com/android/server/wifi/RttServiceTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/RttServiceTest.java
@@ -36,8 +36,11 @@
 import android.net.wifi.WifiManager;
 import android.os.Handler;
 import android.os.Message;
+import android.os.test.TestLooper;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.internal.util.test.BidirectionalAsyncChannel;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -60,7 +63,7 @@
     Context mContext;
     @Mock
     WifiNative mWifiNative;
-    MockLooper mLooper;
+    TestLooper mLooper;
 
     RttService.RttServiceImpl mRttServiceImpl;
     ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor = ArgumentCaptor
@@ -70,7 +73,7 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         TestUtil.installWlanWifiNative(mWifiNative);
-        mLooper = new MockLooper();
+        mLooper = new TestLooper();
         mRttServiceImpl = new RttService.RttServiceImpl(mContext, mLooper.getLooper());
         mRttServiceImpl.startService();
     }
diff --git a/tests/wifitests/src/com/android/server/wifi/ScanTestUtil.java b/tests/wifitests/src/com/android/server/wifi/ScanTestUtil.java
index 8228118..ce77f8a 100644
--- a/tests/wifitests/src/com/android/server/wifi/ScanTestUtil.java
+++ b/tests/wifitests/src/com/android/server/wifi/ScanTestUtil.java
@@ -233,6 +233,18 @@
         return createScanDatas(freqs, new int[freqs.length] /* defaults all 0 */);
     }
 
+    private static void assertScanResultEquals(
+            String prefix, ScanResult expected, ScanResult actual) {
+        assertEquals(prefix + "SSID", expected.SSID, actual.SSID);
+        assertEquals(prefix + "wifiSsid", expected.wifiSsid.toString(), actual.wifiSsid.toString());
+        assertEquals(prefix + "BSSID", expected.BSSID, actual.BSSID);
+        assertEquals(prefix + "capabilities", expected.capabilities, actual.capabilities);
+        assertEquals(prefix + "level", expected.level, actual.level);
+        assertEquals(prefix + "frequency", expected.frequency, actual.frequency);
+        assertEquals(prefix + "timestamp", expected.timestamp, actual.timestamp);
+        assertEquals(prefix + "seen", expected.seen, actual.seen);
+    }
+
     private static void assertScanResultsEquals(String prefix, ScanResult[] expected,
             ScanResult[] actual) {
         assertNotNull(prefix + "expected ScanResults was null", expected);
@@ -241,26 +253,18 @@
         for (int j = 0; j < expected.length; ++j) {
             ScanResult expectedResult = expected[j];
             ScanResult actualResult = actual[j];
-            assertEquals(prefix + "results[" + j + "].SSID",
-                    expectedResult.SSID, actualResult.SSID);
-            assertEquals(prefix + "results[" + j + "].wifiSsid",
-                    expectedResult.wifiSsid.toString(), actualResult.wifiSsid.toString());
-            assertEquals(prefix + "results[" + j + "].BSSID",
-                    expectedResult.BSSID, actualResult.BSSID);
-            assertEquals(prefix + "results[" + j + "].capabilities",
-                    expectedResult.capabilities, actualResult.capabilities);
-            assertEquals(prefix + "results[" + j + "].level",
-                    expectedResult.level, actualResult.level);
-            assertEquals(prefix + "results[" + j + "].frequency",
-                    expectedResult.frequency, actualResult.frequency);
-            assertEquals(prefix + "results[" + j + "].timestamp",
-                    expectedResult.timestamp, actualResult.timestamp);
-            assertEquals(prefix + "results[" + j + "].seen",
-                    expectedResult.seen, actualResult.seen);
+            assertScanResultEquals(prefix + "results[" + j + "]", actualResult, expectedResult);
         }
     }
 
     /**
+     * Asserts if the provided scan results are the same.
+     */
+    public static void assertScanResultEquals(ScanResult expected, ScanResult actual) {
+        assertScanResultEquals("", expected, actual);
+    }
+
+    /**
      * Asserts if the provided scan result arrays are the same.
      */
     public static void assertScanResultsEquals(ScanResult[] expected, ScanResult[] actual) {
diff --git a/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java b/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java
index b91df5a..5919156 100644
--- a/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java
@@ -24,14 +24,14 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.IntentFilter;
-import android.net.ConnectivityManager;
 import android.net.InterfaceConfiguration;
+import android.net.wifi.IApInterface;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
+import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
 import android.os.INetworkManagementService;
+import android.os.test.TestLooper;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import org.junit.Before;
@@ -54,18 +54,19 @@
     private static final String TEST_INTERFACE_NAME = "TestInterface";
     private static final String TEST_COUNTRY_CODE = "TestCountry";
     private static final Integer[] ALLOWED_2G_CHANNELS = {1, 2, 3, 4};
-    private static final String[] AVAILABLE_DEVICES = { TEST_INTERFACE_NAME };
 
     private final ArrayList<Integer> mAllowed2GChannels =
             new ArrayList<Integer>(Arrays.asList(ALLOWED_2G_CHANNELS));
 
-    MockLooper mLooper;
-    @Mock Context mContext;
+    TestLooper mLooper;
     @Mock WifiNative mWifiNative;
     @Mock INetworkManagementService mNmService;
-    @Mock ConnectivityManager mConnectivityManager;
     @Mock SoftApManager.Listener mListener;
     @Mock InterfaceConfiguration mInterfaceConfiguration;
+    @Mock IApInterface mApInterface;
+    @Mock IBinder mApInterfaceBinder;
+    final ArgumentCaptor<DeathRecipient> mDeathListenerCaptor =
+            ArgumentCaptor.forClass(DeathRecipient.class);
 
     SoftApManager mSoftApManager;
 
@@ -73,20 +74,20 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mLooper = new MockLooper();
+        mLooper = new TestLooper();
 
         when(mWifiNative.getInterfaceName()).thenReturn(TEST_INTERFACE_NAME);
         when(mNmService.getInterfaceConfig(TEST_INTERFACE_NAME))
                 .thenReturn(mInterfaceConfiguration);
-        when(mConnectivityManager.getTetherableWifiRegexs())
-                .thenReturn(AVAILABLE_DEVICES);
+        when(mApInterface.asBinder()).thenReturn(mApInterfaceBinder);
 
         mSoftApManager = new SoftApManager(mLooper.getLooper(),
                                            mWifiNative,
                                            mNmService,
                                            TEST_COUNTRY_CODE,
                                            mAllowed2GChannels,
-                                           mListener);
+                                           mListener,
+                                           mApInterface);
 
         mLooper.dispatchAll();
     }
@@ -128,9 +129,21 @@
         order.verify(mListener).onStateChanged(WifiManager.WIFI_AP_STATE_DISABLED, 0);
     }
 
+    @Test
+    public void handlesWificondInterfaceDeath() throws Exception {
+        startSoftApAndVerifyEnabled();
+
+        mDeathListenerCaptor.getValue().binderDied();
+        mLooper.dispatchAll();
+        InOrder order = inOrder(mListener);
+        order.verify(mListener).onStateChanged(WifiManager.WIFI_AP_STATE_DISABLING, 0);
+        order.verify(mListener).onStateChanged(WifiManager.WIFI_AP_STATE_FAILED,
+                WifiManager.SAP_START_FAILURE_GENERAL);
+    }
+
     /** Starts soft AP and verifies that it is enabled successfully. */
     protected void startSoftApAndVerifyEnabled() throws Exception {
-        InOrder order = inOrder(mListener);
+        InOrder order = inOrder(mListener, mApInterfaceBinder);
 
         /**
          *  Only test the default configuration. Testing for different configurations
@@ -146,6 +159,7 @@
         verify(mNmService).startAccessPoint(
                 any(WifiConfiguration.class), eq(TEST_INTERFACE_NAME));
         order.verify(mListener).onStateChanged(WifiManager.WIFI_AP_STATE_ENABLING, 0);
+        order.verify(mApInterfaceBinder).linkToDeath(mDeathListenerCaptor.capture(), eq(0));
         order.verify(mListener).onStateChanged(WifiManager.WIFI_AP_STATE_ENABLED, 0);
     }
 
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiBackupRestoreTest.java b/tests/wifitests/src/com/android/server/wifi/WifiBackupRestoreTest.java
new file mode 100644
index 0000000..c17f2ae
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/WifiBackupRestoreTest.java
@@ -0,0 +1,654 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+import android.net.wifi.WifiConfiguration;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.net.IpConfigStore;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.WifiBackupRestore}.
+ */
+@SmallTest
+public class WifiBackupRestoreTest {
+
+    private final WifiBackupRestore mWifiBackupRestore = new WifiBackupRestore();
+    private boolean mCheckDump = true;
+
+    @Before
+    public void setUp() throws Exception {
+        // Enable verbose logging before tests to check the backup data dumps.
+        mWifiBackupRestore.enableVerboseLogging(1);
+    }
+
+    @After
+    public void cleanUp() throws Exception {
+        if (mCheckDump) {
+            StringWriter stringWriter = new StringWriter();
+            mWifiBackupRestore.dump(
+                    new FileDescriptor(), new PrintWriter(stringWriter), new String[0]);
+            String dumpString = stringWriter.toString();
+            // Ensure that the SSID was dumped out.
+            assertTrue("Dump: " + dumpString,
+                    dumpString.contains(WifiConfigurationTestUtil.TEST_SSID));
+            // Ensure that the password wasn't dumped out.
+            assertFalse("Dump: " + dumpString,
+                    dumpString.contains(WifiConfigurationTestUtil.TEST_PSK));
+            assertFalse("Dump: " + dumpString,
+                    dumpString.contains(WifiConfigurationTestUtil.TEST_WEP_KEYS[0]));
+            assertFalse("Dump: " + dumpString,
+                    dumpString.contains(WifiConfigurationTestUtil.TEST_WEP_KEYS[1]));
+            assertFalse("Dump: " + dumpString,
+                    dumpString.contains(WifiConfigurationTestUtil.TEST_WEP_KEYS[2]));
+            assertFalse("Dump: " + dumpString,
+                    dumpString.contains(WifiConfigurationTestUtil.TEST_WEP_KEYS[3]));
+        }
+    }
+
+    /**
+     * Verify that a single open network configuration is serialized & deserialized correctly.
+     */
+    @Test
+    public void testSingleOpenNetworkBackupRestore() {
+        List<WifiConfiguration> configurations = new ArrayList<>();
+        configurations.add(WifiConfigurationTestUtil.createOpenNetwork());
+
+        byte[] backupData = mWifiBackupRestore.retrieveBackupDataFromConfigurations(configurations);
+        List<WifiConfiguration> retrievedConfigurations =
+                mWifiBackupRestore.retrieveConfigurationsFromBackupData(backupData);
+        WifiConfigurationTestUtil.assertConfigurationsEqualForBackup(
+                configurations, retrievedConfigurations);
+    }
+
+    /**
+     * Verify that a single open hidden network configuration is serialized & deserialized
+     * correctly.
+     */
+    @Test
+    public void testSingleOpenHiddenNetworkBackupRestore() {
+        List<WifiConfiguration> configurations = new ArrayList<>();
+        configurations.add(WifiConfigurationTestUtil.createOpenHiddenNetwork());
+
+        byte[] backupData = mWifiBackupRestore.retrieveBackupDataFromConfigurations(configurations);
+        List<WifiConfiguration> retrievedConfigurations =
+                mWifiBackupRestore.retrieveConfigurationsFromBackupData(backupData);
+        WifiConfigurationTestUtil.assertConfigurationsEqualForBackup(
+                configurations, retrievedConfigurations);
+    }
+
+    /**
+     * Verify that a single PSK network configuration is serialized & deserialized correctly.
+     */
+    @Test
+    public void testSinglePskNetworkBackupRestore() {
+        List<WifiConfiguration> configurations = new ArrayList<>();
+        configurations.add(WifiConfigurationTestUtil.createPskNetwork());
+
+        byte[] backupData = mWifiBackupRestore.retrieveBackupDataFromConfigurations(configurations);
+        List<WifiConfiguration> retrievedConfigurations =
+                mWifiBackupRestore.retrieveConfigurationsFromBackupData(backupData);
+        WifiConfigurationTestUtil.assertConfigurationsEqualForBackup(
+                configurations, retrievedConfigurations);
+    }
+
+    /**
+     * Verify that a single PSK hidden network configuration is serialized & deserialized correctly.
+     */
+    @Test
+    public void testSinglePskHiddenNetworkBackupRestore() {
+        List<WifiConfiguration> configurations = new ArrayList<>();
+        configurations.add(WifiConfigurationTestUtil.createPskHiddenNetwork());
+
+        byte[] backupData = mWifiBackupRestore.retrieveBackupDataFromConfigurations(configurations);
+        List<WifiConfiguration> retrievedConfigurations =
+                mWifiBackupRestore.retrieveConfigurationsFromBackupData(backupData);
+        WifiConfigurationTestUtil.assertConfigurationsEqualForBackup(
+                configurations, retrievedConfigurations);
+    }
+
+    /**
+     * Verify that a single WEP network configuration is serialized & deserialized correctly.
+     */
+    @Test
+    public void testSingleWepNetworkBackupRestore() {
+        List<WifiConfiguration> configurations = new ArrayList<>();
+        configurations.add(WifiConfigurationTestUtil.createWepNetwork());
+
+        byte[] backupData = mWifiBackupRestore.retrieveBackupDataFromConfigurations(configurations);
+        List<WifiConfiguration> retrievedConfigurations =
+                mWifiBackupRestore.retrieveConfigurationsFromBackupData(backupData);
+        WifiConfigurationTestUtil.assertConfigurationsEqualForBackup(
+                configurations, retrievedConfigurations);
+    }
+
+    /**
+     * Verify that a single WEP network configuration with only 1 key is serialized & deserialized
+     * correctly.
+     */
+    @Test
+    public void testSingleWepNetworkWithSingleKeyBackupRestore() {
+        List<WifiConfiguration> configurations = new ArrayList<>();
+        configurations.add(WifiConfigurationTestUtil.createWepNetworkWithSingleKey());
+
+        byte[] backupData = mWifiBackupRestore.retrieveBackupDataFromConfigurations(configurations);
+        List<WifiConfiguration> retrievedConfigurations =
+                mWifiBackupRestore.retrieveConfigurationsFromBackupData(backupData);
+        WifiConfigurationTestUtil.assertConfigurationsEqualForBackup(
+                configurations, retrievedConfigurations);
+    }
+
+    /**
+     * Verify that a single enterprise network configuration is not serialized.
+     */
+    @Test
+    public void testSingleEnterpriseNetworkNotBackupRestore() {
+        List<WifiConfiguration> configurations = new ArrayList<>();
+        configurations.add(WifiConfigurationTestUtil.createEapNetwork());
+
+        byte[] backupData = mWifiBackupRestore.retrieveBackupDataFromConfigurations(configurations);
+        List<WifiConfiguration> retrievedConfigurations =
+                mWifiBackupRestore.retrieveConfigurationsFromBackupData(backupData);
+        assertTrue(retrievedConfigurations.isEmpty());
+        // No valid data to check in dump.
+        mCheckDump = false;
+    }
+
+    /**
+     * Verify that a single PSK network configuration with static ip/proxy settings is serialized &
+     * deserialized correctly.
+     */
+    @Test
+    public void testSinglePskNetworkWithStaticIpAndStaticProxyBackupRestore() {
+        List<WifiConfiguration> configurations = new ArrayList<>();
+        WifiConfiguration pskNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        pskNetwork.setIpConfiguration(
+                WifiConfigurationTestUtil.createStaticIpConfigurationWithStaticProxy());
+        configurations.add(pskNetwork);
+
+        byte[] backupData = mWifiBackupRestore.retrieveBackupDataFromConfigurations(configurations);
+        List<WifiConfiguration> retrievedConfigurations =
+                mWifiBackupRestore.retrieveConfigurationsFromBackupData(backupData);
+        WifiConfigurationTestUtil.assertConfigurationsEqualForBackup(
+                configurations, retrievedConfigurations);
+    }
+
+    /**
+     * Verify that a single PSK network configuration with static ip & PAC proxy settings is
+     * serialized & deserialized correctly.
+     */
+    @Test
+    public void testSinglePskNetworkWithStaticIpAndPACProxyBackupRestore() {
+        List<WifiConfiguration> configurations = new ArrayList<>();
+        WifiConfiguration pskNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        pskNetwork.setIpConfiguration(
+                WifiConfigurationTestUtil.createStaticIpConfigurationWithPacProxy());
+        configurations.add(pskNetwork);
+
+        byte[] backupData = mWifiBackupRestore.retrieveBackupDataFromConfigurations(configurations);
+        List<WifiConfiguration> retrievedConfigurations =
+                mWifiBackupRestore.retrieveConfigurationsFromBackupData(backupData);
+        WifiConfigurationTestUtil.assertConfigurationsEqualForBackup(
+                configurations, retrievedConfigurations);
+    }
+
+    /**
+     * Verify that a single PSK network configuration with DHCP ip & PAC proxy settings is
+     * serialized & deserialized correctly.
+     */
+    @Test
+    public void testSinglePskNetworkWithDHCPIpAndPACProxyBackupRestore() {
+        List<WifiConfiguration> configurations = new ArrayList<>();
+        WifiConfiguration pskNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        pskNetwork.setIpConfiguration(
+                WifiConfigurationTestUtil.createDHCPIpConfigurationWithPacProxy());
+        configurations.add(pskNetwork);
+
+        byte[] backupData = mWifiBackupRestore.retrieveBackupDataFromConfigurations(configurations);
+        List<WifiConfiguration> retrievedConfigurations =
+                mWifiBackupRestore.retrieveConfigurationsFromBackupData(backupData);
+        WifiConfigurationTestUtil.assertConfigurationsEqualForBackup(
+                configurations, retrievedConfigurations);
+    }
+
+    /**
+     * Verify that a single PSK network configuration with partial static ip settings is serialized
+     * & deserialized correctly.
+     */
+    @Test
+    public void testSinglePskNetworkWithPartialStaticIpBackupRestore() {
+        List<WifiConfiguration> configurations = new ArrayList<>();
+        WifiConfiguration pskNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        pskNetwork.setIpConfiguration(
+                WifiConfigurationTestUtil.createPartialStaticIpConfigurationWithPacProxy());
+        configurations.add(pskNetwork);
+
+        byte[] backupData = mWifiBackupRestore.retrieveBackupDataFromConfigurations(configurations);
+        List<WifiConfiguration> retrievedConfigurations =
+                mWifiBackupRestore.retrieveConfigurationsFromBackupData(backupData);
+        WifiConfigurationTestUtil.assertConfigurationsEqualForBackup(
+                configurations, retrievedConfigurations);
+    }
+
+    /**
+     * Verify that multiple networks of different types are serialized and deserialized correctly.
+     */
+    @Test
+    public void testMultipleNetworksAllBackupRestore() {
+        List<WifiConfiguration> configurations = new ArrayList<>();
+        configurations.add(WifiConfigurationTestUtil.createWepNetwork());
+        configurations.add(WifiConfigurationTestUtil.createWepNetwork());
+        configurations.add(WifiConfigurationTestUtil.createPskNetwork());
+        configurations.add(WifiConfigurationTestUtil.createOpenNetwork());
+
+        byte[] backupData = mWifiBackupRestore.retrieveBackupDataFromConfigurations(configurations);
+        List<WifiConfiguration> retrievedConfigurations =
+                mWifiBackupRestore.retrieveConfigurationsFromBackupData(backupData);
+        WifiConfigurationTestUtil.assertConfigurationsEqualForBackup(
+                configurations, retrievedConfigurations);
+    }
+
+    /**
+     * Verify that multiple networks of different types except enterprise ones are serialized and
+     * deserialized correctly
+     */
+    @Test
+    public void testMultipleNetworksNonEnterpriseBackupRestore() {
+        List<WifiConfiguration> configurations = new ArrayList<>();
+        List<WifiConfiguration> expectedConfigurations = new ArrayList<>();
+
+        WifiConfiguration wepNetwork = WifiConfigurationTestUtil.createWepNetwork();
+        configurations.add(wepNetwork);
+        expectedConfigurations.add(wepNetwork);
+
+        configurations.add(WifiConfigurationTestUtil.createEapNetwork());
+
+        WifiConfiguration pskNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        configurations.add(pskNetwork);
+        expectedConfigurations.add(pskNetwork);
+
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        configurations.add(openNetwork);
+        expectedConfigurations.add(openNetwork);
+
+        byte[] backupData = mWifiBackupRestore.retrieveBackupDataFromConfigurations(configurations);
+        List<WifiConfiguration> retrievedConfigurations =
+                mWifiBackupRestore.retrieveConfigurationsFromBackupData(backupData);
+        WifiConfigurationTestUtil.assertConfigurationsEqualForBackup(
+                expectedConfigurations, retrievedConfigurations);
+    }
+
+    /**
+     * Verify that multiple networks with different credential types and IpConfiguration types are
+     * serialized and deserialized correctly.
+     */
+    @Test
+    public void testMultipleNetworksWithDifferentIpConfigurationsAllBackupRestore() {
+        List<WifiConfiguration> configurations = new ArrayList<>();
+
+        WifiConfiguration wepNetwork = WifiConfigurationTestUtil.createWepNetwork();
+        wepNetwork.setIpConfiguration(
+                WifiConfigurationTestUtil.createDHCPIpConfigurationWithPacProxy());
+        configurations.add(wepNetwork);
+
+        WifiConfiguration pskNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        pskNetwork.setIpConfiguration(
+                WifiConfigurationTestUtil.createStaticIpConfigurationWithPacProxy());
+        configurations.add(pskNetwork);
+
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        openNetwork.setIpConfiguration(
+                WifiConfigurationTestUtil.createStaticIpConfigurationWithStaticProxy());
+        configurations.add(openNetwork);
+
+        byte[] backupData = mWifiBackupRestore.retrieveBackupDataFromConfigurations(configurations);
+        List<WifiConfiguration> retrievedConfigurations =
+                mWifiBackupRestore.retrieveConfigurationsFromBackupData(backupData);
+        WifiConfigurationTestUtil.assertConfigurationsEqualForBackup(
+                configurations, retrievedConfigurations);
+    }
+
+    /**
+     * Verify that a single open network configuration is serialized & deserialized correctly from
+     * old backups.
+     */
+    @Test
+    public void testSingleOpenNetworkSupplicantBackupRestore() {
+        List<WifiConfiguration> configurations = new ArrayList<>();
+        configurations.add(WifiConfigurationTestUtil.createOpenNetwork());
+
+        byte[] supplicantData = createWpaSupplicantConfBackupData(configurations);
+        byte[] ipConfigData = createIpConfBackupData(configurations);
+        List<WifiConfiguration> retrievedConfigurations =
+                mWifiBackupRestore.retrieveConfigurationsFromSupplicantBackupData(
+                        supplicantData, ipConfigData);
+        WifiConfigurationTestUtil.assertConfigurationsEqualForBackup(
+                configurations, retrievedConfigurations);
+    }
+
+    /**
+     * Verify that a single open hidden network configuration is serialized & deserialized
+     * correctly from old backups.
+     */
+    @Test
+    public void testSingleOpenHiddenNetworkSupplicantBackupRestore() {
+        List<WifiConfiguration> configurations = new ArrayList<>();
+        configurations.add(WifiConfigurationTestUtil.createOpenHiddenNetwork());
+
+        byte[] supplicantData = createWpaSupplicantConfBackupData(configurations);
+        byte[] ipConfigData = createIpConfBackupData(configurations);
+        List<WifiConfiguration> retrievedConfigurations =
+                mWifiBackupRestore.retrieveConfigurationsFromSupplicantBackupData(
+                        supplicantData, ipConfigData);
+        WifiConfigurationTestUtil.assertConfigurationsEqualForBackup(
+                configurations, retrievedConfigurations);
+    }
+
+    /**
+     * Verify that a single PSK network configuration is serialized & deserialized correctly from
+     * old backups.
+     */
+    @Test
+    public void testSinglePskNetworkSupplicantBackupRestore() {
+        List<WifiConfiguration> configurations = new ArrayList<>();
+        configurations.add(WifiConfigurationTestUtil.createPskNetwork());
+
+        byte[] supplicantData = createWpaSupplicantConfBackupData(configurations);
+        byte[] ipConfigData = createIpConfBackupData(configurations);
+        List<WifiConfiguration> retrievedConfigurations =
+                mWifiBackupRestore.retrieveConfigurationsFromSupplicantBackupData(
+                        supplicantData, ipConfigData);
+        WifiConfigurationTestUtil.assertConfigurationsEqualForBackup(
+                configurations, retrievedConfigurations);
+    }
+
+    /**
+     * Verify that a single PSK hidden network configuration is serialized & deserialized correctly
+     * from old backups.
+     */
+    @Test
+    public void testSinglePskHiddenNetworkSupplicantBackupRestore() {
+        List<WifiConfiguration> configurations = new ArrayList<>();
+        configurations.add(WifiConfigurationTestUtil.createPskHiddenNetwork());
+
+        byte[] supplicantData = createWpaSupplicantConfBackupData(configurations);
+        byte[] ipConfigData = createIpConfBackupData(configurations);
+        List<WifiConfiguration> retrievedConfigurations =
+                mWifiBackupRestore.retrieveConfigurationsFromSupplicantBackupData(
+                        supplicantData, ipConfigData);
+        WifiConfigurationTestUtil.assertConfigurationsEqualForBackup(
+                configurations, retrievedConfigurations);
+    }
+
+    /**
+     * Verify that a single WEP network configuration is serialized & deserialized correctly from
+     * old backups.
+     */
+    @Test
+    public void testSingleWepNetworkSupplicantBackupRestore() {
+        List<WifiConfiguration> configurations = new ArrayList<>();
+        configurations.add(WifiConfigurationTestUtil.createWepNetwork());
+
+        byte[] supplicantData = createWpaSupplicantConfBackupData(configurations);
+        byte[] ipConfigData = createIpConfBackupData(configurations);
+        List<WifiConfiguration> retrievedConfigurations =
+                mWifiBackupRestore.retrieveConfigurationsFromSupplicantBackupData(
+                        supplicantData, ipConfigData);
+        WifiConfigurationTestUtil.assertConfigurationsEqualForBackup(
+                configurations, retrievedConfigurations);
+    }
+
+    /**
+     * Verify that a single WEP network configuration with only 1 key is serialized & deserialized
+     * correctly from old backups.
+     */
+    @Test
+    public void testSingleWepNetworkWithSingleKeySupplicantBackupRestore() {
+        List<WifiConfiguration> configurations = new ArrayList<>();
+        configurations.add(WifiConfigurationTestUtil.createWepNetworkWithSingleKey());
+
+        byte[] supplicantData = createWpaSupplicantConfBackupData(configurations);
+        byte[] ipConfigData = createIpConfBackupData(configurations);
+        List<WifiConfiguration> retrievedConfigurations =
+                mWifiBackupRestore.retrieveConfigurationsFromSupplicantBackupData(
+                        supplicantData, ipConfigData);
+        WifiConfigurationTestUtil.assertConfigurationsEqualForBackup(
+                configurations, retrievedConfigurations);
+    }
+
+    /**
+     * Verify that a single enterprise network configuration is not serialized from old backups.
+     */
+    @Test
+    public void testSingleEnterpriseNetworkNotSupplicantBackupRestore() {
+        List<WifiConfiguration> configurations = new ArrayList<>();
+        configurations.add(WifiConfigurationTestUtil.createEapNetwork());
+
+        byte[] supplicantData = createWpaSupplicantConfBackupData(configurations);
+        byte[] ipConfigData = createIpConfBackupData(configurations);
+        List<WifiConfiguration> retrievedConfigurations =
+                mWifiBackupRestore.retrieveConfigurationsFromSupplicantBackupData(
+                        supplicantData, ipConfigData);
+        assertTrue(retrievedConfigurations.isEmpty());
+    }
+
+    /**
+     * Verify that multiple networks with different credential types and IpConfiguration types are
+     * serialized and deserialized correctly from old backups
+     */
+    @Test
+    public void testMultipleNetworksWithDifferentIpConfigurationsAllSupplicantBackupRestore() {
+        List<WifiConfiguration> configurations = new ArrayList<>();
+
+        WifiConfiguration wepNetwork = WifiConfigurationTestUtil.createWepNetwork();
+        wepNetwork.setIpConfiguration(
+                WifiConfigurationTestUtil.createDHCPIpConfigurationWithPacProxy());
+        configurations.add(wepNetwork);
+
+        WifiConfiguration pskNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        pskNetwork.setIpConfiguration(
+                WifiConfigurationTestUtil.createStaticIpConfigurationWithPacProxy());
+        configurations.add(pskNetwork);
+
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        openNetwork.setIpConfiguration(
+                WifiConfigurationTestUtil.createStaticIpConfigurationWithStaticProxy());
+        configurations.add(openNetwork);
+
+        byte[] supplicantData = createWpaSupplicantConfBackupData(configurations);
+        byte[] ipConfigData = createIpConfBackupData(configurations);
+        List<WifiConfiguration> retrievedConfigurations =
+                mWifiBackupRestore.retrieveConfigurationsFromSupplicantBackupData(
+                        supplicantData, ipConfigData);
+        WifiConfigurationTestUtil.assertConfigurationsEqualForBackup(
+                configurations, retrievedConfigurations);
+    }
+
+    /**
+     * Verify that a single open network configuration is serialized & deserialized correctly from
+     * old backups with no ipconfig data.
+     */
+    @Test
+    public void testSingleOpenNetworkSupplicantBackupRestoreWithNoIpConfigData() {
+        List<WifiConfiguration> configurations = new ArrayList<>();
+        configurations.add(WifiConfigurationTestUtil.createOpenNetwork());
+
+        byte[] supplicantData = createWpaSupplicantConfBackupData(configurations);
+        List<WifiConfiguration> retrievedConfigurations =
+                mWifiBackupRestore.retrieveConfigurationsFromSupplicantBackupData(
+                        supplicantData, null);
+        WifiConfigurationTestUtil.assertConfigurationsEqualForBackup(
+                configurations, retrievedConfigurations);
+    }
+
+    /**
+     * Verify that multiple networks with different credential types are serialized and
+     * deserialized correctly from old backups with no ipconfig data.
+     */
+    @Test
+    public void testMultipleNetworksAllSupplicantBackupRestoreWithNoIpConfigData() {
+        List<WifiConfiguration> configurations = new ArrayList<>();
+
+        WifiConfiguration wepNetwork = WifiConfigurationTestUtil.createWepNetwork();
+        configurations.add(wepNetwork);
+
+        WifiConfiguration pskNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        configurations.add(pskNetwork);
+
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        configurations.add(openNetwork);
+
+        byte[] supplicantData = createWpaSupplicantConfBackupData(configurations);
+        List<WifiConfiguration> retrievedConfigurations =
+                mWifiBackupRestore.retrieveConfigurationsFromSupplicantBackupData(
+                        supplicantData, null);
+        WifiConfigurationTestUtil.assertConfigurationsEqualForBackup(
+                configurations, retrievedConfigurations);
+    }
+
+    /**
+     * Verify that any corrupted data provided by Backup/Restore is ignored correctly.
+     */
+    @Test
+    public void testCorruptBackupRestore() {
+        Random random = new Random();
+        byte[] backupData = new byte[100];
+        random.nextBytes(backupData);
+
+        List<WifiConfiguration> retrievedConfigurations =
+                mWifiBackupRestore.retrieveConfigurationsFromBackupData(backupData);
+        assertNull(retrievedConfigurations);
+        // No valid data to check in dump.
+        mCheckDump = false;
+    }
+
+    /**
+     * Helper method to write a list of networks in wpa_supplicant.conf format to the output stream.
+     */
+    private byte[] createWpaSupplicantConfBackupData(List<WifiConfiguration> configurations) {
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        OutputStreamWriter out = new OutputStreamWriter(bos);
+        try {
+            for (WifiConfiguration configuration : configurations) {
+                writeConfigurationToWpaSupplicantConf(out, configuration);
+            }
+            out.flush();
+            return bos.toByteArray();
+        } catch (IOException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Helper method to write a network in wpa_supplicant.conf format to the output stream.
+     * This was created using a sample wpa_supplicant.conf file. Using the raw key strings here
+     * (instead of consts in WifiBackupRestore).
+     */
+    private void writeConfigurationToWpaSupplicantConf(
+            OutputStreamWriter out, WifiConfiguration configuration)
+            throws IOException {
+        out.write("network={\n");
+        out.write("        " + "ssid=" + configuration.SSID + "\n");
+        String allowedKeyManagement = "";
+        if (configuration.hiddenSSID) {
+            out.write("        " + "scan_ssid=1" + "\n");
+        }
+        if (configuration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)) {
+            allowedKeyManagement += "NONE";
+        }
+        if (configuration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
+            allowedKeyManagement += "WPA-PSK ";
+        }
+        if (configuration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP)) {
+            allowedKeyManagement += "WPA-EAP ";
+        }
+        if (configuration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X)) {
+            allowedKeyManagement += "IEEE8021X ";
+        }
+        out.write("        " + "key_mgmt=" + allowedKeyManagement + "\n");
+        if (configuration.preSharedKey != null) {
+            out.write("        " + "psk=" + configuration.preSharedKey + "\n");
+        }
+        if (configuration.wepKeys[0] != null) {
+            out.write("        " + "wep_key0=" + configuration.wepKeys[0] + "\n");
+        }
+        if (configuration.wepKeys[1] != null) {
+            out.write("        " + "wep_key1=" + configuration.wepKeys[1] + "\n");
+        }
+        if (configuration.wepKeys[2] != null) {
+            out.write("        " + "wep_key2=" + configuration.wepKeys[2] + "\n");
+        }
+        if (configuration.wepKeys[3] != null) {
+            out.write("        " + "wep_key3=" + configuration.wepKeys[3] + "\n");
+        }
+        if (configuration.wepKeys[0] != null || configuration.wepKeys[1] != null
+                || configuration.wepKeys[2] != null || configuration.wepKeys[3] != null) {
+            out.write("        " + "wep_tx_keyidx=" + configuration.wepTxKeyIndex + "\n");
+        }
+        Map<String, String> extras = new HashMap<>();
+        extras.put(WifiSupplicantControl.ID_STRING_KEY_CONFIG_KEY, configuration.configKey());
+        extras.put(WifiSupplicantControl.ID_STRING_KEY_CREATOR_UID,
+                Integer.toString(configuration.creatorUid));
+        String idString = WifiNative.createNetworkExtra(extras);
+        if (idString != null) {
+            idString = "\"" + idString + "\"";
+            out.write("        " + "id_str=" + idString + "\n");
+        }
+        out.write("}\n");
+        out.write("\n");
+    }
+
+    /**
+     * Helper method to write a list of networks in ipconfig.txt format to the output stream.
+     */
+    private byte[] createIpConfBackupData(List<WifiConfiguration> configurations) {
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        DataOutputStream out = new DataOutputStream(bos);
+        try {
+            // write version first.
+            out.writeInt(2);
+            for (WifiConfiguration configuration : configurations) {
+                IpConfigStore.writeConfig(out, configuration.configKey().hashCode(),
+                        configuration.getIpConfiguration());
+            }
+            out.flush();
+            return bos.toByteArray();
+        } catch (IOException e) {
+            return null;
+        }
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerNewTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerNewTest.java
new file mode 100644
index 0000000..2ff6de2
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerNewTest.java
@@ -0,0 +1,1361 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+import android.app.test.MockAnswerUtil.AnswerWithArguments;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.IpConfiguration;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
+import android.net.wifi.WifiEnterpriseConfig;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiSsid;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.TextUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.WifiConfigManagerNew}.
+ */
+@SmallTest
+public class WifiConfigManagerNewTest {
+
+    private static final String TEST_BSSID = "0a:08:5c:67:89:00";
+    private static final long TEST_WALLCLOCK_CREATION_TIME_MILLIS = 9845637;
+    private static final long TEST_WALLCLOCK_UPDATE_TIME_MILLIS = 75455637;
+    private static final long TEST_ELAPSED_UPDATE_NETWORK_SELECTION_TIME_MILLIS = 29457631;
+    private static final int TEST_CREATOR_UID = 5;
+    private static final int TEST_UPDATE_UID = 4;
+    private static final String TEST_CREATOR_NAME = "com.wificonfigmanagerNew.creator";
+    private static final String TEST_UPDATE_NAME = "com.wificonfigmanagerNew.update";
+
+    @Mock private Context mContext;
+    @Mock private FrameworkFacade mFrameworkFacade;
+    @Mock private Clock mClock;
+    @Mock private UserManager mUserManager;
+    @Mock private WifiKeyStore mWifiKeyStore;
+    @Mock private WifiConfigStoreNew mWifiConfigStore;
+    @Mock private PackageManager mPackageManager;
+
+    private InOrder mContextConfigStoreMockOrder;
+
+    private WifiConfigManagerNew mWifiConfigManager;
+
+    /**
+     * Setup the mocks and an instance of WifiConfigManagerNew before each test.
+     */
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        // Set up the inorder for verifications. This is needed to verify that the broadcasts,
+        // store writes for network updates followed by network additions are in the expected order.
+        mContextConfigStoreMockOrder = inOrder(mContext, mWifiConfigStore);
+
+        // Set up the package name stuff & permission override.
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        doAnswer(new AnswerWithArguments() {
+            public String answer(int uid) throws Exception {
+                if (uid == TEST_CREATOR_UID) {
+                    return TEST_CREATOR_NAME;
+                } else if (uid == TEST_UPDATE_UID) {
+                    return TEST_UPDATE_NAME;
+                }
+                fail("Unexpected UID: " + uid);
+                return "";
+            }
+        }).when(mPackageManager).getNameForUid(anyInt());
+
+        // Both the UID's in the test have the configuration override permission granted by
+        // default. This maybe modified for particular tests if needed.
+        doAnswer(new AnswerWithArguments() {
+            public int answer(String permName, int uid) throws Exception {
+                if (uid == TEST_CREATOR_UID || uid == TEST_UPDATE_UID) {
+                    return PackageManager.PERMISSION_GRANTED;
+                }
+                return PackageManager.PERMISSION_DENIED;
+            }
+        }).when(mFrameworkFacade).checkUidPermission(anyString(), anyInt());
+
+        when(mWifiKeyStore
+                .updateNetworkKeys(any(WifiConfiguration.class), any(WifiConfiguration.class)))
+                .thenReturn(true);
+
+        mWifiConfigManager =
+                new WifiConfigManagerNew(
+                        mContext, mFrameworkFacade, mClock, mUserManager, mWifiKeyStore,
+                        mWifiConfigStore);
+        mWifiConfigManager.enableVerboseLogging(1);
+    }
+
+    /**
+     * Verifies the addition of a single network using
+     * {@link WifiConfigManagerNew#addOrUpdateNetwork(WifiConfiguration, int)}
+     */
+    @Test
+    public void testAddSingleOpenNetwork() {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        List<WifiConfiguration> networks = new ArrayList<>();
+        networks.add(openNetwork);
+
+        verifyAddNetworkToWifiConfigManager(openNetwork);
+
+        List<WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworksWithPasswords();
+        WifiConfigurationTestUtil.assertConfigurationsEqualForConfigManagerAddOrUpdate(
+                networks, retrievedNetworks);
+    }
+
+    /**
+     * Verifies the modification of a single network using
+     * {@link WifiConfigManagerNew#addOrUpdateNetwork(WifiConfiguration, int)}
+     */
+    @Test
+    public void testUpdateSingleOpenNetwork() {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        List<WifiConfiguration> networks = new ArrayList<>();
+        networks.add(openNetwork);
+
+        verifyAddNetworkToWifiConfigManager(openNetwork);
+
+        // Now change BSSID for the network.
+        assertAndSetNetworkBSSID(openNetwork, TEST_BSSID);
+        verifyUpdateNetworkToWifiConfigManagerWithoutIpChange(openNetwork);
+
+        // Now verify that the modification has been effective.
+        List<WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworksWithPasswords();
+        WifiConfigurationTestUtil.assertConfigurationsEqualForConfigManagerAddOrUpdate(
+                networks, retrievedNetworks);
+    }
+
+    /**
+     * Verifies the addition of a single ephemeral network using
+     * {@link WifiConfigManagerNew#addOrUpdateNetwork(WifiConfiguration, int)} and verifies that
+     * the {@link WifiConfigManagerNew#getSavedNetworks()} does not return this network.
+     */
+    @Test
+    public void testAddSingleEphemeralNetwork() {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        openNetwork.ephemeral = true;
+        List<WifiConfiguration> networks = new ArrayList<>();
+        networks.add(openNetwork);
+
+        verifyAddEphemeralNetworkToWifiConfigManager(openNetwork);
+
+        List<WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworksWithPasswords();
+        WifiConfigurationTestUtil.assertConfigurationsEqualForConfigManagerAddOrUpdate(
+                networks, retrievedNetworks);
+
+        // Ensure that this is not returned in the saved network list.
+        assertTrue(mWifiConfigManager.getSavedNetworks().isEmpty());
+    }
+
+    /**
+     * Verifies that the modification of a single open network using
+     * {@link WifiConfigManagerNew#addOrUpdateNetwork(WifiConfiguration, int)} with a UID which
+     * has no permission to modify the network fails.
+     */
+    @Test
+    public void testUpdateSingleOpenNetworkFailedDueToPermissionDenied() throws Exception {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        List<WifiConfiguration> networks = new ArrayList<>();
+        networks.add(openNetwork);
+
+        verifyAddNetworkToWifiConfigManager(openNetwork);
+
+        // Now change BSSID of the network.
+        assertAndSetNetworkBSSID(openNetwork, TEST_BSSID);
+
+        // Deny permission for |UPDATE_UID|.
+        doAnswer(new AnswerWithArguments() {
+            public int answer(String permName, int uid) throws Exception {
+                if (uid == TEST_CREATOR_UID) {
+                    return PackageManager.PERMISSION_GRANTED;
+                }
+                return PackageManager.PERMISSION_DENIED;
+            }
+        }).when(mFrameworkFacade).checkUidPermission(anyString(), anyInt());
+
+        // Update the same configuration and ensure that the operation failed.
+        NetworkUpdateResult result = updateNetworkToWifiConfigManager(openNetwork);
+        assertTrue(result.getNetworkId() == WifiConfiguration.INVALID_NETWORK_ID);
+    }
+
+    /**
+     * Verifies that the modification of a single open network using
+     * {@link WifiConfigManagerNew#addOrUpdateNetwork(WifiConfiguration, int)} with the creator UID
+     * should always succeed.
+     */
+    @Test
+    public void testUpdateSingleOpenNetworkSuccessWithCreatorUID() throws Exception {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        List<WifiConfiguration> networks = new ArrayList<>();
+        networks.add(openNetwork);
+
+        verifyAddNetworkToWifiConfigManager(openNetwork);
+
+        // Now change BSSID of the network.
+        assertAndSetNetworkBSSID(openNetwork, TEST_BSSID);
+
+        // Deny permission for all UIDs.
+        doAnswer(new AnswerWithArguments() {
+            public int answer(String permName, int uid) throws Exception {
+                return PackageManager.PERMISSION_DENIED;
+            }
+        }).when(mFrameworkFacade).checkUidPermission(anyString(), anyInt());
+
+        // Update the same configuration using the creator UID.
+        NetworkUpdateResult result =
+                mWifiConfigManager.addOrUpdateNetwork(openNetwork, TEST_CREATOR_UID);
+        assertTrue(result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID);
+
+        // Now verify that the modification has been effective.
+        List<WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworksWithPasswords();
+        WifiConfigurationTestUtil.assertConfigurationsEqualForConfigManagerAddOrUpdate(
+                networks, retrievedNetworks);
+    }
+
+    /**
+     * Verifies the addition of a single PSK network using
+     * {@link WifiConfigManagerNew#addOrUpdateNetwork(WifiConfiguration, int)} and verifies that
+     * {@link WifiConfigManagerNew#getSavedNetworks()} masks the password.
+     */
+    @Test
+    public void testAddSinglePskNetwork() {
+        WifiConfiguration pskNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        List<WifiConfiguration> networks = new ArrayList<>();
+        networks.add(pskNetwork);
+
+        verifyAddNetworkToWifiConfigManager(pskNetwork);
+
+        List<WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworksWithPasswords();
+        WifiConfigurationTestUtil.assertConfigurationsEqualForConfigManagerAddOrUpdate(
+                networks, retrievedNetworks);
+
+        List<WifiConfiguration> retrievedSavedNetworks = mWifiConfigManager.getSavedNetworks();
+        assertEquals(retrievedSavedNetworks.size(), 1);
+        assertEquals(retrievedSavedNetworks.get(0).configKey(), pskNetwork.configKey());
+        assertPasswordsMaskedInWifiConfiguration(retrievedSavedNetworks.get(0));
+    }
+
+    /**
+     * Verifies the addition of a single WEP network using
+     * {@link WifiConfigManagerNew#addOrUpdateNetwork(WifiConfiguration, int)} and verifies that
+     * {@link WifiConfigManagerNew#getSavedNetworks()} masks the password.
+     */
+    @Test
+    public void testAddSingleWepNetwork() {
+        WifiConfiguration wepNetwork = WifiConfigurationTestUtil.createWepNetwork();
+        List<WifiConfiguration> networks = new ArrayList<>();
+        networks.add(wepNetwork);
+
+        verifyAddNetworkToWifiConfigManager(wepNetwork);
+
+        List<WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworksWithPasswords();
+        WifiConfigurationTestUtil.assertConfigurationsEqualForConfigManagerAddOrUpdate(
+                networks, retrievedNetworks);
+
+        List<WifiConfiguration> retrievedSavedNetworks = mWifiConfigManager.getSavedNetworks();
+        assertEquals(retrievedSavedNetworks.size(), 1);
+        assertEquals(retrievedSavedNetworks.get(0).configKey(), wepNetwork.configKey());
+        assertPasswordsMaskedInWifiConfiguration(retrievedSavedNetworks.get(0));
+    }
+
+    /**
+     * Verifies the modification of an IpConfiguration using
+     * {@link WifiConfigManagerNew#addOrUpdateNetwork(WifiConfiguration, int)}
+     */
+    @Test
+    public void testUpdateIpConfiguration() {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        List<WifiConfiguration> networks = new ArrayList<>();
+        networks.add(openNetwork);
+
+        verifyAddNetworkToWifiConfigManager(openNetwork);
+
+        // Now change BSSID of the network.
+        assertAndSetNetworkBSSID(openNetwork, TEST_BSSID);
+
+        // Update the same configuration and ensure that the IP configuration change flags
+        // are not set.
+        verifyUpdateNetworkToWifiConfigManagerWithoutIpChange(openNetwork);
+
+        // Change the IpConfiguration now and ensure that the IP configuration flags are set now.
+        assertAndSetNetworkIpConfiguration(
+                openNetwork,
+                WifiConfigurationTestUtil.createStaticIpConfigurationWithStaticProxy());
+        verifyUpdateNetworkToWifiConfigManagerWithIpChange(openNetwork);
+
+        // Now verify that all the modifications have been effective.
+        List<WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworksWithPasswords();
+        WifiConfigurationTestUtil.assertConfigurationsEqualForConfigManagerAddOrUpdate(
+                networks, retrievedNetworks);
+    }
+
+    /**
+     * Verifies the removal of a single network using
+     * {@link WifiConfigManagerNew#removeNetwork(int)}
+     */
+    @Test
+    public void testRemoveSingleOpenNetwork() {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        List<WifiConfiguration> networks = new ArrayList<>();
+        networks.add(openNetwork);
+
+        verifyAddNetworkToWifiConfigManager(openNetwork);
+
+        verifyRemoveNetworkFromWifiConfigManager(openNetwork);
+
+        // Ensure that configured network list is empty now.
+        assertTrue(mWifiConfigManager.getConfiguredNetworks().isEmpty());
+    }
+
+    /**
+     * Verifies the addition & update of multiple networks using
+     * {@link WifiConfigManagerNew#addOrUpdateNetwork(WifiConfiguration, int)} and the
+     * removal of networks using
+     * {@link WifiConfigManagerNew#removeNetwork(int)}
+     */
+    @Test
+    public void testAddUpdateRemoveMultipleNetworks() {
+        List<WifiConfiguration> networks = new ArrayList<>();
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        WifiConfiguration pskNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        WifiConfiguration wepNetwork = WifiConfigurationTestUtil.createWepNetwork();
+        networks.add(openNetwork);
+        networks.add(pskNetwork);
+        networks.add(wepNetwork);
+
+        verifyAddNetworkToWifiConfigManager(openNetwork);
+        verifyAddNetworkToWifiConfigManager(pskNetwork);
+        verifyAddNetworkToWifiConfigManager(wepNetwork);
+
+        // Now verify that all the additions has been effective.
+        List<WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworksWithPasswords();
+        WifiConfigurationTestUtil.assertConfigurationsEqualForConfigManagerAddOrUpdate(
+                networks, retrievedNetworks);
+
+        // Modify all the 3 configurations and update it to WifiConfigManager.
+        assertAndSetNetworkBSSID(openNetwork, TEST_BSSID);
+        assertAndSetNetworkBSSID(pskNetwork, TEST_BSSID);
+        assertAndSetNetworkIpConfiguration(
+                wepNetwork,
+                WifiConfigurationTestUtil.createStaticIpConfigurationWithPacProxy());
+
+        verifyUpdateNetworkToWifiConfigManagerWithoutIpChange(openNetwork);
+        verifyUpdateNetworkToWifiConfigManagerWithoutIpChange(pskNetwork);
+        verifyUpdateNetworkToWifiConfigManagerWithIpChange(wepNetwork);
+        // Now verify that all the modifications has been effective.
+        retrievedNetworks = mWifiConfigManager.getConfiguredNetworksWithPasswords();
+        WifiConfigurationTestUtil.assertConfigurationsEqualForConfigManagerAddOrUpdate(
+                networks, retrievedNetworks);
+
+        // Now remove all 3 networks.
+        verifyRemoveNetworkFromWifiConfigManager(openNetwork);
+        verifyRemoveNetworkFromWifiConfigManager(pskNetwork);
+        verifyRemoveNetworkFromWifiConfigManager(wepNetwork);
+
+        // Ensure that configured network list is empty now.
+        assertTrue(mWifiConfigManager.getConfiguredNetworks().isEmpty());
+    }
+
+    /**
+     * Verifies the update of network status using
+     * {@link WifiConfigManagerNew#updateNetworkSelectionStatus(int, int)}.
+     */
+    @Test
+    public void testNetworkSelectionStatus() {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+
+        NetworkUpdateResult result = verifyAddNetworkToWifiConfigManager(openNetwork);
+
+        // First set it to enabled.
+        verifyUpdateNetworkSelectionStatus(
+                result.getNetworkId(), NetworkSelectionStatus.NETWORK_SELECTION_ENABLE, 0);
+
+        // Now set it to temporarily disabled. The threshold for association rejection is 5, so
+        // disable it 5 times to actually mark it temporarily disabled.
+        int assocRejectReason = NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION;
+        int assocRejectThreshold =
+                WifiConfigManagerNew.NETWORK_SELECTION_DISABLE_THRESHOLD[assocRejectReason];
+        for (int i = 1; i <= assocRejectThreshold; i++) {
+            verifyUpdateNetworkSelectionStatus(result.getNetworkId(), assocRejectReason, i);
+        }
+
+        // Now set it to permanently disabled.
+        verifyUpdateNetworkSelectionStatus(
+                result.getNetworkId(), NetworkSelectionStatus.DISABLED_BY_WIFI_MANAGER, 0);
+
+        // Now set it back to enabled.
+        verifyUpdateNetworkSelectionStatus(
+                result.getNetworkId(), NetworkSelectionStatus.NETWORK_SELECTION_ENABLE, 0);
+    }
+
+    /**
+     * Verifies the update of network status using
+     * {@link WifiConfigManagerNew#updateNetworkSelectionStatus(int, int)} and ensures that
+     * enabling a network clears out all the temporary disable counters.
+     */
+    @Test
+    public void testNetworkSelectionStatusEnableClearsDisableCounters() {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+
+        NetworkUpdateResult result = verifyAddNetworkToWifiConfigManager(openNetwork);
+
+        // First set it to enabled.
+        verifyUpdateNetworkSelectionStatus(
+                result.getNetworkId(), NetworkSelectionStatus.NETWORK_SELECTION_ENABLE, 0);
+
+        // Now set it to temporarily disabled 2 times for 2 different reasons.
+        verifyUpdateNetworkSelectionStatus(
+                result.getNetworkId(), NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION, 1);
+        verifyUpdateNetworkSelectionStatus(
+                result.getNetworkId(), NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION, 2);
+        verifyUpdateNetworkSelectionStatus(
+                result.getNetworkId(), NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE, 1);
+        verifyUpdateNetworkSelectionStatus(
+                result.getNetworkId(), NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE, 2);
+
+        // Now set it back to enabled.
+        verifyUpdateNetworkSelectionStatus(
+                result.getNetworkId(), NetworkSelectionStatus.NETWORK_SELECTION_ENABLE, 0);
+
+        // Ensure that the counters have all been reset now.
+        verifyUpdateNetworkSelectionStatus(
+                result.getNetworkId(), NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION, 1);
+        verifyUpdateNetworkSelectionStatus(
+                result.getNetworkId(), NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE, 1);
+    }
+
+    /**
+     * Verifies the enabling of temporarily disabled network using
+     * {@link WifiConfigManagerNew#tryEnableNetwork(int)}.
+     */
+    @Test
+    public void testTryEnableNetwork() {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+
+        NetworkUpdateResult result = verifyAddNetworkToWifiConfigManager(openNetwork);
+
+        // First set it to enabled.
+        verifyUpdateNetworkSelectionStatus(
+                result.getNetworkId(), NetworkSelectionStatus.NETWORK_SELECTION_ENABLE, 0);
+
+        // Now set it to temporarily disabled. The threshold for association rejection is 5, so
+        // disable it 5 times to actually mark it temporarily disabled.
+        int assocRejectReason = NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION;
+        int assocRejectThreshold =
+                WifiConfigManagerNew.NETWORK_SELECTION_DISABLE_THRESHOLD[assocRejectReason];
+        for (int i = 1; i <= assocRejectThreshold; i++) {
+            verifyUpdateNetworkSelectionStatus(result.getNetworkId(), assocRejectReason, i);
+        }
+
+        // Now let's try enabling this network without changing the time, this should fail and the
+        // status remains temporarily disabled.
+        assertFalse(mWifiConfigManager.tryEnableNetwork(result.getNetworkId()));
+        NetworkSelectionStatus retrievedStatus =
+                mWifiConfigManager.getConfiguredNetwork(result.getNetworkId())
+                        .getNetworkSelectionStatus();
+        assertTrue(retrievedStatus.isNetworkTemporaryDisabled());
+
+        // Now advance time by the timeout for association rejection and ensure that the network
+        // is now enabled.
+        int assocRejectTimeout =
+                WifiConfigManagerNew.NETWORK_SELECTION_DISABLE_TIMEOUT_MS[assocRejectReason];
+        when(mClock.getElapsedSinceBootMillis())
+                .thenReturn(TEST_ELAPSED_UPDATE_NETWORK_SELECTION_TIME_MILLIS + assocRejectTimeout);
+
+        assertTrue(mWifiConfigManager.tryEnableNetwork(result.getNetworkId()));
+        retrievedStatus =
+                mWifiConfigManager.getConfiguredNetwork(result.getNetworkId())
+                        .getNetworkSelectionStatus();
+        assertTrue(retrievedStatus.isNetworkEnabled());
+    }
+
+    /**
+     * Verifies the enabling of network using
+     * {@link WifiConfigManagerNew#enableNetwork(int, int)} and
+     * {@link WifiConfigManagerNew#disableNetwork(int, int)}.
+     */
+    @Test
+    public void testEnableDisableNetwork() {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+
+        NetworkUpdateResult result = verifyAddNetworkToWifiConfigManager(openNetwork);
+
+        assertTrue(mWifiConfigManager.enableNetwork(result.getNetworkId(), TEST_CREATOR_UID));
+        WifiConfiguration retrievedNetwork =
+                mWifiConfigManager.getConfiguredNetwork(result.getNetworkId());
+        NetworkSelectionStatus retrievedStatus = retrievedNetwork.getNetworkSelectionStatus();
+        assertTrue(retrievedStatus.isNetworkEnabled());
+        verifyUpdateNetworkStatus(retrievedNetwork, WifiConfiguration.Status.ENABLED);
+
+        // Now set it disabled.
+        assertTrue(mWifiConfigManager.disableNetwork(result.getNetworkId(), TEST_CREATOR_UID));
+        retrievedNetwork = mWifiConfigManager.getConfiguredNetwork(result.getNetworkId());
+        retrievedStatus = retrievedNetwork.getNetworkSelectionStatus();
+        assertTrue(retrievedStatus.isNetworkPermanentlyDisabled());
+        verifyUpdateNetworkStatus(retrievedNetwork, WifiConfiguration.Status.DISABLED);
+    }
+
+    /**
+     * Verifies the enabling of network using
+     * {@link WifiConfigManagerNew#enableNetwork(int, int)} with a UID which
+     * has no permission to modify the network fails..
+     */
+    @Test
+    public void testEnableDisableNetworkFailedDueToPermissionDenied() throws Exception {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+
+        NetworkUpdateResult result = verifyAddNetworkToWifiConfigManager(openNetwork);
+
+        assertTrue(mWifiConfigManager.enableNetwork(result.getNetworkId(), TEST_CREATOR_UID));
+        WifiConfiguration retrievedNetwork =
+                mWifiConfigManager.getConfiguredNetwork(result.getNetworkId());
+        NetworkSelectionStatus retrievedStatus = retrievedNetwork.getNetworkSelectionStatus();
+        assertTrue(retrievedStatus.isNetworkEnabled());
+        verifyUpdateNetworkStatus(retrievedNetwork, WifiConfiguration.Status.ENABLED);
+
+        // Deny permission for |UPDATE_UID|.
+        doAnswer(new AnswerWithArguments() {
+            public int answer(String permName, int uid) throws Exception {
+                if (uid == TEST_CREATOR_UID) {
+                    return PackageManager.PERMISSION_GRANTED;
+                }
+                return PackageManager.PERMISSION_DENIED;
+            }
+        }).when(mFrameworkFacade).checkUidPermission(anyString(), anyInt());
+
+        // Now try to set it disabled with |TEST_UPDATE_UID|, it should fail and the network
+        // should remain enabled.
+        assertFalse(mWifiConfigManager.disableNetwork(result.getNetworkId(), TEST_UPDATE_UID));
+        retrievedStatus =
+                mWifiConfigManager.getConfiguredNetwork(result.getNetworkId())
+                        .getNetworkSelectionStatus();
+        assertTrue(retrievedStatus.isNetworkEnabled());
+        assertEquals(WifiConfiguration.Status.ENABLED, retrievedNetwork.status);
+    }
+
+    /**
+     * Verifies the updation of network's connectUid using
+     * {@link WifiConfigManagerNew#checkAndUpdateLastConnectUid(int, int)}.
+     */
+    @Test
+    public void testUpdateLastConnectUid() throws Exception {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+
+        NetworkUpdateResult result = verifyAddNetworkToWifiConfigManager(openNetwork);
+
+        assertTrue(
+                mWifiConfigManager.checkAndUpdateLastConnectUid(result.getNetworkId(), TEST_CREATOR_UID));
+        WifiConfiguration retrievedNetwork =
+                mWifiConfigManager.getConfiguredNetwork(result.getNetworkId());
+        assertEquals(TEST_CREATOR_UID, retrievedNetwork.lastConnectUid);
+
+        // Deny permission for |UPDATE_UID|.
+        doAnswer(new AnswerWithArguments() {
+            public int answer(String permName, int uid) throws Exception {
+                if (uid == TEST_CREATOR_UID) {
+                    return PackageManager.PERMISSION_GRANTED;
+                }
+                return PackageManager.PERMISSION_DENIED;
+            }
+        }).when(mFrameworkFacade).checkUidPermission(anyString(), anyInt());
+
+        // Now try to update the last connect UID with |TEST_UPDATE_UID|, it should fail and
+        // the lastConnectUid should remain the same.
+        assertFalse(
+                mWifiConfigManager.checkAndUpdateLastConnectUid(
+                        result.getNetworkId(), TEST_UPDATE_UID));
+        retrievedNetwork = mWifiConfigManager.getConfiguredNetwork(result.getNetworkId());
+        assertEquals(TEST_CREATOR_UID, retrievedNetwork.lastConnectUid);
+    }
+
+    /**
+     * Verifies that any configuration update attempt with an null config is gracefully
+     * handled.
+     * This invokes {@link WifiConfigManagerNew#addOrUpdateNetwork(WifiConfiguration, int)}.
+     */
+    @Test
+    public void testAddOrUpdateNetworkWithNullConfig() {
+        NetworkUpdateResult result = mWifiConfigManager.addOrUpdateNetwork(null, TEST_CREATOR_UID);
+        assertFalse(result.isSuccess());
+    }
+
+    /**
+     * Verifies that any configuration removal attempt with an invalid networkID is gracefully
+     * handled.
+     * This invokes {@link WifiConfigManagerNew#removeNetwork(int)}.
+     */
+    @Test
+    public void testRemoveNetworkWithInvalidNetworkId() {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+
+        verifyAddNetworkToWifiConfigManager(openNetwork);
+
+        // Change the networkID to an invalid one.
+        openNetwork.networkId++;
+        assertFalse(mWifiConfigManager.removeNetwork(openNetwork.networkId));
+    }
+
+    /**
+     * Verifies that any configuration update attempt with an invalid networkID is gracefully
+     * handled.
+     * This invokes {@link WifiConfigManagerNew#enableNetwork(int, int)},
+     * {@link WifiConfigManagerNew#disableNetwork(int, int)},
+     * {@link WifiConfigManagerNew#updateNetworkSelectionStatus(int, int)} and
+     * {@link WifiConfigManagerNew#checkAndUpdateLastConnectUid(int, int)}.
+     */
+    @Test
+    public void testChangeConfigurationWithInvalidNetworkId() {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+
+        NetworkUpdateResult result = verifyAddNetworkToWifiConfigManager(openNetwork);
+
+        assertFalse(mWifiConfigManager.enableNetwork(result.getNetworkId() + 1, TEST_CREATOR_UID));
+        assertFalse(mWifiConfigManager.disableNetwork(result.getNetworkId() + 1, TEST_CREATOR_UID));
+        assertFalse(mWifiConfigManager.updateNetworkSelectionStatus(
+                result.getNetworkId() + 1, NetworkSelectionStatus.DISABLED_BY_WIFI_MANAGER));
+        assertFalse(mWifiConfigManager.checkAndUpdateLastConnectUid(
+                result.getNetworkId() + 1, TEST_CREATOR_UID));
+    }
+
+    /**
+     * Verifies multiple modification of a single network using
+     * {@link WifiConfigManagerNew#addOrUpdateNetwork(WifiConfiguration, int)}.
+     * This test is basically checking if the apps can reset some of the fields of the config after
+     * addition. The fields being reset in this test are the |preSharedKey| and |wepKeys|.
+     * 1. Create an open network initially.
+     * 2. Modify the added network config to a WEP network config with all the 4 keys set.
+     * 3. Modify the added network config to a WEP network config with only 1 key set.
+     * 4. Modify the added network config to a PSK network config.
+     */
+    @Test
+    public void testMultipleUpdatesSingleNetwork() {
+        WifiConfiguration network = WifiConfigurationTestUtil.createOpenNetwork();
+        verifyAddNetworkToWifiConfigManager(network);
+
+        // Now add |wepKeys| to the network. We don't need to update the |allowedKeyManagement|
+        // fields for open to WEP conversion.
+        String[] wepKeys =
+                Arrays.copyOf(WifiConfigurationTestUtil.TEST_WEP_KEYS,
+                        WifiConfigurationTestUtil.TEST_WEP_KEYS.length);
+        int wepTxKeyIdx = WifiConfigurationTestUtil.TEST_WEP_TX_KEY_INDEX;
+        assertAndSetNetworkWepKeysAndTxIndex(network, wepKeys, wepTxKeyIdx);
+
+        verifyUpdateNetworkToWifiConfigManagerWithoutIpChange(network);
+        WifiConfigurationTestUtil.assertConfigurationEqualForConfigManagerAddOrUpdate(
+                network, mWifiConfigManager.getConfiguredNetworkWithPassword(network.networkId));
+
+        // Now empty out 3 of the |wepKeys[]| and ensure that those keys have been reset correctly.
+        for (int i = 1; i < network.wepKeys.length; i++) {
+            wepKeys[i] = "";
+        }
+        wepTxKeyIdx = 0;
+        assertAndSetNetworkWepKeysAndTxIndex(network, wepKeys, wepTxKeyIdx);
+
+        verifyUpdateNetworkToWifiConfigManagerWithoutIpChange(network);
+        WifiConfigurationTestUtil.assertConfigurationEqualForConfigManagerAddOrUpdate(
+                network, mWifiConfigManager.getConfiguredNetworkWithPassword(network.networkId));
+
+        // Now change the config to a PSK network config by resetting the remaining |wepKey[0]|
+        // field and setting the |preSharedKey| and |allowedKeyManagement| fields.
+        wepKeys[0] = "";
+        wepTxKeyIdx = -1;
+        assertAndSetNetworkWepKeysAndTxIndex(network, wepKeys, wepTxKeyIdx);
+        network.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+        assertAndSetNetworkPreSharedKey(network, WifiConfigurationTestUtil.TEST_PSK);
+
+        verifyUpdateNetworkToWifiConfigManagerWithoutIpChange(network);
+        WifiConfigurationTestUtil.assertConfigurationEqualForConfigManagerAddOrUpdate(
+                network, mWifiConfigManager.getConfiguredNetworkWithPassword(network.networkId));
+    }
+
+    /**
+     * Verifies the modification of a WifiEnteriseConfig using
+     * {@link WifiConfigManagerNew#addOrUpdateNetwork(WifiConfiguration, int)}.
+     */
+    @Test
+    public void testUpdateWifiEnterpriseConfig() {
+        WifiConfiguration network = WifiConfigurationTestUtil.createEapNetwork();
+        verifyAddNetworkToWifiConfigManager(network);
+
+        // Set the |password| field in WifiEnterpriseConfig and modify the config to PEAP/GTC.
+        network.enterpriseConfig =
+                WifiConfigurationTestUtil.createPEAPWifiEnterpriseConfigWithGTCPhase2();
+        assertAndSetNetworkEnterprisePassword(network, "test");
+
+        verifyUpdateNetworkToWifiConfigManagerWithoutIpChange(network);
+        WifiConfigurationTestUtil.assertConfigurationEqualForConfigManagerAddOrUpdate(
+                network, mWifiConfigManager.getConfiguredNetworkWithPassword(network.networkId));
+
+        // Reset the |password| field in WifiEnterpriseConfig and modify the config to TLS/None.
+        network.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+        network.enterpriseConfig.setPhase2Method(WifiEnterpriseConfig.Phase2.NONE);
+        assertAndSetNetworkEnterprisePassword(network, "");
+
+        verifyUpdateNetworkToWifiConfigManagerWithoutIpChange(network);
+        WifiConfigurationTestUtil.assertConfigurationEqualForConfigManagerAddOrUpdate(
+                network, mWifiConfigManager.getConfiguredNetworkWithPassword(network.networkId));
+    }
+
+    /**
+     * Verifies the modification of a single network using
+     * {@link WifiConfigManagerNew#addOrUpdateNetwork(WifiConfiguration, int)} by passing in nulls
+     * in all the publicly exposed fields.
+     */
+    @Test
+    public void testUpdateSingleNetworkWithNullValues() {
+        WifiConfiguration network = WifiConfigurationTestUtil.createEapNetwork();
+        verifyAddNetworkToWifiConfigManager(network);
+
+        // Save a copy of the original network for comparison.
+        WifiConfiguration originalNetwork = new WifiConfiguration(network);
+
+        // Now set all the public fields to null and try updating the network.
+        network.allowedAuthAlgorithms = null;
+        network.allowedProtocols = null;
+        network.allowedKeyManagement = null;
+        network.allowedPairwiseCiphers = null;
+        network.allowedGroupCiphers = null;
+        network.setIpConfiguration(null);
+        network.enterpriseConfig = null;
+
+        verifyUpdateNetworkToWifiConfigManagerWithoutIpChange(network);
+
+        // Copy over the updated debug params to the original network config before comparison.
+        originalNetwork.lastUpdateUid = network.lastUpdateUid;
+        originalNetwork.lastUpdateName = network.lastUpdateName;
+        originalNetwork.updateTime = network.updateTime;
+
+        // Now verify that there was no change to the network configurations.
+        WifiConfigurationTestUtil.assertConfigurationEqualForConfigManagerAddOrUpdate(
+                originalNetwork,
+                mWifiConfigManager.getConfiguredNetworkWithPassword(originalNetwork.networkId));
+    }
+
+    /**
+     * Verifies the matching of networks with different encryption types with the
+     * corresponding scan detail using
+     * {@link WifiConfigManagerNew#getSavedNetworkForScanDetailAndCache(ScanDetail)}.
+     * The test also verifies that the provided scan detail was cached,
+     */
+    @Test
+    public void testMatchScanDetailToNetworksAndCache() {
+        // Create networks of different types and ensure that they're all matched using
+        // the corresponding ScanDetail correctly.
+        verifyAddSingleNetworkAndMatchScanDetailToNetworkAndCache(
+                WifiConfigurationTestUtil.createOpenNetwork());
+        verifyAddSingleNetworkAndMatchScanDetailToNetworkAndCache(
+                WifiConfigurationTestUtil.createWepNetwork());
+        verifyAddSingleNetworkAndMatchScanDetailToNetworkAndCache(
+                WifiConfigurationTestUtil.createPskNetwork());
+        verifyAddSingleNetworkAndMatchScanDetailToNetworkAndCache(
+                WifiConfigurationTestUtil.createEapNetwork());
+    }
+
+    /**
+     * Verifies that scan details with wrong SSID/authentication types are not matched using
+     * {@link WifiConfigManagerNew#getSavedNetworkForScanDetailAndCache(ScanDetail)}
+     * to the added networks.
+     */
+    @Test
+    public void testNoMatchScanDetailToNetwork() {
+        // First create networks of different types.
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        WifiConfiguration wepNetwork = WifiConfigurationTestUtil.createWepNetwork();
+        WifiConfiguration pskNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        WifiConfiguration eapNetwork = WifiConfigurationTestUtil.createEapNetwork();
+
+        // Now add them to WifiConfigManager.
+        verifyAddNetworkToWifiConfigManager(openNetwork);
+        verifyAddNetworkToWifiConfigManager(wepNetwork);
+        verifyAddNetworkToWifiConfigManager(pskNetwork);
+        verifyAddNetworkToWifiConfigManager(eapNetwork);
+
+        // Now create dummy scan detail corresponding to the networks.
+        ScanDetail openNetworkScanDetail = createScanDetailForNetwork(openNetwork);
+        ScanDetail wepNetworkScanDetail = createScanDetailForNetwork(wepNetwork);
+        ScanDetail pskNetworkScanDetail = createScanDetailForNetwork(pskNetwork);
+        ScanDetail eapNetworkScanDetail = createScanDetailForNetwork(eapNetwork);
+
+        // Now mix and match parameters from different scan details.
+        openNetworkScanDetail.getScanResult().SSID =
+                wepNetworkScanDetail.getScanResult().SSID;
+        wepNetworkScanDetail.getScanResult().capabilities =
+                pskNetworkScanDetail.getScanResult().capabilities;
+        pskNetworkScanDetail.getScanResult().capabilities =
+                eapNetworkScanDetail.getScanResult().capabilities;
+        eapNetworkScanDetail.getScanResult().capabilities =
+                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));
+
+        // All the cache's should be empty as well.
+        assertNull(mWifiConfigManager.getScanDetailCacheForNetwork(openNetwork.networkId));
+        assertNull(mWifiConfigManager.getScanDetailCacheForNetwork(wepNetwork.networkId));
+        assertNull(mWifiConfigManager.getScanDetailCacheForNetwork(pskNetwork.networkId));
+        assertNull(mWifiConfigManager.getScanDetailCacheForNetwork(eapNetwork.networkId));
+    }
+
+    /**
+     * Verifies that scan detail cache is trimmed down when the size of the cache for a network
+     * exceeds {@link WifiConfigManagerNew#SCAN_CACHE_ENTRIES_MAX_SIZE}.
+     */
+    @Test
+    public void testScanDetailCacheTrimForNetwork() {
+        // Add a single network.
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        verifyAddNetworkToWifiConfigManager(openNetwork);
+
+        ScanDetailCache scanDetailCache;
+        String testBssidPrefix = "00:a5:b8:c9:45:";
+
+        // Modify |BSSID| field in the scan result and add copies of scan detail
+        // |SCAN_CACHE_ENTRIES_MAX_SIZE| times.
+        int scanDetailNum = 1;
+        for (; scanDetailNum <= WifiConfigManagerNew.SCAN_CACHE_ENTRIES_MAX_SIZE; scanDetailNum++) {
+            // Create dummy scan detail caches with different BSSID for the network.
+            ScanDetail scanDetail =
+                    createScanDetailForNetwork(
+                            openNetwork, String.format("%s%02x", testBssidPrefix, scanDetailNum));
+            assertNotNull(
+                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(scanDetail));
+
+            // The size of scan detail cache should keep growing until it hits
+            // |SCAN_CACHE_ENTRIES_MAX_SIZE|.
+            scanDetailCache =
+                    mWifiConfigManager.getScanDetailCacheForNetwork(openNetwork.networkId);
+            assertEquals(scanDetailNum, scanDetailCache.size());
+        }
+
+        // Now add the |SCAN_CACHE_ENTRIES_MAX_SIZE + 1| entry. This should trigger the trim.
+        ScanDetail scanDetail =
+                createScanDetailForNetwork(
+                        openNetwork, String.format("%s%02x", testBssidPrefix, scanDetailNum));
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(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
+        // was added after the trim.
+        scanDetailCache = mWifiConfigManager.getScanDetailCacheForNetwork(openNetwork.networkId);
+        assertEquals(WifiConfigManagerNew.SCAN_CACHE_ENTRIES_TRIM_SIZE + 1, scanDetailCache.size());
+    }
+
+    /**
+     * This method sets defaults in the provided WifiConfiguration object if not set
+     * so that it can be used for comparison with the configuration retrieved from
+     * WifiConfigManager.
+     */
+    private void setDefaults(WifiConfiguration configuration) {
+        if (configuration.allowedAuthAlgorithms.isEmpty()) {
+            configuration.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
+            configuration.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED);
+        }
+        if (configuration.allowedProtocols.isEmpty()) {
+            configuration.allowedProtocols.set(WifiConfiguration.Protocol.RSN);
+            configuration.allowedProtocols.set(WifiConfiguration.Protocol.WPA);
+        }
+        if (configuration.allowedKeyManagement.isEmpty()) {
+            configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+            configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
+        }
+        if (configuration.allowedPairwiseCiphers.isEmpty()) {
+            configuration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
+            configuration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);
+        }
+        if (configuration.allowedGroupCiphers.isEmpty()) {
+            configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
+            configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
+            configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40);
+            configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104);
+        }
+        if (configuration.getIpAssignment() == IpConfiguration.IpAssignment.UNASSIGNED) {
+            configuration.setIpAssignment(IpConfiguration.IpAssignment.DHCP);
+        }
+        if (configuration.getProxySettings() == IpConfiguration.ProxySettings.UNASSIGNED) {
+            configuration.setProxySettings(IpConfiguration.ProxySettings.NONE);
+        }
+    }
+
+    /**
+     * Modifies the provided configuration with creator uid, package name
+     * and time.
+     */
+    private void setCreationDebugParams(WifiConfiguration configuration) {
+        configuration.creatorUid = configuration.lastUpdateUid = TEST_CREATOR_UID;
+        configuration.creatorName = configuration.lastUpdateName = TEST_CREATOR_NAME;
+        configuration.creationTime = configuration.updateTime =
+                WifiConfigManagerNew.createDebugTimeStampString(
+                        TEST_WALLCLOCK_CREATION_TIME_MILLIS);
+    }
+
+    /**
+     * Modifies the provided configuration with update uid, package name
+     * and time.
+     */
+    private void setUpdateDebugParams(WifiConfiguration configuration) {
+        configuration.lastUpdateUid = TEST_UPDATE_UID;
+        configuration.lastUpdateName = TEST_UPDATE_NAME;
+        configuration.updateTime =
+                WifiConfigManagerNew.createDebugTimeStampString(TEST_WALLCLOCK_UPDATE_TIME_MILLIS);
+    }
+
+    private void assertNotEquals(Object expected, Object actual) {
+        if (actual != null) {
+            assertFalse(actual.equals(expected));
+        } else {
+            assertNotNull(expected);
+        }
+    }
+
+    /**
+     * Modifies the provided WifiConfiguration with the specified bssid value. Also, asserts that
+     * the existing |BSSID| field is not the same value as the one being set
+     */
+    private void assertAndSetNetworkBSSID(WifiConfiguration configuration, String bssid) {
+        assertNotEquals(bssid, configuration.BSSID);
+        configuration.BSSID = bssid;
+    }
+
+    /**
+     * Modifies the provided WifiConfiguration with the specified |IpConfiguration| object. Also,
+     * asserts that the existing |mIpConfiguration| field is not the same value as the one being set
+     */
+    private void assertAndSetNetworkIpConfiguration(
+            WifiConfiguration configuration, IpConfiguration ipConfiguration) {
+        assertNotEquals(ipConfiguration, configuration.getIpConfiguration());
+        configuration.setIpConfiguration(ipConfiguration);
+    }
+
+    /**
+     * Modifies the provided WifiConfiguration with the specified |wepKeys| value and
+     * |wepTxKeyIndex|.
+     */
+    private void assertAndSetNetworkWepKeysAndTxIndex(
+            WifiConfiguration configuration, String[] wepKeys, int wepTxKeyIdx) {
+        assertNotEquals(wepKeys, configuration.wepKeys);
+        assertNotEquals(wepTxKeyIdx, configuration.wepTxKeyIndex);
+        configuration.wepKeys = Arrays.copyOf(wepKeys, wepKeys.length);
+        configuration.wepTxKeyIndex = wepTxKeyIdx;
+    }
+
+    /**
+     * Modifies the provided WifiConfiguration with the specified |preSharedKey| value.
+     */
+    private void assertAndSetNetworkPreSharedKey(
+            WifiConfiguration configuration, String preSharedKey) {
+        assertNotEquals(preSharedKey, configuration.preSharedKey);
+        configuration.preSharedKey = preSharedKey;
+    }
+
+    /**
+     * Modifies the provided WifiConfiguration with the specified enteprise |password| value.
+     */
+    private void assertAndSetNetworkEnterprisePassword(
+            WifiConfiguration configuration, String password) {
+        assertNotEquals(password, configuration.enterpriseConfig.getPassword());
+        configuration.enterpriseConfig.setPassword(password);
+    }
+
+    /**
+     * Returns whether the provided network was in the store data or not.
+     */
+    private boolean isNetworkInConfigStoreData(WifiConfiguration configuration) {
+        try {
+            ArgumentCaptor<WifiConfigStoreData> storeDataCaptor =
+                    ArgumentCaptor.forClass(WifiConfigStoreData.class);
+            mContextConfigStoreMockOrder.verify(mWifiConfigStore)
+                    .write(anyBoolean(), storeDataCaptor.capture());
+
+            WifiConfigStoreData storeData = storeDataCaptor.getValue();
+
+            boolean foundNetworkInStoreData = false;
+            for (WifiConfiguration retrievedConfig : storeData.configurations) {
+                if (retrievedConfig.configKey().equals(configuration.configKey())) {
+                    foundNetworkInStoreData = true;
+                }
+            }
+            return foundNetworkInStoreData;
+        } catch (Exception e) {
+            fail("Exception encountered during write " + e);
+        }
+        return false;
+    }
+
+    /**
+     * Verifies that the provided network was not present in the last config store write.
+     */
+    private void verifyNetworkNotInConfigStoreData(WifiConfiguration configuration) {
+        assertFalse(isNetworkInConfigStoreData(configuration));
+    }
+
+    /**
+     * Verifies that the provided network was present in the last config store write.
+     */
+    private void verifyNetworkInConfigStoreData(WifiConfiguration configuration) {
+        assertTrue(isNetworkInConfigStoreData(configuration));
+    }
+
+    private void assertPasswordsMaskedInWifiConfiguration(WifiConfiguration configuration) {
+        if (!TextUtils.isEmpty(configuration.preSharedKey)) {
+            assertEquals(WifiConfigManagerNew.PASSWORD_MASK, configuration.preSharedKey);
+        }
+        if (configuration.wepKeys != null) {
+            for (int i = 0; i < configuration.wepKeys.length; i++) {
+                if (!TextUtils.isEmpty(configuration.wepKeys[i])) {
+                    assertEquals(WifiConfigManagerNew.PASSWORD_MASK, configuration.wepKeys[i]);
+                }
+            }
+        }
+        if (!TextUtils.isEmpty(configuration.enterpriseConfig.getPassword())) {
+            assertEquals(
+                    WifiConfigManagerNew.PASSWORD_MASK,
+                    configuration.enterpriseConfig.getPassword());
+        }
+    }
+
+    /**
+     * Verifies that the network was present in the network change broadcast and returns the
+     * change reason.
+     */
+    private int verifyNetworkInBroadcastAndReturnReason(WifiConfiguration configuration) {
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        ArgumentCaptor<UserHandle> userHandleCaptor = ArgumentCaptor.forClass(UserHandle.class);
+        mContextConfigStoreMockOrder.verify(mContext)
+                .sendBroadcastAsUser(intentCaptor.capture(), userHandleCaptor.capture());
+
+        assertEquals(userHandleCaptor.getValue(), UserHandle.ALL);
+        Intent intent = intentCaptor.getValue();
+
+        int changeReason = intent.getIntExtra(WifiManager.EXTRA_CHANGE_REASON, -1);
+        WifiConfiguration retrievedConfig =
+                (WifiConfiguration) intent.getExtra(WifiManager.EXTRA_WIFI_CONFIGURATION);
+        assertEquals(retrievedConfig.configKey(), configuration.configKey());
+
+        // Verify that all the passwords are masked in the broadcast configuration.
+        assertPasswordsMaskedInWifiConfiguration(retrievedConfig);
+
+        return changeReason;
+    }
+
+    /**
+     * Verifies that we sent out an add broadcast with the provided network.
+     */
+    private void verifyNetworkAddBroadcast(WifiConfiguration configuration) {
+        assertEquals(
+                verifyNetworkInBroadcastAndReturnReason(configuration),
+                WifiManager.CHANGE_REASON_ADDED);
+    }
+
+    /**
+     * Verifies that we sent out an update broadcast with the provided network.
+     */
+    private void verifyNetworkUpdateBroadcast(WifiConfiguration configuration) {
+        assertEquals(
+                verifyNetworkInBroadcastAndReturnReason(configuration),
+                WifiManager.CHANGE_REASON_CONFIG_CHANGE);
+    }
+
+    /**
+     * Verifies that we sent out a remove broadcast with the provided network.
+     */
+    private void verifyNetworkRemoveBroadcast(WifiConfiguration configuration) {
+        assertEquals(
+                verifyNetworkInBroadcastAndReturnReason(configuration),
+                WifiManager.CHANGE_REASON_REMOVED);
+    }
+
+    /**
+     * Adds the provided configuration to WifiConfigManager and modifies the provided configuration
+     * with creator/update uid, package name and time. This also sets defaults for fields not
+     * populated.
+     * These fields are populated internally by WifiConfigManager and hence we need
+     * to modify the configuration before we compare the added network with the retrieved network.
+     */
+    private NetworkUpdateResult addNetworkToWifiConfigManager(WifiConfiguration configuration) {
+        when(mClock.getWallClockMillis()).thenReturn(TEST_WALLCLOCK_CREATION_TIME_MILLIS);
+        NetworkUpdateResult result =
+                mWifiConfigManager.addOrUpdateNetwork(configuration, TEST_CREATOR_UID);
+        setDefaults(configuration);
+        setCreationDebugParams(configuration);
+        configuration.networkId = result.getNetworkId();
+        return result;
+    }
+
+    /**
+     * Add network to WifiConfigManager and ensure that it was successful.
+     */
+    private NetworkUpdateResult verifyAddNetworkToWifiConfigManager(
+            WifiConfiguration configuration) {
+        NetworkUpdateResult result = addNetworkToWifiConfigManager(configuration);
+        assertTrue(result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID);
+        assertTrue(result.isNewNetwork());
+        assertTrue(result.hasIpChanged());
+        assertTrue(result.hasProxyChanged());
+
+        verifyNetworkAddBroadcast(configuration);
+        // Verify that the config store write was triggered with this new configuration.
+        verifyNetworkInConfigStoreData(configuration);
+        return result;
+    }
+
+    /**
+     * Add ephemeral network to WifiConfigManager and ensure that it was successful.
+     */
+    private NetworkUpdateResult verifyAddEphemeralNetworkToWifiConfigManager(
+            WifiConfiguration configuration) {
+        NetworkUpdateResult result = addNetworkToWifiConfigManager(configuration);
+        assertTrue(result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID);
+        assertTrue(result.isNewNetwork());
+        assertTrue(result.hasIpChanged());
+        assertTrue(result.hasProxyChanged());
+
+        verifyNetworkAddBroadcast(configuration);
+        // Ephemeral networks should not be persisted.
+        verifyNetworkNotInConfigStoreData(configuration);
+        return result;
+    }
+
+    /**
+     * Updates the provided configuration to WifiConfigManager and modifies the provided
+     * configuration with update uid, package name and time.
+     * These fields are populated internally by WifiConfigManager and hence we need
+     * to modify the configuration before we compare the added network with the retrieved network.
+     */
+    private NetworkUpdateResult updateNetworkToWifiConfigManager(WifiConfiguration configuration) {
+        when(mClock.getWallClockMillis()).thenReturn(TEST_WALLCLOCK_UPDATE_TIME_MILLIS);
+        NetworkUpdateResult result =
+                mWifiConfigManager.addOrUpdateNetwork(configuration, TEST_UPDATE_UID);
+        setUpdateDebugParams(configuration);
+        return result;
+    }
+
+    /**
+     * Update network to WifiConfigManager config change and ensure that it was successful.
+     */
+    private NetworkUpdateResult verifyUpdateNetworkToWifiConfigManager(
+            WifiConfiguration configuration) {
+        NetworkUpdateResult result = updateNetworkToWifiConfigManager(configuration);
+        assertTrue(result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID);
+        assertFalse(result.isNewNetwork());
+
+        verifyNetworkUpdateBroadcast(configuration);
+        // Verify that the config store write was triggered with this new configuration.
+        verifyNetworkInConfigStoreData(configuration);
+        return result;
+    }
+
+    /**
+     * Update network to WifiConfigManager without IP config change and ensure that it was
+     * successful.
+     */
+    private NetworkUpdateResult verifyUpdateNetworkToWifiConfigManagerWithoutIpChange(
+            WifiConfiguration configuration) {
+        NetworkUpdateResult result = verifyUpdateNetworkToWifiConfigManager(configuration);
+        assertFalse(result.hasIpChanged());
+        assertFalse(result.hasProxyChanged());
+        return result;
+    }
+
+    /**
+     * Update network to WifiConfigManager with IP config change and ensure that it was
+     * successful.
+     */
+    private NetworkUpdateResult verifyUpdateNetworkToWifiConfigManagerWithIpChange(
+            WifiConfiguration configuration) {
+        NetworkUpdateResult result = verifyUpdateNetworkToWifiConfigManager(configuration);
+        assertTrue(result.hasIpChanged());
+        assertTrue(result.hasProxyChanged());
+        return result;
+    }
+
+    /**
+     * Removes network from WifiConfigManager and ensure that it was successful.
+     */
+    private void verifyRemoveNetworkFromWifiConfigManager(
+            WifiConfiguration configuration) {
+        assertTrue(mWifiConfigManager.removeNetwork(configuration.networkId));
+
+        verifyNetworkRemoveBroadcast(configuration);
+        // Verify if the config store write was triggered without this new configuration.
+        verifyNetworkNotInConfigStoreData(configuration);
+    }
+
+    /**
+     * Verifies the provided network's public status and ensures that the network change broadcast
+     * has been sent out.
+     */
+    private void verifyUpdateNetworkStatus(WifiConfiguration configuration, int status) {
+        assertEquals(status, configuration.status);
+        verifyNetworkUpdateBroadcast(configuration);
+    }
+
+    /**
+     * Verifies the network's selection status update.
+     *
+     * For temporarily disabled reasons, the method ensures that the status has changed only if
+     * disable reason counter has exceeded the threshold.
+     *
+     * For permanently disabled/enabled reasons, the method ensures that the public status has
+     * changed and the network change broadcast has been sent out.
+     */
+    private void verifyUpdateNetworkSelectionStatus(
+            int networkId, int reason, int temporaryDisableReasonCounter) {
+        when(mClock.getElapsedSinceBootMillis())
+                .thenReturn(TEST_ELAPSED_UPDATE_NETWORK_SELECTION_TIME_MILLIS);
+
+        // Fetch the current status of the network before we try to update the status.
+        WifiConfiguration retrievedNetwork = mWifiConfigManager.getConfiguredNetwork(networkId);
+        NetworkSelectionStatus currentStatus = retrievedNetwork.getNetworkSelectionStatus();
+        int currentDisableReason = currentStatus.getNetworkSelectionDisableReason();
+
+        // First set the status to the provided reason.
+        assertTrue(mWifiConfigManager.updateNetworkSelectionStatus(networkId, reason));
+
+        // Now fetch the network configuration and verify the new status of the network.
+        retrievedNetwork = mWifiConfigManager.getConfiguredNetwork(networkId);
+
+        NetworkSelectionStatus retrievedStatus = retrievedNetwork.getNetworkSelectionStatus();
+        int retrievedDisableReason = retrievedStatus.getNetworkSelectionDisableReason();
+        long retrievedDisableTime = retrievedStatus.getDisableTime();
+        int retrievedDisableReasonCounter = retrievedStatus.getDisableReasonCounter(reason);
+        int disableReasonThreshold =
+                WifiConfigManagerNew.NETWORK_SELECTION_DISABLE_THRESHOLD[reason];
+
+        if (reason == NetworkSelectionStatus.NETWORK_SELECTION_ENABLE) {
+            assertEquals(reason, retrievedDisableReason);
+            assertTrue(retrievedStatus.isNetworkEnabled());
+            assertEquals(
+                    NetworkSelectionStatus.INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP,
+                    retrievedDisableTime);
+            verifyUpdateNetworkStatus(retrievedNetwork, WifiConfiguration.Status.ENABLED);
+        } else if (reason < NetworkSelectionStatus.DISABLED_TLS_VERSION_MISMATCH) {
+            // For temporarily disabled networks, we need to ensure that the current status remains
+            // until the threshold is crossed.
+            assertEquals(temporaryDisableReasonCounter, retrievedDisableReasonCounter);
+            if (retrievedDisableReasonCounter < disableReasonThreshold) {
+                assertEquals(currentDisableReason, retrievedDisableReason);
+                assertEquals(
+                        currentStatus.getNetworkSelectionStatus(),
+                        retrievedStatus.getNetworkSelectionStatus());
+            } else {
+                assertEquals(reason, retrievedDisableReason);
+                assertTrue(retrievedStatus.isNetworkTemporaryDisabled());
+                assertEquals(
+                        TEST_ELAPSED_UPDATE_NETWORK_SELECTION_TIME_MILLIS, retrievedDisableTime);
+            }
+        } else if (reason < NetworkSelectionStatus.NETWORK_SELECTION_DISABLED_MAX) {
+            assertEquals(reason, retrievedDisableReason);
+            assertTrue(retrievedStatus.isNetworkPermanentlyDisabled());
+            assertEquals(
+                    NetworkSelectionStatus.INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP,
+                    retrievedDisableTime);
+            verifyUpdateNetworkStatus(retrievedNetwork, WifiConfiguration.Status.DISABLED);
+        }
+    }
+
+    /**
+     * Creates a scan detail corresponding to the provided network and BSSID value.
+     */
+    private ScanDetail createScanDetailForNetwork(WifiConfiguration configuration, String bssid) {
+        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, 0, 0, SystemClock.uptimeMillis(), System.currentTimeMillis());
+    }
+
+    /**
+     * Creates a scan detail corresponding to the provided network and fixed BSSID value.
+     */
+    private ScanDetail createScanDetailForNetwork(WifiConfiguration configuration) {
+        return createScanDetailForNetwork(configuration, TEST_BSSID);
+    }
+
+    /**
+     * 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 WifiConfigManagerNew#getSavedNetworkForScanDetailAndCache(ScanDetail)} and also
+     * verifies that the provided scan detail was cached,
+     */
+    private void verifyAddSingleNetworkAndMatchScanDetailToNetworkAndCache(
+            WifiConfiguration network) {
+        // First add the provided network.
+        verifyAddNetworkToWifiConfigManager(network);
+
+        // Now create a dummy scan detail corresponding to the network.
+        ScanDetail scanDetail = createScanDetailForNetwork(network);
+        ScanResult scanResult = scanDetail.getScanResult();
+
+        WifiConfiguration retrievedNetwork =
+                mWifiConfigManager.getSavedNetworkForScanDetailAndCache(scanDetail);
+
+        WifiConfigurationTestUtil.assertConfigurationEqualForConfigManagerAddOrUpdate(
+                network, retrievedNetwork);
+
+        // Now retrieve the scan detail cache and ensure that the new scan detail is in cache.
+        ScanDetailCache retrievedScanDetailCache =
+                mWifiConfigManager.getScanDetailCacheForNetwork(network.networkId);
+        assertEquals(1, retrievedScanDetailCache.size());
+        ScanResult retrievedScanResult = retrievedScanDetailCache.get(scanResult.BSSID);
+
+        ScanTestUtil.assertScanResultEquals(scanResult, retrievedScanResult);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java
index a585000..fd1d66d 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java
@@ -20,6 +20,7 @@
 import static org.junit.Assert.*;
 import static org.mockito.Mockito.*;
 
+import android.app.test.MockAnswerUtil.AnswerWithArguments;
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.net.wifi.FakeKeys;
@@ -45,7 +46,6 @@
 import android.util.SparseArray;
 
 import com.android.server.net.DelayedDiskWrite;
-import com.android.server.wifi.MockAnswerUtil.AnswerWithArguments;
 import com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager;
 import com.android.server.wifi.hotspot2.pps.Credential;
 import com.android.server.wifi.hotspot2.pps.HomeSP;
@@ -420,23 +420,15 @@
                         assertEquals(WifiConfiguration.Status.ENABLED, config2.status);
                     }
                 } else {
-                    // If the network configuration is visible to the current user, verify that it
-                    // was enabled and all other network configurations visible to the user were
-                    // disabled.
+                    // If the network configuration is visible to the current user, verify that
+                    // a connection attempt was made to it. This does not modify the status of
+                    // other networks.
                     assertTrue(success);
                     verify(mWifiNative).selectNetwork(config.networkId);
                     verify(mWifiNative, never()).selectNetwork(intThat(not(config.networkId)));
                     verify(mWifiNative, never()).enableNetwork(config.networkId);
                     verify(mWifiNative, never()).enableNetwork(intThat(not(config.networkId)));
-                    for (WifiConfiguration config2 : mConfiguredNetworks.valuesForAllUsers()) {
-                        if (WifiConfigurationUtil.isVisibleToAnyProfile(config2,
-                                USER_PROFILES.get(userId))
-                                && config2.networkId != config.networkId) {
-                            assertEquals(WifiConfiguration.Status.DISABLED, config2.status);
-                        } else {
-                            assertEquals(WifiConfiguration.Status.ENABLED, config2.status);
-                        }
-                    }
+                    assertEquals(WifiConfiguration.Status.ENABLED, config.status);
                 }
             }
         }
@@ -471,12 +463,13 @@
         // configuration.
         final Map<String, String> metadata = new HashMap<String, String>();
         if (CONFIGS.get(network).FQDN != null) {
-            metadata.put(WifiConfigStore.ID_STRING_KEY_FQDN, CONFIGS.get(network).FQDN);
+            metadata.put(WifiSupplicantControl.ID_STRING_KEY_FQDN, CONFIGS.get(network).FQDN);
         }
-        metadata.put(WifiConfigStore.ID_STRING_KEY_CONFIG_KEY, CONFIGS.get(network).configKey());
-        metadata.put(WifiConfigStore.ID_STRING_KEY_CREATOR_UID,
+        metadata.put(
+                WifiSupplicantControl.ID_STRING_KEY_CONFIG_KEY, CONFIGS.get(network).configKey());
+        metadata.put(WifiSupplicantControl.ID_STRING_KEY_CREATOR_UID,
                 Integer.toString(CONFIGS.get(network).creatorUid));
-        verify(mWifiNative).setNetworkExtra(network, WifiConfigStore.ID_STRING_VAR_NAME,
+        verify(mWifiNative).setNetworkExtra(network, WifiSupplicantControl.ID_STRING_VAR_NAME,
                 metadata);
 
         // Verify that an attempt to read back the requirePMF variable was made.
@@ -576,27 +569,27 @@
                 .thenReturn(encodeConfigSSID(CONFIGS.get(i)));
         }
         // Legacy regular network configuration: No "id_str".
-        when(mWifiNative.getNetworkExtra(0, WifiConfigStore.ID_STRING_VAR_NAME))
+        when(mWifiNative.getNetworkExtra(0, WifiSupplicantControl.ID_STRING_VAR_NAME))
             .thenReturn(null);
         // Legacy Hotspot 2.0 network configuration: Quoted FQDN in "id_str".
-        when(mWifiNative.getNetworkExtra(1, WifiConfigStore.ID_STRING_VAR_NAME))
+        when(mWifiNative.getNetworkExtra(1, WifiSupplicantControl.ID_STRING_VAR_NAME))
             .thenReturn(null);
-        when(mWifiNative.getNetworkVariable(1, WifiConfigStore.ID_STRING_VAR_NAME))
+        when(mWifiNative.getNetworkVariable(1, WifiSupplicantControl.ID_STRING_VAR_NAME))
             .thenReturn('"' + CONFIGS.get(1).FQDN + '"');
         // Up-to-date Hotspot 2.0 network configuration: Metadata in "id_str".
         Map<String, String> metadata = new HashMap<String, String>();
-        metadata.put(WifiConfigStore.ID_STRING_KEY_CONFIG_KEY, CONFIGS.get(2).configKey());
-        metadata.put(WifiConfigStore.ID_STRING_KEY_CREATOR_UID,
+        metadata.put(WifiSupplicantControl.ID_STRING_KEY_CONFIG_KEY, CONFIGS.get(2).configKey());
+        metadata.put(WifiSupplicantControl.ID_STRING_KEY_CREATOR_UID,
                 Integer.toString(CONFIGS.get(2).creatorUid));
-        metadata.put(WifiConfigStore.ID_STRING_KEY_FQDN, CONFIGS.get(2).FQDN);
-        when(mWifiNative.getNetworkExtra(2, WifiConfigStore.ID_STRING_VAR_NAME))
+        metadata.put(WifiSupplicantControl.ID_STRING_KEY_FQDN, CONFIGS.get(2).FQDN);
+        when(mWifiNative.getNetworkExtra(2, WifiSupplicantControl.ID_STRING_VAR_NAME))
             .thenReturn(metadata);
         // Up-to-date regular network configuration: Metadata in "id_str".
         metadata = new HashMap<String, String>();
-        metadata.put(WifiConfigStore.ID_STRING_KEY_CONFIG_KEY, CONFIGS.get(3).configKey());
-        metadata.put(WifiConfigStore.ID_STRING_KEY_CREATOR_UID,
+        metadata.put(WifiSupplicantControl.ID_STRING_KEY_CONFIG_KEY, CONFIGS.get(3).configKey());
+        metadata.put(WifiSupplicantControl.ID_STRING_KEY_CREATOR_UID,
                 Integer.toString(CONFIGS.get(3).creatorUid));
-        when(mWifiNative.getNetworkExtra(3, WifiConfigStore.ID_STRING_VAR_NAME))
+        when(mWifiNative.getNetworkExtra(3, WifiSupplicantControl.ID_STRING_VAR_NAME))
             .thenReturn(metadata);
 
         // Set up networkHistory.txt file.
@@ -661,10 +654,10 @@
         when(mWifiNative.getNetworkVariable(anyInt(), eq(WifiConfiguration.ssidVarName)))
             .thenReturn(encodeConfigSSID(config));
         final Map<String, String> metadata = new HashMap<String, String>();
-        metadata.put(WifiConfigStore.ID_STRING_KEY_CONFIG_KEY, config.configKey());
-        metadata.put(WifiConfigStore.ID_STRING_KEY_CREATOR_UID,
+        metadata.put(WifiSupplicantControl.ID_STRING_KEY_CONFIG_KEY, config.configKey());
+        metadata.put(WifiSupplicantControl.ID_STRING_KEY_CREATOR_UID,
                 Integer.toString(config.creatorUid));
-        when(mWifiNative.getNetworkExtra(anyInt(), eq(WifiConfigStore.ID_STRING_VAR_NAME)))
+        when(mWifiNative.getNetworkExtra(anyInt(), eq(WifiSupplicantControl.ID_STRING_VAR_NAME)))
             .thenReturn(metadata);
 
         // Load network configurations.
@@ -741,16 +734,20 @@
         for (WifiConfiguration config : newConfigs) {
             if (oldUserOnlyConfigs.contains(config)) {
                 verify(mWifiNative).disableNetwork(config.networkId);
-                assertEquals(WifiConfiguration.Status.DISABLED, config.status);
+                assertNetworkStatus(
+                        config,
+                        WifiConfiguration.NetworkSelectionStatus.DISABLED_DUE_TO_USER_SWITCH);
             } else {
                 verify(mWifiNative, never()).disableNetwork(config.networkId);
                 if (neitherUserConfigs.contains(config)) {
-                    assertEquals(WifiConfiguration.Status.DISABLED, config.status);
+                    assertNetworkStatus(
+                            config,
+                            WifiConfiguration.NetworkSelectionStatus.DISABLED_DUE_TO_USER_SWITCH);
                 } else {
-                    // Only enabled in networkSelection.
-                    assertTrue(config.getNetworkSelectionStatus().isNetworkEnabled());
+                    assertNetworkStatus(
+                            config,
+                            WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
                 }
-
             }
         }
     }
@@ -1463,8 +1460,8 @@
         updateRequirePMFConfig.requirePMF = true;
 
         // Set up mock to allow the new value to be read back into the config
-        // TODO: please see b/28088226  - this test is implemented as if WifiConfigStore correctly
-        // read back the boolean value.  When fixed, uncomment the following line and the
+        // TODO: please see b/28088226  - this test is implemented as if WifiSupplicantControl
+        // correctly read back the boolean value. When fixed, uncomment the following line and the
         // checkHasEverConnectedFalse below.
         //when(mWifiNative.getNetworkVariable(BASE_HAS_EVER_CONNECTED_CONFIG.networkId,
         //        WifiConfiguration.pmfVarName)).thenReturn("2");
@@ -1591,7 +1588,6 @@
                 eapConfigSame.enterpriseConfig));
     }
 
-
     private void checkHasEverConnectedTrue(int networkId) {
         WifiConfiguration checkConfig = mWifiConfigManager.getWifiConfiguration(networkId);
         assertTrue("hasEverConnected expected to be true.",
@@ -1627,5 +1623,80 @@
         return buf.toString();
     }
 
+    /**
+     * Test whether enableNetwork with the disableOthers flag set to false enables the
+     * input network, but does not attempt a connection.
+     */
+    @Test
+    public void testEnableNetworkWithoutDisableOthers() throws Exception {
+        addNetworks();
 
+        for (Map.Entry<Integer, List<WifiConfiguration>> entry : VISIBLE_CONFIGS.entrySet()) {
+            switchUser(entry.getKey());
+            // Iterate through all the configs for the current user and invoke |enableNetwork|
+            // on the corresponding config retrieved from WifiConfigManager.
+            for (WifiConfiguration config : entry.getValue()) {
+                WifiConfiguration retrievedConfig =
+                        mWifiConfigManager.getWifiConfiguration(config.networkId);
+                assertTrue(mWifiConfigManager.enableNetwork(retrievedConfig, false, 0));
+                assertNetworkStatus(retrievedConfig,
+                        WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
+                verify(mWifiNative, never()).selectNetwork(anyInt());
+            }
+        }
+    }
+
+    /**
+     * Test whether enableNetwork without the disableOthers flag set to true enables the input
+     * network and attempts a connection to it immediately. It also checks if all the other
+     * networks are disabled.
+     */
+    @Test
+    public void testEnableNetworkWithDisableOthers() throws Exception {
+        addNetworks();
+
+        for (Map.Entry<Integer, List<WifiConfiguration>> entry : VISIBLE_CONFIGS.entrySet()) {
+            switchUser(entry.getKey());
+            // Iterate through all the configs for the current user and invoke |enableNetwork|
+            // on the corresponding config retrieved from WifiConfigManager.
+            for (WifiConfiguration config : entry.getValue()) {
+                reset(mWifiNative);
+                when(mWifiNative.selectNetwork(anyInt())).thenReturn(true);
+                WifiConfiguration retrievedConfig =
+                        mWifiConfigManager.getWifiConfiguration(config.networkId);
+                assertTrue(mWifiConfigManager.enableNetwork(retrievedConfig, true, 0));
+                assertNetworkStatus(retrievedConfig,
+                        WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
+                assertAllNetworksDisabledExcept(retrievedConfig.networkId,
+                        WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WIFI_MANAGER);
+                verify(mWifiNative).selectNetwork(retrievedConfig.networkId);
+                verify(mWifiNative, never()).selectNetwork(intThat(not(retrievedConfig.networkId)));
+            }
+        }
+    }
+
+    private void assertNetworkStatus(WifiConfiguration config, int disableReason) {
+        final WifiConfiguration.NetworkSelectionStatus status = config.getNetworkSelectionStatus();
+        assertEquals(disableReason, status.getNetworkSelectionDisableReason());
+        if (disableReason
+                == WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE) {
+            assertEquals(WifiConfiguration.Status.ENABLED, config.status);
+            assertTrue(config.getNetworkSelectionStatus().isNetworkEnabled());
+        } else if (disableReason
+                < WifiConfiguration.NetworkSelectionStatus.DISABLED_TLS_VERSION_MISMATCH) {
+            assertEquals(WifiConfiguration.Status.ENABLED, config.status);
+            assertTrue(config.getNetworkSelectionStatus().isNetworkTemporaryDisabled());
+        } else {
+            assertEquals(WifiConfiguration.Status.DISABLED, config.status);
+            assertTrue(config.getNetworkSelectionStatus().isNetworkPermanentlyDisabled());
+        }
+    }
+
+    private void assertAllNetworksDisabledExcept(int netId, int disableReason) {
+        for (WifiConfiguration config : mWifiConfigManager.getSavedNetworks()) {
+            if (config.networkId != netId) {
+                assertNetworkStatus(config, disableReason);
+            }
+        }
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreDataTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreDataTest.java
new file mode 100644
index 0000000..7bed17a
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreDataTest.java
@@ -0,0 +1,419 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.WifiConfigStoreData}.
+ */
+@SmallTest
+public class WifiConfigStoreDataTest {
+
+    private static final String TEST_SSID = "WifiConfigStoreDataSSID_";
+    private static final String TEST_CONNECT_CHOICE = "XmlUtilConnectChoice";
+    private static final long TEST_CONNECT_CHOICE_TIMESTAMP = 0x4566;
+    private static final Set<String> TEST_DELETED_EPHEMERAL_LIST = new HashSet<String>() {
+        {
+            add("\"" + TEST_SSID + "1\"");
+            add("\"" + TEST_SSID + "2\"");
+        }
+    };
+    private static final String SINGLE_OPEN_NETWORK_LIST_XML_STRING_FORMAT =
+            "<NetworkList>\n"
+                    + "<Network>\n"
+                    + "<WifiConfiguration>\n"
+                    + "<string name=\"ConfigKey\">%s</string>\n"
+                    + "<string name=\"SSID\">%s</string>\n"
+                    + "<null name=\"BSSID\" />\n"
+                    + "<null name=\"PreSharedKey\" />\n"
+                    + "<null name=\"WEPKeys\" />\n"
+                    + "<int name=\"WEPTxKeyIndex\" value=\"0\" />\n"
+                    + "<boolean name=\"HiddenSSID\" value=\"false\" />\n"
+                    + "<byte-array name=\"AllowedKeyMgmt\" num=\"1\">01</byte-array>\n"
+                    + "<byte-array name=\"AllowedProtocols\" num=\"0\"></byte-array>\n"
+                    + "<byte-array name=\"AllowedAuthAlgos\" num=\"0\"></byte-array>\n"
+                    + "<boolean name=\"Shared\" value=\"%s\" />\n"
+                    + "<null name=\"FQDN\" />\n"
+                    + "<null name=\"ProviderFriendlyName\" />\n"
+                    + "<null name=\"LinkedNetworksList\" />\n"
+                    + "<null name=\"DefaultGwMacAddress\" />\n"
+                    + "<boolean name=\"ValidatedInternetAccess\" value=\"false\" />\n"
+                    + "<boolean name=\"NoInternetAccessExpected\" value=\"false\" />\n"
+                    + "<int name=\"UserApproved\" value=\"0\" />\n"
+                    + "<boolean name=\"MeteredHint\" value=\"false\" />\n"
+                    + "<boolean name=\"UseExternalScores\" value=\"false\" />\n"
+                    + "<int name=\"NumAssociation\" value=\"0\" />\n"
+                    + "<int name=\"CreatorUid\" value=\"%d\" />\n"
+                    + "<null name=\"CreatorName\" />\n"
+                    + "<null name=\"CreationTime\" />\n"
+                    + "<int name=\"LastUpdateUid\" value=\"-1\" />\n"
+                    + "<null name=\"LastUpdateName\" />\n"
+                    + "<int name=\"LastConnectUid\" value=\"0\" />\n"
+                    + "</WifiConfiguration>\n"
+                    + "<NetworkStatus>\n"
+                    + "<int name=\"SelectionStatus\" value=\"0\" />\n"
+                    + "<int name=\"DisableReason\" value=\"0\" />\n"
+                    + "<null name=\"ConnectChoice\" />\n"
+                    + "<long name=\"ConnectChoiceTimeStamp\" value=\"-1\" />\n"
+                    + "<boolean name=\"HasEverConnected\" value=\"false\" />\n"
+                    + "</NetworkStatus>\n"
+                    + "<IpConfiguration>\n"
+                    + "<string name=\"IpAssignment\">DHCP</string>\n"
+                    + "<string name=\"ProxySettings\">NONE</string>\n"
+                    + "</IpConfiguration>\n"
+                    + "</Network>\n"
+                    + "</NetworkList>\n";
+    private static final String SINGLE_OPEN_NETWORK_SHARED_DATA_XML_STRING_FORMAT =
+            "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                    + "<WifiConfigStoreData>\n"
+                    + "<int name=\"Version\" value=\"1\" />\n"
+                    + SINGLE_OPEN_NETWORK_LIST_XML_STRING_FORMAT
+                    + "</WifiConfigStoreData>\n";
+    private static final String SINGLE_OPEN_NETWORK_USER_DATA_XML_STRING_FORMAT =
+            "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                    + "<WifiConfigStoreData>\n"
+                    + "<int name=\"Version\" value=\"1\" />\n"
+                    + SINGLE_OPEN_NETWORK_LIST_XML_STRING_FORMAT
+                    + "<DeletedEphemeralSSIDList>\n"
+                    + "<set name=\"SSIDList\" />\n"
+                    + "</DeletedEphemeralSSIDList>\n"
+                    + "</WifiConfigStoreData>\n";
+
+    /**
+     * Asserts that the 2 config store data are equal.
+     */
+    public static void assertConfigStoreDataEqual(
+            WifiConfigStoreData expected, WifiConfigStoreData actual) {
+        WifiConfigurationTestUtil.assertConfigurationsEqualForConfigStore(
+                expected.configurations, actual.configurations);
+        assertEquals(expected.deletedEphemeralSSIDs, actual.deletedEphemeralSSIDs);
+    }
+
+    /**
+     * Verify that multiple shared networks with different credential types and IpConfiguration
+     * types are serialized and deserialized correctly.
+     */
+    @Test
+    public void testMultipleNetworkAllShared()
+            throws XmlPullParserException, IOException {
+        List<WifiConfiguration> configurations = createNetworks(true);
+        serializeDeserializeConfigStoreData(configurations);
+    }
+
+    /**
+     * Verify that multiple user networks with different credential types and IpConfiguration
+     * types are serialized and deserialized correctly.
+     */
+    @Test
+    public void testMultipleNetworksAllUser()
+            throws XmlPullParserException, IOException {
+        List<WifiConfiguration> configurations = createNetworks(false);
+        serializeDeserializeConfigStoreData(configurations);
+    }
+
+    /**
+     * Verify that multiple networks with different credential types and IpConfiguration
+     * types are serialized and deserialized correctly when both user & shared networks are present.
+     */
+    @Test
+    public void testMultipleNetworksSharedAndUserNetworks()
+            throws XmlPullParserException, IOException {
+        List<WifiConfiguration> configurations = createNetworks();
+        // Let's split the list of networks into 2 and make all the networks in the first list
+        // shared and the second list all user networks.
+        int listSize = configurations.size();
+        List<WifiConfiguration> sharedConfigurations = configurations.subList(0, listSize / 2);
+        List<WifiConfiguration> userConfigurations = configurations.subList(listSize / 2, listSize);
+        for (WifiConfiguration config : sharedConfigurations) {
+            config.shared = true;
+        }
+        for (WifiConfiguration config : userConfigurations) {
+            config.shared = false;
+        }
+        serializeDeserializeConfigStoreData(configurations);
+    }
+
+    /**
+     * Verify that multiple shared networks with different credential types and IpConfiguration
+     * types are serialized and deserialized correctly when the shared data bytes are null in
+     * |parseRawData| method.
+     */
+    @Test
+    public void testMultipleNetworksSharedDataNullInParseRawData()
+            throws XmlPullParserException, IOException {
+        List<WifiConfiguration> configurations = createNetworks(false);
+        serializeDeserializeConfigStoreData(configurations, true, false);
+    }
+
+    /**
+     * Verify that multiple shared networks with different credential types and IpConfiguration
+     * types are serialized and deserialized correctly when the user data bytes are null in
+     * |parseRawData| method.
+     */
+    @Test
+    public void testMultipleNetworksUserDataNullInParseRawData()
+            throws XmlPullParserException, IOException {
+        List<WifiConfiguration> configurations = createNetworks(true);
+        serializeDeserializeConfigStoreData(configurations, false, true);
+    }
+
+    /**
+     * Verify that the manually populated xml string for is deserialized/serialized correctly.
+     * This generates a store data corresponding to the XML string and verifies that the string
+     * is indeed parsed correctly to the store data.
+     */
+    @Test
+    public void testManualConfigStoreDataParse() {
+        WifiConfiguration sharedNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        sharedNetwork.shared = true;
+        sharedNetwork.setIpConfiguration(WifiConfigurationTestUtil.createDHCPIpConfigurationWithNoProxy());
+        WifiConfiguration userNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        userNetwork.setIpConfiguration(WifiConfigurationTestUtil.createDHCPIpConfigurationWithNoProxy());
+        userNetwork.shared = false;
+
+        // Create the store data for comparison.
+        List<WifiConfiguration> networks = new ArrayList<>();
+        networks.add(sharedNetwork);
+        networks.add(userNetwork);
+        WifiConfigStoreData storeData =
+                new WifiConfigStoreData(networks, new HashSet<String>());
+
+        String sharedStoreXmlString =
+                String.format(SINGLE_OPEN_NETWORK_SHARED_DATA_XML_STRING_FORMAT,
+                        sharedNetwork.configKey().replaceAll("\"", "&quot;"),
+                        sharedNetwork.SSID.replaceAll("\"", "&quot;"),
+                        sharedNetwork.shared, sharedNetwork.creatorUid);
+        String userStoreXmlString =
+                String.format(SINGLE_OPEN_NETWORK_USER_DATA_XML_STRING_FORMAT,
+                        userNetwork.configKey().replaceAll("\"", "&quot;"),
+                        userNetwork.SSID.replaceAll("\"", "&quot;"),
+                        userNetwork.shared, userNetwork.creatorUid);
+        byte[] rawSharedData = sharedStoreXmlString.getBytes();
+        byte[] rawUserData = userStoreXmlString.getBytes();
+        WifiConfigStoreData retrievedStoreData = null;
+        try {
+            retrievedStoreData = WifiConfigStoreData.parseRawData(rawSharedData, rawUserData);
+        } catch (Exception e) {
+            // Assert if an exception was raised.
+            fail("Error in parsing the xml data: " + e
+                    + ". Shared data: " + sharedStoreXmlString
+                    + ", User data: " + userStoreXmlString);
+        }
+        // Compare the retrieved config store data with the original.
+        assertConfigStoreDataEqual(storeData, retrievedStoreData);
+
+        // Now convert the store data to XML bytes and compare the output with the expected string.
+        byte[] retrievedSharedStoreXmlBytes = null;
+        byte[] retrievedUserStoreXmlBytes = null;
+        try {
+            retrievedSharedStoreXmlBytes = retrievedStoreData.createSharedRawData();
+            retrievedUserStoreXmlBytes = retrievedStoreData.createUserRawData();
+        } catch (Exception e) {
+            // Assert if an exception was raised.
+            fail("Error in writing the xml data: " + e);
+        }
+        String retrievedSharedStoreXmlString =
+                new String(retrievedSharedStoreXmlBytes, StandardCharsets.UTF_8);
+        String retrievedUserStoreXmlString =
+                new String(retrievedUserStoreXmlBytes, StandardCharsets.UTF_8);
+        assertEquals("Retrieved: " + retrievedSharedStoreXmlString
+                + ", Expected: " + sharedStoreXmlString,
+                sharedStoreXmlString, retrievedSharedStoreXmlString);
+        assertEquals("Retrieved: " + retrievedUserStoreXmlString
+                + ", Expected: " + userStoreXmlString,
+                userStoreXmlString, retrievedUserStoreXmlString);
+    }
+
+    /**
+     * Verify that XML with corrupted version provided to WifiConfigStoreData is ignored correctly.
+     */
+    @Test
+    public void testCorruptVersionConfigStoreData() {
+        String storeDataAsString =
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                        + "<WifiConfigStoreData>\n"
+                        + "<int name=\"Version\" value=\"200\" />\n"
+                        + "</WifiConfigStoreData>\n";
+        byte[] rawData = storeDataAsString.getBytes();
+        try {
+            WifiConfigStoreData storeData = WifiConfigStoreData.parseRawData(rawData, rawData);
+        } catch (Exception e) {
+            return;
+        }
+        // Assert if there was no exception was raised.
+        fail();
+    }
+
+    /**
+     * Verify that XML with no network list provided to WifiConfigStoreData is ignored correctly.
+     */
+    @Test
+    public void testCorruptNetworkListConfigStoreData() {
+        String storeDataAsString =
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                        + "<WifiConfigStoreData>\n"
+                        + "<int name=\"Version\" value=\"1\" />\n"
+                        + "</WifiConfigStoreData>\n";
+        byte[] rawData = storeDataAsString.getBytes();
+        try {
+            WifiConfigStoreData storeData = WifiConfigStoreData.parseRawData(rawData, rawData);
+        } catch (Exception e) {
+            return;
+        }
+        // Assert if there was no exception was raised.
+        fail();
+    }
+
+    /**
+     * Verify that any corrupted data provided to WifiConfigStoreData is ignored correctly.
+     */
+    @Test
+    public void testRandomCorruptConfigStoreData() {
+        Random random = new Random();
+        byte[] rawData = new byte[100];
+        random.nextBytes(rawData);
+        try {
+            WifiConfigStoreData storeData = WifiConfigStoreData.parseRawData(rawData, rawData);
+        } catch (Exception e) {
+            return;
+        }
+        // Assert if there was no exception was raised.
+        fail();
+    }
+
+    /**
+     * Helper method to add 4 networks with different credential types, IpConfiguration
+     * types for all tests in the class.
+     *
+     * @return
+     */
+    private List<WifiConfiguration> createNetworks() {
+        List<WifiConfiguration> configurations = new ArrayList<>();
+
+        WifiConfiguration wepNetwork = WifiConfigurationTestUtil.createWepNetwork();
+        wepNetwork.setIpConfiguration(WifiConfigurationTestUtil.createDHCPIpConfigurationWithPacProxy());
+        wepNetwork.getNetworkSelectionStatus().setNetworkSelectionStatus(
+                NetworkSelectionStatus.NETWORK_SELECTION_ENABLED);
+        configurations.add(wepNetwork);
+
+        WifiConfiguration pskNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        pskNetwork.setIpConfiguration(WifiConfigurationTestUtil.createStaticIpConfigurationWithPacProxy());
+        pskNetwork.getNetworkSelectionStatus().setNetworkSelectionStatus(
+                NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED);
+        pskNetwork.getNetworkSelectionStatus().setNetworkSelectionDisableReason(
+                NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION);
+        configurations.add(pskNetwork);
+
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        openNetwork.setIpConfiguration(WifiConfigurationTestUtil.createStaticIpConfigurationWithStaticProxy());
+        openNetwork.getNetworkSelectionStatus().setNetworkSelectionStatus(
+                NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED);
+        openNetwork.getNetworkSelectionStatus().setNetworkSelectionDisableReason(
+                NetworkSelectionStatus.DISABLED_BY_WIFI_MANAGER);
+        configurations.add(openNetwork);
+
+        WifiConfiguration eapNetwork = WifiConfigurationTestUtil.createEapNetwork();
+        eapNetwork.setIpConfiguration(WifiConfigurationTestUtil.createPartialStaticIpConfigurationWithPacProxy());
+        eapNetwork.getNetworkSelectionStatus().setConnectChoice(TEST_CONNECT_CHOICE);
+        eapNetwork.getNetworkSelectionStatus().setConnectChoiceTimestamp(
+                TEST_CONNECT_CHOICE_TIMESTAMP);
+        eapNetwork.getNetworkSelectionStatus().setHasEverConnected(true);
+        configurations.add(eapNetwork);
+
+        return configurations;
+    }
+
+    private List<WifiConfiguration> createNetworks(boolean shared) {
+        List<WifiConfiguration> configurations = createNetworks();
+        for (WifiConfiguration config : configurations) {
+            config.shared = shared;
+        }
+        return configurations;
+    }
+
+    /**
+     * Helper method to serialize/deserialize store data.
+     */
+    private void serializeDeserializeConfigStoreData(List<WifiConfiguration> configurations)
+            throws XmlPullParserException, IOException {
+        serializeDeserializeConfigStoreData(configurations, false, false);
+    }
+
+    /**
+     * Helper method to ensure the the provided config store data is serialized/deserialized
+     * correctly.
+     * This method serialize the provided config store data instance to raw bytes in XML format
+     * and then deserialzes the raw bytes back to a config store data instance. It then
+     * compares that the original config store data matches with the deserialzed instance.
+     *
+     * @param configurations list of configurations to be added in the store data instance.
+     * @param setSharedDataNull whether to set the shared data to null to simulate the non-existence
+     *                          of the shared store file.
+     * @param setUserDataNull whether to set the user data to null to simulate the non-existence
+     *                        of the user store file.
+     */
+    private void serializeDeserializeConfigStoreData(
+            List<WifiConfiguration> configurations, boolean setSharedDataNull,
+            boolean setUserDataNull)
+            throws XmlPullParserException, IOException {
+        // Will not work if both the flags are set because then we need to ignore the configuration
+        // list as well.
+        assertFalse(setSharedDataNull & setUserDataNull);
+
+        Set<String> deletedEphemeralList;
+        if (setUserDataNull) {
+            deletedEphemeralList = new HashSet<>();
+        } else {
+            deletedEphemeralList = TEST_DELETED_EPHEMERAL_LIST;
+        }
+
+        // Serialize the data.
+        WifiConfigStoreData storeData =
+                new WifiConfigStoreData(configurations, deletedEphemeralList);
+
+        byte[] sharedDataBytes = null;
+        byte[] userDataBytes = null;
+        if (!setSharedDataNull) {
+            sharedDataBytes = storeData.createSharedRawData();
+        }
+        if (!setUserDataNull) {
+            userDataBytes = storeData.createUserRawData();
+        }
+
+        // Deserialize the data.
+        WifiConfigStoreData retrievedStoreData =
+                WifiConfigStoreData.parseRawData(sharedDataBytes, userDataBytes);
+        assertConfigStoreDataEqual(storeData, retrievedStoreData);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreNewTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreNewTest.java
new file mode 100644
index 0000000..462a229
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreNewTest.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import static com.android.server.wifi.WifiConfigStoreDataTest.assertConfigStoreDataEqual;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+import android.app.test.TestAlarmManager;
+import android.content.Context;
+import android.net.wifi.WifiConfiguration;
+import android.os.test.TestLooper;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.wifi.WifiConfigStoreNew.StoreFile;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.WifiConfigStoreNew}.
+ */
+@SmallTest
+public class WifiConfigStoreNewTest {
+    // Test mocks
+    @Mock private Context mContext;
+    private TestAlarmManager mAlarmManager;
+    private TestLooper mLooper;
+    @Mock private Clock mClock;
+    private MockStoreFile mSharedStore;
+    private MockStoreFile mUserStore;
+
+    /**
+     * Test instance of WifiConfigStore.
+     */
+    private WifiConfigStoreNew mWifiConfigStore;
+
+    /**
+     * Setup mocks before the test starts.
+     */
+    private void setupMocks() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mAlarmManager = new TestAlarmManager();
+        mLooper = new TestLooper();
+        when(mContext.getSystemService(Context.ALARM_SERVICE))
+                .thenReturn(mAlarmManager.getAlarmManager());
+        mUserStore = new MockStoreFile();
+        mSharedStore = new MockStoreFile();
+    }
+
+    /**
+     * Setup the test environment.
+     */
+    @Before
+    public void setUp() throws Exception {
+        setupMocks();
+
+        mWifiConfigStore =
+                new WifiConfigStoreNew(
+                        mContext, mLooper.getLooper(), mClock, mSharedStore, mUserStore);
+
+        // Enable verbose logging before tests.
+        mWifiConfigStore.enableVerboseLogging(true);
+    }
+
+    /**
+     * Called after each test
+     */
+    @After
+    public void cleanup() {
+        validateMockitoUsage();
+    }
+
+    /**
+     * Tests the write API with the force flag set to true.
+     * Expected behavior: This should trigger an immediate write to the store files and no alarms
+     * should be started.
+     */
+    @Test
+    public void testForceWrite() throws Exception {
+        mWifiConfigStore.write(true, getEmptyStoreData());
+
+        assertFalse(mAlarmManager.isPending(WifiConfigStoreNew.BUFFERED_WRITE_ALARM_TAG));
+        assertTrue(mSharedStore.isStoreWritten());
+        assertTrue(mUserStore.isStoreWritten());
+    }
+
+    /**
+     * Tests the write API with the force flag set to false.
+     * Expected behavior: This should set an alarm to write to the store files.
+     */
+    @Test
+    public void testBufferedWrite() throws Exception {
+        mWifiConfigStore.write(false, getEmptyStoreData());
+
+        assertTrue(mAlarmManager.isPending(WifiConfigStoreNew.BUFFERED_WRITE_ALARM_TAG));
+        assertFalse(mSharedStore.isStoreWritten());
+        assertFalse(mUserStore.isStoreWritten());
+
+        // Now send the alarm and ensure that the writes happen.
+        mAlarmManager.dispatch(WifiConfigStoreNew.BUFFERED_WRITE_ALARM_TAG);
+        mLooper.dispatchAll();
+        assertTrue(mSharedStore.isStoreWritten());
+        assertTrue(mUserStore.isStoreWritten());
+    }
+
+    /**
+     * Tests the force write after a buffered write.
+     * Expected behaviour: The force write should override the previous buffered write and stop the
+     * buffer write alarms.
+     */
+    @Test
+    public void testForceWriteAfterBufferedWrite() throws Exception {
+        WifiConfigStoreData bufferedStoreData = createSingleOpenNetworkStoreData();
+        mWifiConfigStore.write(false, bufferedStoreData);
+
+        assertTrue(mAlarmManager.isPending(WifiConfigStoreNew.BUFFERED_WRITE_ALARM_TAG));
+        assertFalse(mSharedStore.isStoreWritten());
+        assertFalse(mUserStore.isStoreWritten());
+
+        // Now send a force write and ensure that the writes have been performed and alarms have
+        // been stopped.
+        WifiConfigStoreData forcedStoreData = createSinglePskNetworkStoreData();
+        mWifiConfigStore.write(true, forcedStoreData);
+
+        assertFalse(mAlarmManager.isPending(WifiConfigStoreNew.BUFFERED_WRITE_ALARM_TAG));
+        assertTrue(mSharedStore.isStoreWritten());
+        assertTrue(mUserStore.isStoreWritten());
+
+        // Now deserialize the data and ensure that the configuration retrieved matches the force
+        // write data.
+        WifiConfigStoreData retrievedStoreData =
+                WifiConfigStoreData.parseRawData(
+                        mSharedStore.getStoreBytes(), mUserStore.getStoreBytes());
+
+        assertConfigStoreDataEqual(forcedStoreData, retrievedStoreData);
+    }
+
+    /**
+     * Tests the read API behaviour when there is no file on the device.
+     * Expected behaviour: The read should return an empty store data instance when the file not
+     * found exception is raised.
+     */
+    @Test
+    public void testReadWithNoStoreFile() throws Exception {
+        // Reading the mock store without a write should simulate the file not found case because
+        // |readRawData| would return null.
+        WifiConfigStoreData readData = mWifiConfigStore.read();
+        assertConfigStoreDataEqual(getEmptyStoreData(), readData);
+    }
+
+    /**
+     * Tests the read API behaviour after a write to the store file.
+     * Expected behaviour: The read should return the same data that was last written.
+     */
+    @Test
+    public void testReadAfterWrite() throws Exception {
+        WifiConfigStoreData writeData = createSingleOpenNetworkStoreData();
+        mWifiConfigStore.write(true, writeData);
+        WifiConfigStoreData readData = mWifiConfigStore.read();
+
+        assertConfigStoreDataEqual(writeData, readData);
+    }
+
+    /**
+     * Returns an empty store data object.
+     */
+    private WifiConfigStoreData getEmptyStoreData() {
+        return new WifiConfigStoreData(new ArrayList<WifiConfiguration>(), new HashSet<String>());
+    }
+
+    /**
+     * Returns an store data object with a single open network.
+     */
+    private WifiConfigStoreData createSingleOpenNetworkStoreData() {
+        List<WifiConfiguration> configurations = new ArrayList<>();
+        configurations.add(WifiConfigurationTestUtil.createOpenNetwork());
+        return new WifiConfigStoreData(configurations, new HashSet<String>());
+    }
+
+    /**
+     * Returns an store data object with a single psk network.
+     */
+    private WifiConfigStoreData createSinglePskNetworkStoreData() {
+        List<WifiConfiguration> configurations = new ArrayList<>();
+        configurations.add(WifiConfigurationTestUtil.createPskNetwork());
+        return new WifiConfigStoreData(configurations, new HashSet<String>());
+    }
+
+    /**
+     * Mock Store File to redirect all file writes from WifiConfigStoreNew to local buffers.
+     * This can be used to examine the data output by WifiConfigStoreNew.
+     */
+    private class MockStoreFile extends StoreFile {
+        private byte[] mStoreBytes;
+        private boolean mStoreWritten;
+
+        public MockStoreFile() {
+            super(new File("MockStoreFile"));
+        }
+
+        @Override
+        public byte[] readRawData() {
+            return mStoreBytes;
+        }
+
+        @Override
+        public void storeRawDataToWrite(byte[] data) {
+            mStoreBytes = data;
+            mStoreWritten = false;
+        }
+
+        @Override
+        public void writeBufferedRawData() {
+            mStoreWritten = true;
+        }
+
+        public byte[] getStoreBytes() {
+            return mStoreBytes;
+        }
+
+        public boolean isStoreWritten() {
+            return mStoreWritten;
+        }
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConfigurationTestUtil.java b/tests/wifitests/src/com/android/server/wifi/WifiConfigurationTestUtil.java
index 7117c2a..39bfd16 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiConfigurationTestUtil.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiConfigurationTestUtil.java
@@ -16,8 +16,22 @@
 
 package com.android.server.wifi;
 
+import static org.junit.Assert.*;
+
+import android.net.IpConfiguration;
+import android.net.LinkAddress;
+import android.net.NetworkUtils;
+import android.net.ProxyInfo;
+import android.net.StaticIpConfiguration;
+import android.net.wifi.FakeKeys;
 import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
 import android.net.wifi.WifiEnterpriseConfig;
+import android.text.TextUtils;
+
+import java.net.InetAddress;
+import java.security.cert.X509Certificate;
+import java.util.List;
 
 /**
  * Helper for creating and populating WifiConfigurations in unit tests.
@@ -33,6 +47,45 @@
     public static final int SECURITY_EAP =  1 << 2;
 
     /**
+     * These values are used to describe ip configuration parameters for a network.
+     */
+    public static final int STATIC_IP_ASSIGNMENT = 0;
+    public static final int DHCP_IP_ASSIGNMENT = 1;
+    public static final int STATIC_PROXY_SETTING = 0;
+    public static final int PAC_PROXY_SETTING = 1;
+    public static final int NONE_PROXY_SETTING = 2;
+
+    /**
+     * These are constants used to generate predefined WifiConfiguration objects.
+     */
+    public static final int TEST_NETWORK_ID = -1;
+    public static final int TEST_UID = 1;
+    public static final String TEST_SSID = "WifiConfigurationTestUtilSSID_";
+    public static final String TEST_PSK = "WifiConfigurationTestUtilPsk";
+    public static final String[] TEST_WEP_KEYS =
+            {"WifiConfigurationTestUtilWep1", "WifiConfigurationTestUtilWep2",
+                    "WifiConfigurationTestUtilWep3", "WifiConfigurationTestUtilWep3"};
+    public static final int TEST_WEP_TX_KEY_INDEX = 1;
+    public static final String TEST_FQDN = "WifiConfigurationTestUtilFQDN";
+    public static final String TEST_PROVIDER_FRIENDLY_NAME =
+            "WifiConfigurationTestUtilFriendlyName";
+    public static final String TEST_STATIC_IP_LINK_ADDRESS = "192.168.48.2";
+    public static final int TEST_STATIC_IP_LINK_PREFIX_LENGTH = 8;
+    public static final String TEST_STATIC_IP_GATEWAY_ADDRESS = "192.168.48.1";
+    public static final String[] TEST_STATIC_IP_DNS_SERVER_ADDRESSES =
+            new String[]{"192.168.48.1", "192.168.48.10"};
+    public static final String TEST_STATIC_PROXY_HOST = "192.168.48.1";
+    public static final int TEST_STATIC_PROXY_PORT = 8000;
+    public static final String TEST_STATIC_PROXY_EXCLUSION_LIST = "";
+    public static final String TEST_PAC_PROXY_LOCATION = "http://";
+    public static final String TEST_CA_CERT_ALIAS = "WifiConfigurationTestUtilCaCertAlias";
+
+    /**
+     * Index used to assign unique SSIDs for the generation of predefined WifiConfiguration objects.
+     */
+    private static int sNetworkIndex = 0;
+
+    /**
      * Construct a {@link android.net.wifi.WifiConfiguration}.
      * @param networkId the configuration's networkId
      * @param uid the configuration's creator uid
@@ -78,10 +131,10 @@
         WifiConfiguration config = generateWifiConfig(networkId, uid, ssid, shared, enabled, fqdn,
                 providerFriendlyName);
 
-        if (security == SECURITY_NONE) {
+        if ((security == SECURITY_NONE) || ((security & SECURITY_WEP) != 0)) {
             config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         } else {
-            if (((security & SECURITY_WEP) != 0) || ((security & SECURITY_PSK) != 0)) {
+            if ((security & SECURITY_PSK) != 0) {
                 config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
             }
 
@@ -92,4 +145,416 @@
         }
         return config;
     }
+
+    /**
+     * Construct a {@link android.net.IpConfiguration }.
+     * @param ipAssignmentType One of {@link #STATIC_IP_ASSIGNMENT} or {@link #DHCP_IP_ASSIGNMENT}.
+     * @param proxySettingType One of {@link #STATIC_PROXY_SETTING} or {@link #PAC_PROXY_SETTING} or
+     *                        {@link #NONE_PROXY_SETTING}.
+     * @param linkAddress static ip address string.
+     * @param linkPrefixLength static ip address prefix length.
+     * @param gatewayAddress static gateway address.
+     * @param dnsServerAddresses list of dns servers for static ip configuration.
+     * @param proxyHost Static proxy server address.
+     * @param proxyPort Static proxy server port.
+     * @param proxyExclusionList Static proxy exclusion list.
+     * @param pacProxyPath Pac proxy server path.
+     * @return the constructed {@link android.net.IpConfiguration}
+     */
+    public static IpConfiguration generateIpConfig(
+            int ipAssignmentType, int proxySettingType, String linkAddress, int linkPrefixLength,
+            String gatewayAddress, String[] dnsServerAddresses, String proxyHost,
+            int proxyPort, String proxyExclusionList, String pacProxyPath) {
+        StaticIpConfiguration staticIpConfiguration = null;
+        ProxyInfo proxyInfo = null;
+        IpConfiguration.IpAssignment ipAssignment = IpConfiguration.IpAssignment.UNASSIGNED;
+        IpConfiguration.ProxySettings proxySettings = IpConfiguration.ProxySettings.UNASSIGNED;
+
+        if (ipAssignmentType == STATIC_IP_ASSIGNMENT) {
+            staticIpConfiguration = new StaticIpConfiguration();
+            if (!TextUtils.isEmpty(linkAddress)) {
+                LinkAddress linkAddr =
+                        new LinkAddress(
+                                NetworkUtils.numericToInetAddress(linkAddress), linkPrefixLength);
+                staticIpConfiguration.ipAddress = linkAddr;
+            }
+
+            if (!TextUtils.isEmpty(gatewayAddress)) {
+                InetAddress gatewayAddr =
+                        NetworkUtils.numericToInetAddress(gatewayAddress);
+                staticIpConfiguration.gateway = gatewayAddr;
+            }
+            if (dnsServerAddresses != null) {
+                for (String dnsServerAddress : dnsServerAddresses) {
+                    if (!TextUtils.isEmpty(dnsServerAddress)) {
+                        staticIpConfiguration.dnsServers.add(
+                                NetworkUtils.numericToInetAddress(dnsServerAddress));
+                    }
+
+                }
+            }
+            ipAssignment = IpConfiguration.IpAssignment.STATIC;
+        } else if (ipAssignmentType == DHCP_IP_ASSIGNMENT) {
+            ipAssignment = IpConfiguration.IpAssignment.DHCP;
+        }
+
+        if (proxySettingType == STATIC_PROXY_SETTING) {
+            proxyInfo = new ProxyInfo(proxyHost, proxyPort, proxyExclusionList);
+            proxySettings = IpConfiguration.ProxySettings.STATIC;
+        } else if (proxySettingType == PAC_PROXY_SETTING) {
+            proxyInfo = new ProxyInfo(pacProxyPath);
+            proxySettings = IpConfiguration.ProxySettings.PAC;
+        } else if (proxySettingType == NONE_PROXY_SETTING) {
+            proxySettings = IpConfiguration.ProxySettings.NONE;
+        }
+        return new IpConfiguration(ipAssignment, proxySettings, staticIpConfiguration, proxyInfo);
+    }
+
+    /**
+     * Create a new SSID for the the network being created.
+     */
+    private static String createNewSSID() {
+        return "\"" + TEST_SSID + sNetworkIndex++ + "\"";
+    }
+
+    /**
+     * Helper methods to generate predefined WifiConfiguration objects of the required type. These
+     * use a static index to avoid duplicate configurations.
+     */
+    public static WifiConfiguration createOpenNetwork() {
+        return generateWifiConfig(TEST_NETWORK_ID, TEST_UID, createNewSSID(), true, true, null,
+                null, SECURITY_NONE);
+    }
+
+    public static WifiConfiguration createOpenHiddenNetwork() {
+        WifiConfiguration configuration = createOpenNetwork();
+        configuration.hiddenSSID = true;
+        return configuration;
+    }
+
+    public static WifiConfiguration createPskNetwork() {
+        WifiConfiguration configuration =
+                generateWifiConfig(TEST_NETWORK_ID, TEST_UID, createNewSSID(), true, true, null,
+                        null, SECURITY_PSK);
+        configuration.preSharedKey = TEST_PSK;
+        return configuration;
+    }
+
+    public static WifiConfiguration createPskHiddenNetwork() {
+        WifiConfiguration configuration = createPskNetwork();
+        configuration.hiddenSSID = true;
+        return configuration;
+    }
+
+    public static WifiConfiguration createWepNetwork() {
+        WifiConfiguration configuration =
+                generateWifiConfig(TEST_NETWORK_ID, TEST_UID, createNewSSID(), true, true, null,
+                        null, SECURITY_WEP);
+        configuration.wepKeys = TEST_WEP_KEYS;
+        configuration.wepTxKeyIndex = TEST_WEP_TX_KEY_INDEX;
+        return configuration;
+    }
+
+    public static WifiConfiguration createWepHiddenNetwork() {
+        WifiConfiguration configuration = createWepNetwork();
+        configuration.hiddenSSID = true;
+        return configuration;
+    }
+
+
+    public static WifiConfiguration createWepNetworkWithSingleKey() {
+        WifiConfiguration configuration =
+                generateWifiConfig(TEST_NETWORK_ID, TEST_UID, createNewSSID(), true, true, null,
+                        null, SECURITY_WEP);
+        configuration.wepKeys[0] = TEST_WEP_KEYS[0];
+        configuration.wepTxKeyIndex = 0;
+        return configuration;
+    }
+
+
+    public static WifiConfiguration createEapNetwork() {
+        WifiConfiguration configuration =
+                generateWifiConfig(TEST_NETWORK_ID, TEST_UID, createNewSSID(), true, true,
+                        TEST_FQDN, TEST_PROVIDER_FRIENDLY_NAME, SECURITY_EAP);
+        return configuration;
+    }
+
+    public static IpConfiguration createStaticIpConfigurationWithPacProxy() {
+        return generateIpConfig(
+                STATIC_IP_ASSIGNMENT, PAC_PROXY_SETTING,
+                TEST_STATIC_IP_LINK_ADDRESS, TEST_STATIC_IP_LINK_PREFIX_LENGTH,
+                TEST_STATIC_IP_GATEWAY_ADDRESS, TEST_STATIC_IP_DNS_SERVER_ADDRESSES,
+                TEST_STATIC_PROXY_HOST, TEST_STATIC_PROXY_PORT, TEST_STATIC_PROXY_EXCLUSION_LIST,
+                TEST_PAC_PROXY_LOCATION);
+    }
+
+    public static IpConfiguration createStaticIpConfigurationWithStaticProxy() {
+        return generateIpConfig(
+                STATIC_IP_ASSIGNMENT, STATIC_PROXY_SETTING,
+                TEST_STATIC_IP_LINK_ADDRESS, TEST_STATIC_IP_LINK_PREFIX_LENGTH,
+                TEST_STATIC_IP_GATEWAY_ADDRESS, TEST_STATIC_IP_DNS_SERVER_ADDRESSES,
+                TEST_STATIC_PROXY_HOST, TEST_STATIC_PROXY_PORT, TEST_STATIC_PROXY_EXCLUSION_LIST,
+                TEST_PAC_PROXY_LOCATION);
+    }
+
+    public static IpConfiguration createPartialStaticIpConfigurationWithPacProxy() {
+        return generateIpConfig(
+                STATIC_IP_ASSIGNMENT, PAC_PROXY_SETTING,
+                TEST_STATIC_IP_LINK_ADDRESS, TEST_STATIC_IP_LINK_PREFIX_LENGTH,
+                null, null,
+                TEST_STATIC_PROXY_HOST, TEST_STATIC_PROXY_PORT, TEST_STATIC_PROXY_EXCLUSION_LIST,
+                TEST_PAC_PROXY_LOCATION);
+    }
+
+    public static IpConfiguration createDHCPIpConfigurationWithPacProxy() {
+        return generateIpConfig(
+                DHCP_IP_ASSIGNMENT, PAC_PROXY_SETTING,
+                TEST_STATIC_IP_LINK_ADDRESS, TEST_STATIC_IP_LINK_PREFIX_LENGTH,
+                TEST_STATIC_IP_GATEWAY_ADDRESS, TEST_STATIC_IP_DNS_SERVER_ADDRESSES,
+                TEST_STATIC_PROXY_HOST, TEST_STATIC_PROXY_PORT, TEST_STATIC_PROXY_EXCLUSION_LIST,
+                TEST_PAC_PROXY_LOCATION);
+    }
+
+    public static IpConfiguration createDHCPIpConfigurationWithStaticProxy() {
+        return generateIpConfig(
+                DHCP_IP_ASSIGNMENT, STATIC_PROXY_SETTING,
+                TEST_STATIC_IP_LINK_ADDRESS, TEST_STATIC_IP_LINK_PREFIX_LENGTH,
+                TEST_STATIC_IP_GATEWAY_ADDRESS, TEST_STATIC_IP_DNS_SERVER_ADDRESSES,
+                TEST_STATIC_PROXY_HOST, TEST_STATIC_PROXY_PORT, TEST_STATIC_PROXY_EXCLUSION_LIST,
+                TEST_PAC_PROXY_LOCATION);
+    }
+
+    public static IpConfiguration createDHCPIpConfigurationWithNoProxy() {
+        return generateIpConfig(
+                DHCP_IP_ASSIGNMENT, NONE_PROXY_SETTING,
+                TEST_STATIC_IP_LINK_ADDRESS, TEST_STATIC_IP_LINK_PREFIX_LENGTH,
+                TEST_STATIC_IP_GATEWAY_ADDRESS, TEST_STATIC_IP_DNS_SERVER_ADDRESSES,
+                TEST_STATIC_PROXY_HOST, TEST_STATIC_PROXY_PORT, TEST_STATIC_PROXY_EXCLUSION_LIST,
+                TEST_PAC_PROXY_LOCATION);
+    }
+
+    // TODO: These enterprise configurations may need more parameters set.
+    public static WifiEnterpriseConfig createPEAPWifiEnterpriseConfigWithGTCPhase2() {
+        WifiEnterpriseConfig config = new WifiEnterpriseConfig();
+        config.setEapMethod(WifiEnterpriseConfig.Eap.PEAP);
+        config.setPhase2Method(WifiEnterpriseConfig.Phase2.GTC);
+        config.setCaCertificateAliases(new String[] {TEST_CA_CERT_ALIAS + "PEAP"});
+        config.setCaCertificates(new X509Certificate[] {FakeKeys.CA_CERT0, FakeKeys.CA_CERT1});
+        return config;
+    }
+
+    public static WifiEnterpriseConfig createTLSWifiEnterpriseConfigWithNonePhase2() {
+        WifiEnterpriseConfig config = new WifiEnterpriseConfig();
+        config.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+        config.setPhase2Method(WifiEnterpriseConfig.Phase2.NONE);
+        config.setCaCertificateAliases(new String[] {TEST_CA_CERT_ALIAS + "TLS"});
+        config.setCaCertificates(new X509Certificate[] {FakeKeys.CA_CERT0, FakeKeys.CA_CERT1});
+        return config;
+    }
+
+    /**
+     * Asserts that the 2 WifiConfigurations are equal in the elements saved for both backup/restore
+     * and config store.
+     */
+    private static void assertCommonConfigurationElementsEqual(
+            WifiConfiguration expected, WifiConfiguration actual) {
+        assertEquals(expected.SSID, actual.SSID);
+        assertEquals(expected.BSSID, actual.BSSID);
+        assertEquals(expected.preSharedKey, actual.preSharedKey);
+        assertEquals(expected.wepKeys, actual.wepKeys);
+        assertEquals(expected.wepTxKeyIndex, actual.wepTxKeyIndex);
+        assertEquals(expected.hiddenSSID, actual.hiddenSSID);
+        assertEquals(expected.allowedKeyManagement, actual.allowedKeyManagement);
+        assertEquals(expected.allowedProtocols, actual.allowedProtocols);
+        assertEquals(expected.allowedAuthAlgorithms, actual.allowedAuthAlgorithms);
+        assertEquals(expected.shared, actual.shared);
+        assertEquals(expected.getIpConfiguration(), actual.getIpConfiguration());
+    }
+
+    /**
+     * Asserts that the 2 WifiConfigurations are equal. This only compares the elements saved
+     * fpr backup/restore.
+     */
+    public static void assertConfigurationEqualForBackup(
+            WifiConfiguration expected, WifiConfiguration actual) {
+        assertCommonConfigurationElementsEqual(expected, actual);
+    }
+
+    /**
+     * Asserts that the 2 WifiConfigurations are equal. This compares all the elements saved for
+     * config store.
+     */
+    public static void assertConfigurationEqualForConfigStore(
+            WifiConfiguration expected, WifiConfiguration actual) {
+        assertCommonConfigurationElementsEqual(expected, actual);
+        assertEquals(expected.FQDN, actual.FQDN);
+        assertEquals(expected.providerFriendlyName, actual.providerFriendlyName);
+        assertEquals(expected.linkedConfigurations, actual.linkedConfigurations);
+        assertEquals(expected.defaultGwMacAddress, actual.defaultGwMacAddress);
+        assertEquals(expected.validatedInternetAccess, actual.validatedInternetAccess);
+        assertEquals(expected.noInternetAccessExpected, actual.noInternetAccessExpected);
+        assertEquals(expected.userApproved, actual.userApproved);
+        assertEquals(expected.meteredHint, actual.meteredHint);
+        assertEquals(expected.useExternalScores, actual.useExternalScores);
+        assertEquals(expected.numAssociation, actual.numAssociation);
+        assertEquals(expected.creatorUid, actual.creatorUid);
+        assertEquals(expected.creatorName, actual.creatorName);
+        assertEquals(expected.creationTime, actual.creationTime);
+        assertEquals(expected.lastUpdateUid, actual.lastUpdateUid);
+        assertEquals(expected.lastUpdateName, actual.lastUpdateName);
+        assertEquals(expected.lastConnectUid, actual.lastConnectUid);
+        assertEquals(expected.updateTime, actual.updateTime);
+        assertNetworkSelectionStatusEqualForConfigStore(
+                expected.getNetworkSelectionStatus(), actual.getNetworkSelectionStatus());
+        assertWifiEnterpriseConfigEqualForConfigStore(
+                expected.enterpriseConfig, actual.enterpriseConfig);
+    }
+
+    /**
+     * Asserts that the 2 WifiConfigurations are equal. This compares all the elements that are
+     * saved into internal database by WifiConfigurationManager for network additions/updates.
+     */
+    public static void assertConfigurationEqualForConfigManagerAddOrUpdate(
+            WifiConfiguration expected, WifiConfiguration actual) {
+        assertCommonConfigurationElementsEqual(expected, actual);
+        assertEquals(expected.FQDN, actual.FQDN);
+        assertEquals(expected.providerFriendlyName, actual.providerFriendlyName);
+        assertEquals(expected.noInternetAccessExpected, actual.noInternetAccessExpected);
+        assertEquals(expected.meteredHint, actual.meteredHint);
+        assertEquals(expected.useExternalScores, actual.useExternalScores);
+        assertEquals(expected.ephemeral, actual.ephemeral);
+        assertEquals(expected.creatorUid, actual.creatorUid);
+        assertEquals(expected.creatorName, actual.creatorName);
+        assertEquals(expected.creationTime, actual.creationTime);
+        assertEquals(expected.lastUpdateUid, actual.lastUpdateUid);
+        assertEquals(expected.lastUpdateName, actual.lastUpdateName);
+        assertEquals(expected.updateTime, actual.updateTime);
+        assertNetworkSelectionStatusEqualForConfigStore(
+                expected.getNetworkSelectionStatus(), actual.getNetworkSelectionStatus());
+        assertWifiEnterpriseConfigEqualForConfigStore(
+                expected.enterpriseConfig, actual.enterpriseConfig);
+    }
+
+    /**
+     * Assert that the 2 NetworkSelectionStatus's are equal. This compares all the elements saved
+     * for config store.
+     */
+    public static void assertNetworkSelectionStatusEqualForConfigStore(
+            NetworkSelectionStatus expected, NetworkSelectionStatus actual) {
+        if (expected.isNetworkTemporaryDisabled()) {
+            // Temporarily disabled networks are enabled when persisted.
+            assertEquals(
+                    NetworkSelectionStatus.NETWORK_SELECTION_ENABLED,
+                    actual.getNetworkSelectionStatus());
+        } else {
+            assertEquals(expected.getNetworkSelectionStatus(), actual.getNetworkSelectionStatus());
+        }
+        assertEquals(
+                expected.getNetworkSelectionDisableReason(),
+                actual.getNetworkSelectionDisableReason());
+        assertEquals(expected.getConnectChoice(), actual.getConnectChoice());
+        assertEquals(expected.getConnectChoiceTimestamp(), actual.getConnectChoiceTimestamp());
+        assertEquals(expected.getHasEverConnected(), actual.getHasEverConnected());
+    }
+
+    /**
+     * Assert that the 2 WifiEnterpriseConfig's are equal. This compares all the elements saved
+     * for config store.
+     */
+    public static void assertWifiEnterpriseConfigEqualForConfigStore(
+            WifiEnterpriseConfig expected, WifiEnterpriseConfig actual) {
+        assertEquals(expected.getFieldValue(WifiEnterpriseConfig.IDENTITY_KEY, ""),
+                actual.getFieldValue(WifiEnterpriseConfig.IDENTITY_KEY, ""));
+        assertEquals(expected.getFieldValue(WifiEnterpriseConfig.ANON_IDENTITY_KEY, ""),
+                actual.getFieldValue(WifiEnterpriseConfig.ANON_IDENTITY_KEY, ""));
+        assertEquals(expected.getFieldValue(WifiEnterpriseConfig.PASSWORD_KEY, ""),
+                actual.getFieldValue(WifiEnterpriseConfig.PASSWORD_KEY, ""));
+        assertEquals(expected.getFieldValue(WifiEnterpriseConfig.CLIENT_CERT_KEY, ""),
+                actual.getFieldValue(WifiEnterpriseConfig.CLIENT_CERT_KEY, ""));
+        assertEquals(expected.getFieldValue(WifiEnterpriseConfig.CA_CERT_KEY, ""),
+                actual.getFieldValue(WifiEnterpriseConfig.CA_CERT_KEY, ""));
+        assertEquals(expected.getFieldValue(WifiEnterpriseConfig.SUBJECT_MATCH_KEY, ""),
+                actual.getFieldValue(WifiEnterpriseConfig.SUBJECT_MATCH_KEY, ""));
+        assertEquals(expected.getFieldValue(WifiEnterpriseConfig.ENGINE_KEY, ""),
+                actual.getFieldValue(WifiEnterpriseConfig.ENGINE_KEY, ""));
+        assertEquals(expected.getFieldValue(WifiEnterpriseConfig.ENGINE_ID_KEY, ""),
+                actual.getFieldValue(WifiEnterpriseConfig.ENGINE_ID_KEY, ""));
+        assertEquals(expected.getFieldValue(WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY, ""),
+                actual.getFieldValue(WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY, ""));
+        assertEquals(expected.getFieldValue(WifiEnterpriseConfig.ALTSUBJECT_MATCH_KEY, ""),
+                actual.getFieldValue(WifiEnterpriseConfig.ALTSUBJECT_MATCH_KEY, ""));
+        assertEquals(expected.getFieldValue(WifiEnterpriseConfig.DOM_SUFFIX_MATCH_KEY, ""),
+                actual.getFieldValue(WifiEnterpriseConfig.DOM_SUFFIX_MATCH_KEY, ""));
+        assertEquals(expected.getFieldValue(WifiEnterpriseConfig.CA_PATH_KEY, ""),
+                actual.getFieldValue(WifiEnterpriseConfig.CA_PATH_KEY, ""));
+        assertEquals(expected.getEapMethod(), actual.getEapMethod());
+        assertEquals(expected.getPhase2Method(), actual.getPhase2Method());
+    }
+
+    /**
+     * Asserts that the 2 lists of WifiConfigurations are equal. This compares all the elements
+     * saved for backup/restore.
+     */
+    public static void assertConfigurationsEqualForBackup(
+            List<WifiConfiguration> expected, List<WifiConfiguration> actual) {
+        assertEquals(expected.size(), actual.size());
+        for (WifiConfiguration expectedConfiguration : expected) {
+            String expectedConfigKey = expectedConfiguration.configKey();
+            boolean didCompare = false;
+            for (WifiConfiguration actualConfiguration : actual) {
+                String actualConfigKey = actualConfiguration.configKey();
+                if (actualConfigKey.equals(expectedConfigKey)) {
+                    assertConfigurationEqualForBackup(
+                            expectedConfiguration, actualConfiguration);
+                    didCompare = true;
+                }
+            }
+            assertTrue(didCompare);
+        }
+    }
+
+    /**
+     * Asserts that the 2 lists of WifiConfigurations are equal. This compares all the elements
+     * that are saved into internal database by WifiConfigurationManager for network
+     * additions/updates.
+     */
+    public static void assertConfigurationsEqualForConfigManagerAddOrUpdate(
+            List<WifiConfiguration> expected, List<WifiConfiguration> actual) {
+        assertEquals(expected.size(), actual.size());
+        for (WifiConfiguration expectedConfiguration : expected) {
+            String expectedConfigKey = expectedConfiguration.configKey();
+            boolean didCompare = false;
+            for (WifiConfiguration actualConfiguration : actual) {
+                String actualConfigKey = actualConfiguration.configKey();
+                if (actualConfigKey.equals(expectedConfigKey)) {
+                    assertConfigurationEqualForConfigManagerAddOrUpdate(
+                            expectedConfiguration, actualConfiguration);
+                    didCompare = true;
+                }
+            }
+            assertTrue(didCompare);
+        }
+    }
+
+    /**
+     * Asserts that the 2 lists of WifiConfigurations are equal. This compares all the elements
+     * saved for config store.
+     */
+    public static void assertConfigurationsEqualForConfigStore(
+            List<WifiConfiguration> expected, List<WifiConfiguration> actual) {
+        assertEquals(expected.size(), actual.size());
+        for (WifiConfiguration expectedConfiguration : expected) {
+            String expectedConfigKey = expectedConfiguration.configKey();
+            boolean didCompare = false;
+            for (WifiConfiguration actualConfiguration : actual) {
+                String actualConfigKey = actualConfiguration.configKey();
+                if (actualConfigKey.equals(expectedConfigKey)) {
+                    assertConfigurationEqualForConfigStore(
+                            expectedConfiguration, actualConfiguration);
+                    didCompare = true;
+                }
+            }
+            assertTrue(didCompare);
+        }
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java
index 022997d..fbf1255 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java
@@ -22,6 +22,8 @@
 import static org.junit.Assert.*;
 import static org.mockito.Mockito.*;
 
+import android.app.test.TestAlarmManager;
+import android.app.test.MockAnswerUtil.AnswerWithArguments;
 import android.content.Context;
 import android.content.res.Resources;
 import android.net.wifi.ScanResult;
@@ -38,10 +40,10 @@
 import android.net.wifi.WifiSsid;
 import android.os.SystemClock;
 import android.os.WorkSource;
+import android.os.test.TestLooper;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.internal.R;
-import com.android.server.wifi.MockAnswerUtil.AnswerWithArguments;
 
 import org.junit.After;
 import org.junit.Before;
@@ -66,7 +68,7 @@
     public void setUp() throws Exception {
         mWifiInjector = mockWifiInjector();
         mResource = mockResource();
-        mAlarmManager = new MockAlarmManager();
+        mAlarmManager = new TestAlarmManager();
         mContext = mockContext();
         mWifiStateMachine = mockWifiStateMachine();
         mWifiConfigManager = mockWifiConfigManager();
@@ -77,7 +79,7 @@
                 mWifiScanner, mWifiConfigManager, mWifiInfo, mWifiQNS, mWifiInjector,
                 mLooper.getLooper());
         mWifiConnectivityManager.setWifiEnabled(true);
-        when(mClock.elapsedRealtime()).thenReturn(SystemClock.elapsedRealtime());
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime());
     }
 
     /**
@@ -90,8 +92,8 @@
 
     private Resources mResource;
     private Context mContext;
-    private MockAlarmManager mAlarmManager;
-    private MockLooper mLooper = new MockLooper();
+    private TestAlarmManager mAlarmManager;
+    private TestLooper mLooper = new TestLooper();
     private WifiConnectivityManager mWifiConnectivityManager;
     private WifiQualifiedNetworkSelector mWifiQNS;
     private WifiStateMachine mWifiStateMachine;
@@ -203,8 +205,8 @@
         candidateScanResult.BSSID = CANDIDATE_BSSID;
         candidate.getNetworkSelectionStatus().setCandidate(candidateScanResult);
 
-        when(qns.selectQualifiedNetwork(anyBoolean(), anyBoolean(), anyObject(),
-              anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean())).thenReturn(candidate);
+        when(qns.selectQualifiedNetwork(anyBoolean(), anyBoolean(), anyBoolean(),
+              anyBoolean(), anyBoolean(), anyBoolean(), anyObject())).thenReturn(candidate);
         return qns;
     }
 
@@ -372,7 +374,7 @@
         long currentTimeStamp = 0;
         for (int attempt = 0; attempt < maxAttemptRate; attempt++) {
             currentTimeStamp += connectionAttemptIntervals;
-            when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp);
+            when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
             // Set WiFi to disconnected state to trigger PNO scan
             mWifiConnectivityManager.handleConnectionStateChanged(
                     WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
@@ -380,7 +382,7 @@
         }
         // Now trigger another connection attempt before the rate interval, this should be
         // skipped because we've crossed rate limit.
-        when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
         // Set WiFi to disconnected state to trigger PNO scan
         mWifiConnectivityManager.handleConnectionStateChanged(
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
@@ -411,7 +413,7 @@
         long currentTimeStamp = 0;
         for (int attempt = 0; attempt < maxAttemptRate; attempt++) {
             currentTimeStamp += connectionAttemptIntervals;
-            when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp);
+            when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
             // Set WiFi to disconnected state to trigger PNO scan
             mWifiConnectivityManager.handleConnectionStateChanged(
                     WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
@@ -419,7 +421,7 @@
         }
         // Now trigger another connection attempt after the rate interval, this should not be
         // skipped because we should've evicted the older attempt.
-        when(mClock.elapsedRealtime()).thenReturn(
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(
                 currentTimeStamp + connectionAttemptIntervals * 2);
         // Set WiFi to disconnected state to trigger PNO scan
         mWifiConnectivityManager.handleConnectionStateChanged(
@@ -451,7 +453,7 @@
         long currentTimeStamp = 0;
         for (int attempt = 0; attempt < maxAttemptRate; attempt++) {
             currentTimeStamp += connectionAttemptIntervals;
-            when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp);
+            when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
             // Set WiFi to disconnected state to trigger PNO scan
             mWifiConnectivityManager.handleConnectionStateChanged(
                     WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
@@ -462,7 +464,7 @@
 
         for (int attempt = 0; attempt < maxAttemptRate; attempt++) {
             currentTimeStamp += connectionAttemptIntervals;
-            when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp);
+            when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
             // Set WiFi to disconnected state to trigger PNO scan
             mWifiConnectivityManager.handleConnectionStateChanged(
                     WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
@@ -483,8 +485,8 @@
      */
     @Test
     public void PnoRetryForLowRssiNetwork() {
-        when(mWifiQNS.selectQualifiedNetwork(anyBoolean(), anyBoolean(), anyObject(),
-              anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean())).thenReturn(null);
+        when(mWifiQNS.selectQualifiedNetwork(anyBoolean(), anyBoolean(), anyBoolean(),
+              anyBoolean(), anyBoolean(), anyBoolean(), anyObject())).thenReturn(null);
 
         // Set screen to off
         mWifiConnectivityManager.handleScreenStateChanged(false);
@@ -538,8 +540,8 @@
     @Test
     public void watchdogBitePnoGoodIncrementsMetrics() {
         // Qns returns no candidate after watchdog single scan.
-        when(mWifiQNS.selectQualifiedNetwork(anyBoolean(), anyBoolean(), anyObject(),
-                anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean())).thenReturn(null);
+        when(mWifiQNS.selectQualifiedNetwork(anyBoolean(), anyBoolean(), anyBoolean(),
+                anyBoolean(), anyBoolean(), anyBoolean(), anyObject())).thenReturn(null);
 
         // Set screen to off
         mWifiConnectivityManager.handleScreenStateChanged(false);
@@ -566,7 +568,7 @@
     @Test
     public void checkPeriodicScanIntervalWhenDisconnected() {
         long currentTimeStamp = CURRENT_SYSTEM_TIME_MS;
-        when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
 
         // Set screen to ON
         mWifiConnectivityManager.handleScreenStateChanged(true);
@@ -574,7 +576,7 @@
         // Wait for MAX_PERIODIC_SCAN_INTERVAL_MS so that any impact triggered
         // by screen state change can settle
         currentTimeStamp += WifiConnectivityManager.MAX_PERIODIC_SCAN_INTERVAL_MS;
-        when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
 
         // Set WiFi to disconnected state to trigger periodic scan
         mWifiConnectivityManager.handleConnectionStateChanged(
@@ -587,7 +589,7 @@
         assertEquals(firstIntervalMs, WifiConnectivityManager.PERIODIC_SCAN_INTERVAL_MS);
 
         currentTimeStamp += firstIntervalMs;
-        when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
 
         // Now fire the first periodic scan alarm timer
         mAlarmManager.dispatch(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG);
@@ -602,7 +604,7 @@
         assertEquals(firstIntervalMs * 2, secondIntervalMs);
 
         currentTimeStamp += secondIntervalMs;
-        when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
 
         // Make sure we eventually stay at the maximum scan interval.
         long intervalMs = 0;
@@ -613,7 +615,7 @@
                     .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
                     - currentTimeStamp;
             currentTimeStamp += intervalMs;
-            when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp);
+            when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
         }
 
         assertEquals(intervalMs, WifiConnectivityManager.MAX_PERIODIC_SCAN_INTERVAL_MS);
@@ -629,7 +631,7 @@
     @Test
     public void checkPeriodicScanIntervalWhenConnected() {
         long currentTimeStamp = CURRENT_SYSTEM_TIME_MS;
-        when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
 
         // Set screen to ON
         mWifiConnectivityManager.handleScreenStateChanged(true);
@@ -637,7 +639,7 @@
         // Wait for MAX_PERIODIC_SCAN_INTERVAL_MS so that any impact triggered
         // by screen state change can settle
         currentTimeStamp += WifiConnectivityManager.MAX_PERIODIC_SCAN_INTERVAL_MS;
-        when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
 
         // Set WiFi to connected state to trigger periodic scan
         mWifiConnectivityManager.handleConnectionStateChanged(
@@ -650,7 +652,7 @@
         assertEquals(firstIntervalMs, WifiConnectivityManager.PERIODIC_SCAN_INTERVAL_MS);
 
         currentTimeStamp += firstIntervalMs;
-        when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
 
         // Now fire the first periodic scan alarm timer
         mAlarmManager.dispatch(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG);
@@ -665,7 +667,7 @@
         assertEquals(firstIntervalMs * 2, secondIntervalMs);
 
         currentTimeStamp += secondIntervalMs;
-        when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
 
         // Make sure we eventually stay at the maximum scan interval.
         long intervalMs = 0;
@@ -676,7 +678,7 @@
                     .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
                     - currentTimeStamp;
             currentTimeStamp += intervalMs;
-            when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp);
+            when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
         }
 
         assertEquals(intervalMs, WifiConnectivityManager.MAX_PERIODIC_SCAN_INTERVAL_MS);
@@ -692,7 +694,7 @@
     @Test
     public void checkMinimumPeriodicScanIntervalWhenScreenOn() {
         long currentTimeStamp = CURRENT_SYSTEM_TIME_MS;
-        when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
 
         // Set screen to ON
         mWifiConnectivityManager.handleScreenStateChanged(true);
@@ -701,7 +703,7 @@
         // by screen state change can settle
         currentTimeStamp += WifiConnectivityManager.MAX_PERIODIC_SCAN_INTERVAL_MS;
         long firstScanTimeStamp = currentTimeStamp;
-        when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
 
         // Set WiFi to connected state to trigger the periodic scan
         mWifiConnectivityManager.handleConnectionStateChanged(
@@ -709,7 +711,7 @@
 
         // Set the second scan attempt time stamp.
         currentTimeStamp += 2000;
-        when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
 
         // Set WiFi to disconnected state to trigger another periodic scan
         mWifiConnectivityManager.handleConnectionStateChanged(
@@ -737,7 +739,7 @@
     @Test
     public void checkMinimumPeriodicScanIntervalNotEnforced() {
         long currentTimeStamp = CURRENT_SYSTEM_TIME_MS;
-        when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
 
         // Set screen to ON
         mWifiConnectivityManager.handleScreenStateChanged(true);
@@ -746,7 +748,7 @@
         // by screen state change can settle
         currentTimeStamp += WifiConnectivityManager.MAX_PERIODIC_SCAN_INTERVAL_MS;
         long firstScanTimeStamp = currentTimeStamp;
-        when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
 
         // Set WiFi to connected state to trigger the periodic scan
         mWifiConnectivityManager.handleConnectionStateChanged(
@@ -754,7 +756,7 @@
 
         // Set the second scan attempt time stamp
         currentTimeStamp += 2000;
-        when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
 
         // Force a connectivity scan
         mWifiConnectivityManager.forceConnectivityScan();
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiControllerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiControllerTest.java
index 8f1b23e..b10266e 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiControllerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiControllerTest.java
@@ -33,6 +33,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.os.WorkSource;
+import android.os.test.TestLooper;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
 
@@ -78,7 +79,7 @@
         when(mSettingsStore.isScanAlwaysAvailable()).thenReturn(true);
     }
 
-    MockLooper mLooper;
+    TestLooper mLooper;
     @Mock Context mContext;
     @Mock WifiServiceImpl mService;
     @Mock FrameworkFacade mFacade;
@@ -92,7 +93,7 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
-        mLooper = new MockLooper();
+        mLooper = new TestLooper();
 
         initializeSettingsStore();
 
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiInjectorTest.java b/tests/wifitests/src/com/android/server/wifi/WifiInjectorTest.java
new file mode 100644
index 0000000..a0a1832
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/WifiInjectorTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import static org.mockito.Mockito.*;
+
+import android.content.Context;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Unit tests for {@link WifiInjector}. */
+@SmallTest
+public class WifiInjectorTest {
+
+    @Mock private Context mContext;
+    private WifiInjector mInjector;
+
+    /**
+     * Method to initialize mocks for tests.
+     */
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    /**
+     * Test that attempting to get the instance of the WifiInjector throws an IllegalStateException
+     * if it is not initialized.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testGetInstanceWithUninitializedWifiInjector() {
+        WifiInjector.getInstance();
+    }
+
+    /**
+     * Test that attempting to call the WifiInjector a second time throws an exception.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testShouldNotBeAbleToCreateMoreThanOneWifiInjector() {
+        try {
+            WifiInjector willThrowNullPointerException = new WifiInjector(mContext);
+        } catch (NullPointerException e) {
+        }
+        WifiInjector shouldThrowIllegalStateException = new WifiInjector(mContext);
+    }
+
+    /**
+     * Test that a WifiInjector cannot be created with a null Context.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testShouldNotCreateWifiInjectorWithNullContext() {
+        new WifiInjector(null);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiLastResortWatchdogTest.java b/tests/wifitests/src/com/android/server/wifi/WifiLastResortWatchdogTest.java
index 237fc66..a8b1614 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiLastResortWatchdogTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiLastResortWatchdogTest.java
@@ -55,8 +55,7 @@
     @Before
     public void setUp() throws Exception {
         initMocks(this);
-        mLastResortWatchdog = new WifiLastResortWatchdog(mWifiMetrics);
-        mLastResortWatchdog.setWifiController(mWifiController);
+        mLastResortWatchdog = new WifiLastResortWatchdog(mWifiController, mWifiMetrics);
     }
 
     private List<Pair<ScanDetail, WifiConfiguration>> createFilteredQnsCandidates(String[] ssids,
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiLoggerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiLoggerTest.java
index d915ff3..b70e1dd 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiLoggerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiLoggerTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wifi;
 
+import android.app.test.MockAnswerUtil.AnswerWithArguments;
 import android.content.Context;
 import android.test.suitebuilder.annotation.SmallTest;
 import com.android.internal.R;
@@ -33,8 +34,6 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import com.android.server.wifi.MockAnswerUtil.AnswerWithArguments;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java b/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
index 011682b..d011c54 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
@@ -50,7 +50,7 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mDeserializedWifiMetrics = null;
-        when(mClock.elapsedRealtime()).thenReturn((long) 0);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn((long) 0);
         mWifiMetrics = new WifiMetrics(mClock);
     }
 
@@ -89,7 +89,7 @@
         PrintWriter writer = new PrintWriter(stream);
         String[] args = new String[0];
 
-        when(mClock.elapsedRealtime()).thenReturn(TEST_RECORD_DURATION_MILLIS);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(TEST_RECORD_DURATION_MILLIS);
         //Test proto dump, by passing in proto arg option
         args = new String[]{WifiMetrics.PROTO_DUMP_ARG};
         mWifiMetrics.dump(null, writer, args);
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiMulticastLockManagerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiMulticastLockManagerTest.java
new file mode 100644
index 0000000..02150d7
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/WifiMulticastLockManagerTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import static org.mockito.Mockito.*;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.internal.app.IBatteryStats;
+
+import static org.junit.Assert.*;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.WifiConfigStoreData}.
+ */
+@SmallTest
+public class WifiMulticastLockManagerTest {
+    @Mock WifiMulticastLockManager.FilterController mHandler;
+    @Mock IBatteryStats mBatteryStats;
+    WifiMulticastLockManager mManager;
+
+    /**
+     * Initialize |WifiMulticastLockManager| instance before each test.
+     */
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mManager = new WifiMulticastLockManager(mHandler, mBatteryStats);
+    }
+
+    /**
+     * Test behavior when no locks are held.
+     */
+    @Test
+    public void noLocks() {
+        assertFalse(mManager.isMulticastEnabled());
+        mManager.initializeFiltering();
+        verify(mHandler, times(1)).startFilteringMulticastPackets();
+    }
+
+    /**
+     * Test behavior when one lock is aquired then released.
+     */
+    @Test
+    public void oneLock() throws RemoteException {
+        IBinder binder = mock(IBinder.class);
+        mManager.acquireLock(binder, "Test");
+        assertTrue(mManager.isMulticastEnabled());
+        verify(mHandler).stopFilteringMulticastPackets();
+        mManager.initializeFiltering();
+        verify(mHandler, times(0)).startFilteringMulticastPackets();
+        verify(mBatteryStats).noteWifiMulticastEnabled(anyInt());
+        verify(mBatteryStats, times(0)).noteWifiMulticastDisabled(anyInt());
+
+        mManager.releaseLock();
+        verify(mBatteryStats).noteWifiMulticastDisabled(anyInt());
+        assertFalse(mManager.isMulticastEnabled());
+    }
+}
+
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiNotificationControllerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiNotificationControllerTest.java
index 41e4e46..c3fcc96 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiNotificationControllerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiNotificationControllerTest.java
@@ -34,6 +34,7 @@
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiManager;
 import android.os.UserHandle;
+import android.os.test.TestLooper;
 import android.provider.Settings;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -79,7 +80,7 @@
         when(mFrameworkFacade.getIntegerSetting(mContext,
                 Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1)).thenReturn(1);
 
-        MockLooper mock_looper = new MockLooper();
+        TestLooper mock_looper = new TestLooper();
         mWifiNotificationController = new WifiNotificationController(
                 mContext, mock_looper.getLooper(), mWifiStateMachine, mFrameworkFacade,
                 mock(Notification.Builder.class));
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiQualifiedNetworkSelectorTest.java b/tests/wifitests/src/com/android/server/wifi/WifiQualifiedNetworkSelectorTest.java
index 64fee84..79dfffa 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiQualifiedNetworkSelectorTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiQualifiedNetworkSelectorTest.java
@@ -32,6 +32,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.test.MockAnswerUtil.AnswerWithArguments;
 import android.content.Context;
 import android.content.res.Resources;
 import android.net.NetworkScoreManager;
@@ -39,13 +40,13 @@
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiEnterpriseConfig;
 import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
 import android.net.wifi.WifiSsid;
 import android.os.SystemClock;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.LocalLog;
 
 import com.android.internal.R;
-import com.android.server.wifi.MockAnswerUtil.AnswerWithArguments;
 
 import org.junit.After;
 import org.junit.Before;
@@ -76,9 +77,9 @@
         mWifiQualifiedNetworkSelector = new WifiQualifiedNetworkSelector(mWifiConfigManager,
                 mContext, mWifiInfo, mClock);
         mWifiQualifiedNetworkSelector.enableVerboseLogging(1);
-        mWifiQualifiedNetworkSelector.setUserPreferredBand(1);
+        mWifiQualifiedNetworkSelector.setUserPreferredBand(WifiManager.WIFI_FREQUENCY_BAND_AUTO);
         mWifiQualifiedNetworkSelector.setWifiNetworkScoreCache(mScoreCache);
-        when(mClock.elapsedRealtime()).thenReturn(SystemClock.elapsedRealtime());
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime());
     }
 
     @After
@@ -102,7 +103,7 @@
     private List<ScanDetail> getScanDetails(String[] ssids, String[] bssids, int[] frequencies,
                                             String[] caps, int[] levels) {
         List<ScanDetail> scanDetailList = new ArrayList<ScanDetail>();
-        long timeStamp = mClock.elapsedRealtime();
+        long timeStamp = mClock.getElapsedSinceBootMillis();
         for (int index = 0; index < ssids.length; index++) {
             ScanDetail scanDetail = new ScanDetail(WifiSsid.createFromAsciiEncoded(ssids[index]),
                     bssids[index], caps[index], levels[index], frequencies[index], timeStamp, 0);
@@ -342,7 +343,7 @@
         ScanResult chosenScanResult = scanDetails.get(scanDetails.size() - 1).getScanResult();
 
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, false, true, false);
+                false, false, false, true, false, scanDetails);
 
         verifySelectedResult(chosenScanResult, candidate);
     }
@@ -379,7 +380,7 @@
         ScanResult chosenScanResult = scanDetails.get(scanDetails.size() - 1).getScanResult();
 
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, false, true, false);
+                false, false, false, true, false, scanDetails);
 
         verifySelectedResult(chosenScanResult, candidate);
     }
@@ -415,7 +416,7 @@
         ScanResult chosenScanResult = scanDetails.get(scanDetails.size() - 1).getScanResult();
 
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, false, true, false);
+                false, false, false, true, false, scanDetails);
 
         verifySelectedResult(chosenScanResult, candidate);
     }
@@ -451,8 +452,9 @@
         scanResultLinkConfiguration(savedConfigs, scanDetails);
         ScanResult chosenScanResult = scanDetails.get(0).getScanResult();
 
+        mWifiQualifiedNetworkSelector.setUserPreferredBand(WifiManager.WIFI_FREQUENCY_BAND_AUTO);
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, false, true, false);
+                false, false, false, true, false, scanDetails);
 
         verifySelectedResult(chosenScanResult, candidate);
     }
@@ -489,7 +491,7 @@
         ScanResult chosenScanResult = scanDetails.get(scanDetails.size() - 1).getScanResult();
 
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, false, true, false);
+                false, false, false, true, false, scanDetails);
 
         verifySelectedResult(chosenScanResult, candidate);
     }
@@ -525,7 +527,7 @@
         scanResultLinkConfiguration(savedConfigs, scanDetails);
 
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, false, true, false);
+                false, false, false, true, false, scanDetails);
 
         assertEquals("choose the wrong SSID", null, candidate);
     }
@@ -561,7 +563,7 @@
         ScanResult chosenScanResult = scanDetails.get(0).getScanResult();
 
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, false, true, false);
+                false, false, false, true, false, scanDetails);
 
         verifySelectedResult(chosenScanResult, candidate);
     }
@@ -597,7 +599,7 @@
         ScanResult chosenScanResult = scanDetails.get(0).getScanResult();
 
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, false, true, false);
+                false, false, false, true, false, scanDetails);
 
         verifySelectedResult(chosenScanResult, candidate);
     }
@@ -632,7 +634,7 @@
         ScanResult chosenScanResult = scanDetails.get(0).getScanResult();
 
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, false, true, false);
+                false, false, false, true, false, scanDetails);
         verifySelectedResult(chosenScanResult, candidate);
     }
 
@@ -717,7 +719,7 @@
                 .thenReturn(configs[2]);
 
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, false, true, false);
+                false, false, false, true, false, scanDetails);
         verifySelectedResult(chosenScanResult, candidate);
     }
 
@@ -798,7 +800,7 @@
         mWifiQualifiedNetworkSelector.enableBssidForQualityNetworkSelection(bssids[1], false);
         mWifiQualifiedNetworkSelector.enableBssidForQualityNetworkSelection(bssids[1], false);
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, false, true, false);
+                false, false, false, true, false, scanDetails);
 
         verifySelectedResult(chosenScanResult, candidate);
     }
@@ -840,7 +842,7 @@
         //re-enable it
         mWifiQualifiedNetworkSelector.enableBssidForQualityNetworkSelection(bssids[1], true);
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, false, true, false);
+                false, false, false, true, false, scanDetails);
 
         verifySelectedResult(chosenScanResult, candidate);
     }
@@ -884,10 +886,10 @@
         }
 
         //re-enable it
-        when(mClock.elapsedRealtime()).thenReturn(SystemClock.elapsedRealtime()
-                + WifiQualifiedNetworkSelector.BSSID_BLACKLIST_EXPIRE_TIME);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime()
+                + WifiQualifiedNetworkSelector.BSSID_BLACKLIST_EXPIRE_TIME_MS);
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, false, true, false);
+                false, false, false, true, false, scanDetails);
 
         verifySelectedResult(chosenScanResult, candidate);
     }
@@ -926,7 +928,7 @@
         savedConfigs[1].getNetworkSelectionStatus().setNetworkSelectionStatus(
                 WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED);
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, false, true, false);
+                false, false, false, true, false, scanDetails);
 
         verifySelectedResult(chosenScanResult, candidate);
     }
@@ -962,7 +964,7 @@
         scanResultLinkConfiguration(savedConfigs, scanDetails);
 
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, true, false, true, false);
+                false, true, false, true, false, scanDetails);
 
         assertEquals("choose the wrong network", null, candidate);
     }
@@ -994,11 +996,11 @@
         when(mWifiConfigManager.getSavedNetworks()).thenReturn(savedNetwork);
         scanResultLinkConfiguration(savedConfigs, scanDetails);
         //first QNS
-        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, scanDetails, false,
-                false, true, false);
+        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, false,
+                false, true, false, scanDetails);
         //immediately second QNS
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, false, true, false);
+                false, false, false, true, false, scanDetails);
         ScanResult chosenScanResult = scanDetails.get(1).getScanResult();
 
         verifySelectedResult(chosenScanResult, candidate);
@@ -1032,11 +1034,11 @@
         when(mWifiConfigManager.getSavedNetworks()).thenReturn(savedNetwork);
         scanResultLinkConfiguration(savedConfigs, scanDetails);
         //first QNS
-        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, scanDetails, false,
-                false, true, false);
+        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, false,
+                false, true, false, scanDetails);
         //immediately second QNS
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, true, false, false);
+                false, false, true, false, false, scanDetails);
         ScanResult chosenScanResult = scanDetails.get(1).getScanResult();
         assertEquals("choose the wrong BSSID", null, candidate);
     }
@@ -1068,11 +1070,11 @@
         when(mWifiConfigManager.getSavedNetworks()).thenReturn(savedNetwork);
         scanResultLinkConfiguration(savedConfigs, scanDetails);
         //first QNS
-        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, scanDetails, false,
-                false, true, false);
+        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, false,
+                false, true, false, scanDetails);
         //immediately second QNS
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(true,
-                false, scanDetails, false, true, false, false);
+                false, false, true, false, false, scanDetails);
         ScanResult chosenScanResult = scanDetails.get(1).getScanResult();
 
         verifySelectedResult(chosenScanResult, candidate);
@@ -1105,7 +1107,7 @@
         scanResultLinkConfiguration(savedConfigs, scanDetails);
 
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, true, false, false);
+                false, false, true, false, false, scanDetails);
         assertEquals("choose the wrong BSSID", null, candidate);
         assertEquals("Should receive zero filteredScanDetails", 0,
                 mWifiQualifiedNetworkSelector.getFilteredScanDetails().size());
@@ -1140,20 +1142,20 @@
         scanResultLinkConfiguration(savedConfigs, scanDetails);
 
         //first time, connect to test2 due to 5GHz bonus
-        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, scanDetails, false,
-                false, true, false);
+        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, false,
+                false, true, false, scanDetails);
         when(mWifiInfo.getNetworkId()).thenReturn(1);
         when(mWifiInfo.getBSSID()).thenReturn(bssids[1]);
         when(mWifiInfo.is24GHz()).thenReturn(false);
         when(mWifiConfigManager.getEnableAutoJoinWhenAssociated()).thenReturn(true);
-        when(mClock.elapsedRealtime()).thenReturn(SystemClock.elapsedRealtime() + 11 * 1000);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime() + 11 * 1000);
 
         levels[0] = -50; // if there is QNS, test1 will be chosen
         scanDetails = getScanDetails(ssids, bssids, frequencies, caps, levels);
         scanResultLinkConfiguration(savedConfigs, scanDetails);
 
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, true, false, false);
+                false, false, true, false, false, scanDetails);
         assertEquals("choose the wrong BSSID", null, candidate);
     }
 
@@ -1184,16 +1186,16 @@
         scanResultLinkConfiguration(savedConfigs, scanDetails);
 
         //first connect to test2 due to 5GHz bonus
-        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, scanDetails, false,
-                false, true, false);
+        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, false,
+                false, true, false, scanDetails);
         when(mWifiInfo.getNetworkId()).thenReturn(1);
         when(mWifiInfo.getBSSID()).thenReturn(bssids[1]);
         when(mWifiInfo.is24GHz()).thenReturn(false);
-        when(mClock.elapsedRealtime()).thenReturn(SystemClock.elapsedRealtime() + 11 * 1000);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime() + 11 * 1000);
         when(mWifiConfigManager.getEnableAutoJoinWhenAssociated()).thenReturn(true);
 
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, true, true, false, false);
+                false, true, true, false, false, scanDetails);
         assertEquals("choose the wrong BSSID", null, candidate);
     }
 
@@ -1226,15 +1228,15 @@
         when(mWifiInfo.getBSSID()).thenReturn(bssids[0]);
         when(mWifiInfo.is24GHz()).thenReturn(true);
         //connect to config2 first
-        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, scanDetails, false,
-                false, true, false);
+        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, false,
+                false, true, false, scanDetails);
 
-        when(mClock.elapsedRealtime()).thenReturn(SystemClock.elapsedRealtime() + 11 * 1000);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime() + 11 * 1000);
         when(mWifiConfigManager.getEnableAutoJoinWhenAssociated()).thenReturn(true);
 
         ScanResult chosenScanResult = scanDetails.get(0).getScanResult();
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, true, false, false);
+                false, false, true, false, false, scanDetails);
         verifySelectedResult(chosenScanResult, candidate);
     }
 
@@ -1266,14 +1268,14 @@
         scanResultLinkConfiguration(savedConfigs, scanDetails);
 
         //first connect to test2 because of RSSI
-        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, scanDetails, false,
-                false, true, false);
+        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, false,
+                false, true, false, scanDetails);
         when(mWifiInfo.getNetworkId()).thenReturn(1);
         when(mWifiInfo.getBSSID()).thenReturn(bssids[1]);
         when(mWifiInfo.is24GHz()).thenReturn(false);
         when(mWifiInfo.is5GHz()).thenReturn(true);
         when(mWifiConfigManager.isOpenNetwork(savedConfigs[1])).thenReturn(true);
-        when(mClock.elapsedRealtime()).thenReturn(SystemClock.elapsedRealtime() + 11 * 1000);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime() + 11 * 1000);
         when(mWifiConfigManager.getEnableAutoJoinWhenAssociated()).thenReturn(true);
         levels[0] = -60;
         scanDetails = getScanDetails(ssids, bssids, frequencies, caps, levels);
@@ -1281,7 +1283,7 @@
 
         ScanResult chosenScanResult = scanDetails.get(0).getScanResult();
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, true, false, false);
+                false, false, true, false, false, scanDetails);
         verifySelectedResult(chosenScanResult, candidate);
     }
 
@@ -1314,8 +1316,8 @@
         scanResultLinkConfiguration(savedConfigs, scanDetails);
 
         //first connect to test2 since test1's RSSI is negligible
-        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, scanDetails, false,
-                false, true, false);
+        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, false,
+                false, true, false, scanDetails);
         when(mWifiInfo.getNetworkId()).thenReturn(1);
         when(mWifiInfo.getBSSID()).thenReturn(bssids[1]);
         when(mWifiInfo.is24GHz()).thenReturn(false);
@@ -1324,11 +1326,11 @@
         scanDetails = getScanDetails(ssids, bssids, frequencies, caps, levels);
         scanResultLinkConfiguration(savedConfigs, scanDetails);
         when(mWifiConfigManager.getEnableAutoJoinWhenAssociated()).thenReturn(true);
-        when(mClock.elapsedRealtime()).thenReturn(SystemClock.elapsedRealtime() + 11 * 1000);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime() + 11 * 1000);
 
         ScanResult chosenScanResult = scanDetails.get(0).getScanResult();
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, true, false, false);
+                false, false, true, false, false, scanDetails);
         verifySelectedResult(chosenScanResult, candidate);
     }
 
@@ -1359,8 +1361,8 @@
         when(mWifiConfigManager.getSavedNetworks()).thenReturn(savedNetwork);
         scanResultLinkConfiguration(savedConfigs, scanDetails);
 
-        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, scanDetails, false,
-                false, true, false);
+        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, false,
+                false, true, false, scanDetails);
         when(mWifiInfo.getNetworkId()).thenReturn(1);
         when(mWifiInfo.getBSSID()).thenReturn(bssids[1]);
         when(mWifiInfo.getRssi()).thenReturn(levels[1]);
@@ -1368,14 +1370,14 @@
         when(mWifiInfo.is5GHz()).thenReturn(true);
 
         when(mWifiConfigManager.getEnableAutoJoinWhenAssociated()).thenReturn(true);
-        when(mClock.elapsedRealtime()).thenReturn(SystemClock.elapsedRealtime() + 11 * 1000);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime() + 11 * 1000);
         levels[0] = -60;
         scanDetails = getScanDetails(ssids, bssids, frequencies, caps, levels);
         scanResultLinkConfiguration(savedConfigs, scanDetails);
         ScanResult chosenScanResult = scanDetails.get(0).getScanResult();
 
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, true, false, false);
+                false, false, true, false, false, scanDetails);
         verifySelectedResult(chosenScanResult, candidate);
     }
 
@@ -1405,8 +1407,8 @@
         when(mWifiConfigManager.getSavedNetworks()).thenReturn(savedNetwork);
         scanResultLinkConfiguration(savedConfigs, scanDetails);
 
-        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, scanDetails, false,
-                false, true, false);
+        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, false,
+                false, true, false, scanDetails);
 
         when(mWifiInfo.getNetworkId()).thenReturn(1);
         when(mWifiInfo.getBSSID()).thenReturn(bssids[1]);
@@ -1417,12 +1419,12 @@
         when(mWifiConfigManager.getEnableAutoJoinWhenAssociated()).thenReturn(true);
         levels[0] = -60;
         scanDetails = getScanDetails(ssids, bssids, frequencies, caps, levels);
-        when(mClock.elapsedRealtime()).thenReturn(SystemClock.elapsedRealtime() + 11 * 1000);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime() + 11 * 1000);
         scanResultLinkConfiguration(savedConfigs, scanDetails);
 
         ScanResult chosenScanResult = scanDetails.get(0).getScanResult();
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, true, false, false);
+                false, false, true, false, false, scanDetails);
         verifySelectedResult(chosenScanResult, candidate);
     }
 
@@ -1454,8 +1456,8 @@
         when(mWifiConfigManager.getSavedNetworks()).thenReturn(savedNetwork);
         scanResultLinkConfiguration(savedConfigs, scanDetails);
 
-        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, scanDetails, false,
-                false, true, false);
+        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, false,
+                false, true, false, scanDetails);
         when(mWifiInfo.getNetworkId()).thenReturn(1);
         when(mWifiInfo.getBSSID()).thenReturn(bssids[1]);
         when(mWifiInfo.is24GHz()).thenReturn(true);
@@ -1464,12 +1466,12 @@
         levels[0] = -80 + WifiQualifiedNetworkSelector.SAME_BSSID_AWARD / 4
                 + WifiQualifiedNetworkSelector.SAME_NETWORK_AWARD / 4 - 1;
         scanDetails = getScanDetails(ssids, bssids, frequencies, caps, levels);
-        when(mClock.elapsedRealtime()).thenReturn(SystemClock.elapsedRealtime() + 11 * 1000);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime() + 11 * 1000);
         scanResultLinkConfiguration(savedConfigs, scanDetails);
 
         ScanResult chosenScanResult = scanDetails.get(1).getScanResult();
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, true, false, false);
+                false, false, true, false, false, scanDetails);
         verifySelectedResult(chosenScanResult, candidate);
     }
 
@@ -1499,8 +1501,8 @@
         final List<WifiConfiguration> savedNetwork = Arrays.asList(savedConfigs);
         when(mWifiConfigManager.getSavedNetworks()).thenReturn(savedNetwork);
         scanResultLinkConfiguration(savedConfigs, scanDetails);
-        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, scanDetails, false,
-                false, true, false);
+        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, false,
+                false, true, false, scanDetails);
 
         when(mWifiInfo.getNetworkId()).thenReturn(1);
         when(mWifiInfo.getBSSID()).thenReturn(bssids[1]);
@@ -1510,12 +1512,12 @@
         levels[0] = -80 + WifiQualifiedNetworkSelector.SAME_BSSID_AWARD / 4
                 + WifiQualifiedNetworkSelector.SAME_NETWORK_AWARD / 4 + 1;
         scanDetails = getScanDetails(ssids, bssids, frequencies, caps, levels);
-        when(mClock.elapsedRealtime()).thenReturn(SystemClock.elapsedRealtime() + 11 * 1000);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime() + 11 * 1000);
         scanResultLinkConfiguration(savedConfigs, scanDetails);
 
         ScanResult chosenScanResult = scanDetails.get(0).getScanResult();
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, true, false, false);
+                false, false, true, false, false, scanDetails);
         verifySelectedResult(chosenScanResult, candidate);
     }
 
@@ -1551,8 +1553,8 @@
         final List<WifiConfiguration> savedNetwork = Arrays.asList(savedConfigs);
         when(mWifiConfigManager.getSavedNetworks()).thenReturn(savedNetwork);
         scanResultLinkConfiguration(savedConfigs, scanDetails);
-        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, scanDetails, false,
-                false, true, false);
+        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, false,
+                false, true, false, scanDetails);
 
         when(mWifiInfo.getNetworkId()).thenReturn(1);
         when(mWifiInfo.getBSSID()).thenReturn(bssids[1]);
@@ -1561,12 +1563,12 @@
         when(mWifiConfigManager.getEnableAutoJoinWhenAssociated()).thenReturn(true);
         levels[0] = -80 + WifiQualifiedNetworkSelector.SAME_NETWORK_AWARD / 4 - 1;
         scanDetails = getScanDetails(ssids, bssids, frequencies, caps, levels);
-        when(mClock.elapsedRealtime()).thenReturn(SystemClock.elapsedRealtime() + 11 * 1000);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime() + 11 * 1000);
         scanResultLinkConfiguration(savedConfigs, scanDetails);
 
         ScanResult chosenScanResult = scanDetails.get(1).getScanResult();
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, true, false, false);
+                false, false, true, false, false, scanDetails);
         verifySelectedResult(chosenScanResult, candidate);
     }
 
@@ -1602,8 +1604,8 @@
         final List<WifiConfiguration> savedNetwork = Arrays.asList(savedConfigs);
         when(mWifiConfigManager.getSavedNetworks()).thenReturn(savedNetwork);
         scanResultLinkConfiguration(savedConfigs, scanDetails);
-        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, scanDetails, false,
-                false, true, false);
+        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, false,
+                false, true, false, scanDetails);
 
         when(mWifiInfo.getNetworkId()).thenReturn(1);
         when(mWifiInfo.getBSSID()).thenReturn(bssids[1]);
@@ -1612,12 +1614,12 @@
         when(mWifiConfigManager.getEnableAutoJoinWhenAssociated()).thenReturn(true);
         levels[0] = -80 + WifiQualifiedNetworkSelector.SAME_BSSID_AWARD / 4 + 1;
         scanDetails = getScanDetails(ssids, bssids, frequencies, caps, levels);
-        when(mClock.elapsedRealtime()).thenReturn(SystemClock.elapsedRealtime() + 11 * 1000);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime() + 11 * 1000);
         scanResultLinkConfiguration(savedConfigs, scanDetails);
 
         ScanResult chosenScanResult = scanDetails.get(0).getScanResult();
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, true, false, false);
+                false, false, true, false, false, scanDetails);
         verifySelectedResult(chosenScanResult, candidate);
     }
 
@@ -1671,11 +1673,11 @@
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(
                 false /* forceSelectNetwork */,
                 true /* isUntrustedConnectionsAllowed */,
-                scanDetails,
                 false, /* isLinkDebouncing */
                 false, /* isConnected */
                 true, /* isDisconnected */
-                false /* isSupplicantTransient */);
+                false, /* isSupplicantTransient */
+                scanDetails);
         verify(selectionStatus).setCandidate(untrustedScanResult);
         assertSame(unTrustedNetworkCandidate, candidate);
         assertEquals(meteredHints[1], candidate.meteredHint);
@@ -1744,11 +1746,11 @@
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(
                 false /* forceSelectNetwork */,
                 true /* isUntrustedConnectionsAllowed */,
-                scanDetails,
                 false, /* isLinkDebouncing */
                 false, /* isConnected */
                 true, /* isDisconnected */
-                false /* isSupplicantTransient */);
+                false, /* isSupplicantTransient */
+                scanDetails);
 
         verifySelectedResult(chosenScanResult, candidate);
         //Verify two scanDetails returned in the filteredScanDetails
@@ -1823,11 +1825,11 @@
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(
                 false /* forceSelectNetwork */,
                 false /* isUntrustedConnectionsAllowed */,
-                scanDetails,
                 false, /* isLinkDebouncing */
                 false, /* isConnected */
                 true, /* isDisconnected */
-                false /* isSupplicantTransient */);
+                false, /* isSupplicantTransient */
+                scanDetails);
 
         verifySelectedResult(chosenScanResult, candidate);
         //Verify two scanDetails returned in the filteredScanDetails
@@ -1893,11 +1895,11 @@
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(
                 false /* forceSelectNetwork */,
                 true /* isUntrustedConnectionsAllowed */,
-                scanDetails,
                 false, /* isLinkDebouncing */
                 false, /* isConnected */
                 true, /* isDisconnected */
-                false /* isSupplicantTransient */);
+                false, /* isSupplicantTransient */
+                scanDetails);
         verify(selectionStatus).setCandidate(untrustedScanResult);
         assertSame(candidate, unTrustedNetworkCandidate);
         assertEquals(meteredHints[0], candidate.meteredHint);
@@ -1938,11 +1940,11 @@
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(
                 false /* forceSelectNetwork */,
                 false /* isUntrustedConnectionsAllowed */,
-                scanDetails,
                 false, /* isLinkDebouncing */
                 false, /* isConnected */
                 true, /* isDisconnected */
-                false /* isSupplicantTransient */);
+                false, /* isSupplicantTransient */
+                scanDetails);
         verifySelectedResult(scanDetails.get(0).getScanResult(), candidate);
         assertSame(candidate, savedConfigs[0]);
     }
@@ -1984,11 +1986,11 @@
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(
                 false /* forceSelectNetwork */,
                 false /* isUntrustedConnectionsAllowed */,
-                scanDetails,
                 false, /* isLinkDebouncing */
                 false, /* isConnected */
                 true, /* isDisconnected */
-                false /* isSupplicantTransient */);
+                false, /* isSupplicantTransient */
+                scanDetails);
         verifySelectedResult(scanDetails.get(0).getScanResult(), candidate);
         assertSame(candidate, savedConfigs[0]);
     }
@@ -2035,11 +2037,11 @@
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(
                 false /* forceSelectNetwork */,
                 true /* isUntrustedConnectionsAllowed */,
-                scanDetails,
                 false, /* isLinkDebouncing */
                 false, /* isConnected */
                 true, /* isDisconnected */
-                false /* isSupplicantTransient */);
+                false, /* isSupplicantTransient */
+                scanDetails);
         verifySelectedResult(scanDetails.get(0).getScanResult(), candidate);
         assertSame(candidate, savedConfigs[0]);
     }
@@ -2093,11 +2095,11 @@
         WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(
                 false /* forceSelectNetwork */,
                 true /* isUntrustedConnectionsAllowed */,
-                scanDetails,
                 false, /* isLinkDebouncing */
                 false, /* isConnected */
                 true, /* isDisconnected */
-                false /* isSupplicantTransient */);
+                false, /* isSupplicantTransient */
+                scanDetails);
         verify(selectionStatus).setCandidate(untrustedScanResult);
         assertSame(unTrustedNetworkCandidate, candidate);
     }
@@ -2233,4 +2235,119 @@
         assertEquals(WifiQualifiedNetworkSelector.ExternalScoreEvaluator
                 .BestCandidateType.NONE, evaluator.getBestCandidateType());
     }
+
+    /**
+     * Case #46   5GHz due to user band preference is set to 5GHz only
+     *
+     * In this test. we simulate following scenario
+     * WifiStateMachine is under disconnected state
+     * User band preference is set to 5GHz only
+     * Two networks test1, test2 are secured network
+     * Both network are enabled
+     * test1 is @ 2GHz with RSSI -50
+     * test2 is @ 5Ghz with RSSI -75
+     *
+     * Expected behavior: test2 is chosen due to user band preference
+     */
+    @Test
+    public void chooseNetworkDisconnectUserPrefer5GTest() {
+        String[] ssids = DEFAULT_SSIDS;
+        String[] bssids = DEFAULT_BSSIDS;
+        int[] frequencies = {2437, 5180};
+        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        int[] levels = {-50, -75};
+        int[] security = {SECURITY_PSK, SECURITY_PSK};
+
+        List<ScanDetail> scanDetails = getScanDetails(ssids, bssids, frequencies, caps, levels);
+        WifiConfiguration[] savedConfigs = generateWifiConfigurations(ssids, security);
+        prepareConfigStore(savedConfigs);
+
+        final List<WifiConfiguration> savedNetwork = Arrays.asList(savedConfigs);
+        when(mWifiConfigManager.getSavedNetworks()).thenReturn(savedNetwork);
+        scanResultLinkConfiguration(savedConfigs, scanDetails);
+        ScanResult chosenScanResult = scanDetails.get(1).getScanResult();
+
+        mWifiQualifiedNetworkSelector.setUserPreferredBand(WifiManager.WIFI_FREQUENCY_BAND_5GHZ);
+        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
+                false, false, false, true, false, scanDetails);
+
+        verifySelectedResult(chosenScanResult, candidate);
+    }
+
+    /**
+     * Case #47   2GHz due to user band preference is set to 2GHz only
+     *
+     * In this test. we simulate following scenario
+     * WifiStateMachine is under disconnected state
+     * User band preference is set to 2.4GHz only
+     * Two networks test1, test2 are secured network
+     * Both network are enabled
+     * test1 is @ 2GHz with RSSI -75
+     * test2 is @ 5Ghz with RSSI -50
+     *
+     * Expected behavior: test1 is chosen due to user band preference
+     */
+    @Test
+    public void chooseNetworkDisconnectUserPrefer2GTest() {
+        String[] ssids = DEFAULT_SSIDS;
+        String[] bssids = DEFAULT_BSSIDS;
+        int[] frequencies = {2437, 5180};
+        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        int[] levels = {-75, -50};
+        int[] security = {SECURITY_PSK, SECURITY_PSK};
+
+        List<ScanDetail> scanDetails = getScanDetails(ssids, bssids, frequencies, caps, levels);
+        WifiConfiguration[] savedConfigs = generateWifiConfigurations(ssids, security);
+        prepareConfigStore(savedConfigs);
+
+        final List<WifiConfiguration> savedNetwork = Arrays.asList(savedConfigs);
+        when(mWifiConfigManager.getSavedNetworks()).thenReturn(savedNetwork);
+        scanResultLinkConfiguration(savedConfigs, scanDetails);
+        ScanResult chosenScanResult = scanDetails.get(0).getScanResult();
+
+        mWifiQualifiedNetworkSelector.setUserPreferredBand(WifiManager.WIFI_FREQUENCY_BAND_2GHZ);
+        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
+                false, false, false, true, false, scanDetails);
+
+        verifySelectedResult(chosenScanResult, candidate);
+    }
+
+    /**
+     * Case #48  5GHz due to user band preference is set to AUTO
+     *
+     * In this test. we simulate following scenario
+     * WifiStateMachine is under disconnected state
+     * User band preference is set to AUTO
+     * Two networks test1, test2 are secured network
+     * Both network are enabled
+     * test1 is @ 2GHz with RSSI -50
+     * test2 is @ 5Ghz with RSSI -50
+     *
+     * Expected behavior: test2 is chosen due to 5G bonus and user band preference
+     * is set to AUTO.
+     */
+    @Test
+    public void chooseNetworkDisconnectUserPreferAutoBandTest() {
+        String[] ssids = DEFAULT_SSIDS;
+        String[] bssids = DEFAULT_BSSIDS;
+        int[] frequencies = {2437, 5180};
+        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        int[] levels = {-50, -50};
+        int[] security = {SECURITY_PSK, SECURITY_PSK};
+
+        List<ScanDetail> scanDetails = getScanDetails(ssids, bssids, frequencies, caps, levels);
+        WifiConfiguration[] savedConfigs = generateWifiConfigurations(ssids, security);
+        prepareConfigStore(savedConfigs);
+
+        final List<WifiConfiguration> savedNetwork = Arrays.asList(savedConfigs);
+        when(mWifiConfigManager.getSavedNetworks()).thenReturn(savedNetwork);
+        scanResultLinkConfiguration(savedConfigs, scanDetails);
+        ScanResult chosenScanResult = scanDetails.get(1).getScanResult();
+
+        mWifiQualifiedNetworkSelector.setUserPreferredBand(WifiManager.WIFI_FREQUENCY_BAND_AUTO);
+        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
+                false, false, false, true, false, scanDetails);
+
+        verifySelectedResult(chosenScanResult, candidate);
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java b/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java
index e0f94ad..be19084 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java
@@ -22,6 +22,8 @@
 import static org.mockito.Mockito.*;
 
 import android.app.ActivityManager;
+import android.app.test.TestAlarmManager;
+import android.app.test.MockAnswerUtil.AnswerWithArguments;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -53,6 +55,7 @@
 import android.os.PowerManager;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.test.TestLooper;
 import android.provider.Settings;
 import android.security.KeyStore;
 import android.telephony.TelephonyManager;
@@ -65,7 +68,6 @@
 import com.android.internal.util.AsyncChannel;
 import com.android.internal.util.IState;
 import com.android.internal.util.StateMachine;
-import com.android.server.wifi.MockAnswerUtil.AnswerWithArguments;
 import com.android.server.wifi.hotspot2.NetworkDetail;
 import com.android.server.wifi.hotspot2.Utils;
 import com.android.server.wifi.p2p.WifiP2pServiceImpl;
@@ -84,6 +86,7 @@
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -165,20 +168,16 @@
         WifiP2pServiceImpl p2pm = (WifiP2pServiceImpl) p2pBinder.queryLocalInterface(
                 IWifiP2pManager.class.getCanonicalName());
 
-        final Object sync = new Object();
-        synchronized (sync) {
-            mP2pThread = new HandlerThread("WifiP2pMockThread") {
-                @Override
-                protected void onLooperPrepared() {
-                    synchronized (sync) {
-                        sync.notifyAll();
-                    }
-                }
-            };
+        final CountDownLatch untilDone = new CountDownLatch(1);
+        mP2pThread = new HandlerThread("WifiP2pMockThread") {
+            @Override
+            protected void onLooperPrepared() {
+                untilDone.countDown();
+            }
+        };
 
-            mP2pThread.start();
-            sync.wait();
-        }
+        mP2pThread.start();
+        untilDone.await();
 
         Handler handler = new Handler(mP2pThread.getLooper());
         when(p2pm.getP2pStateMachineMessenger()).thenReturn(new Messenger(handler));
@@ -229,7 +228,7 @@
         when(context.getSystemService(Context.POWER_SERVICE)).thenReturn(
                 new PowerManager(context, mock(IPowerManager.class), new Handler()));
 
-        mAlarmManager = new MockAlarmManager();
+        mAlarmManager = new TestAlarmManager();
         when(context.getSystemService(Context.ALARM_SERVICE)).thenReturn(
                 mAlarmManager.getAlarmManager());
 
@@ -307,10 +306,10 @@
     HandlerThread mP2pThread;
     HandlerThread mSyncThread;
     AsyncChannel  mWsmAsyncChannel;
-    MockAlarmManager mAlarmManager;
+    TestAlarmManager mAlarmManager;
     MockWifiMonitor mWifiMonitor;
     TestIpManager mTestIpManager;
-    MockLooper mLooper;
+    TestLooper mLooper;
     WifiConfigManager mWifiConfigManager;
 
     @Mock WifiNative mWifiNative;
@@ -334,7 +333,7 @@
         Log.d(TAG, "Setting up ...");
 
         // Ensure looper exists
-        mLooper = new MockLooper();
+        mLooper = new TestLooper();
 
         MockitoAnnotations.initMocks(this);
 
@@ -349,6 +348,7 @@
         when(mWifiInjector.getPropertyService()).thenReturn(mPropertyService);
         when(mWifiInjector.getBuildProperties()).thenReturn(mBuildProperties);
         when(mWifiInjector.getKeyStore()).thenReturn(mock(KeyStore.class));
+        when(mWifiInjector.getWifiBackupRestore()).thenReturn(mock(WifiBackupRestore.class));
         FrameworkFacade factory = getFrameworkFacade();
         Context context = getContext();
 
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java b/tests/wifitests/src/com/android/server/wifi/WifiSupplicantControlTest.java
similarity index 93%
rename from tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java
rename to tests/wifitests/src/com/android/server/wifi/WifiSupplicantControlTest.java
index 3993fe5..ceb4b42 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiSupplicantControlTest.java
@@ -11,7 +11,7 @@
  * 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
+ * limitations under the License.
  */
 
 package com.android.server.wifi;
@@ -35,10 +35,10 @@
 import java.util.Map;
 
 /**
- * Unit tests for {@link com.android.server.wifi.WifiConfigStore}.
+ * Unit tests for {@link com.android.server.wifi.WifiSupplicantControl}.
  */
 @SmallTest
-public class WifiConfigStoreTest {
+public class WifiSupplicantControlTest {
     private static final String KEY_SSID = "ssid";
     private static final String KEY_PSK = "psk";
     private static final String KEY_KEY_MGMT = "key_mgmt";
@@ -97,7 +97,7 @@
         NETWORK_3_VARS.put(CONFIG_KEY, "\"testwpa2psk\"WPA_PSK");
     }
 
-    private static final ArrayList<HashMap<String, String>> NETWORK_VARS = new ArrayList<HashMap<String, String>>();
+    private static final ArrayList<HashMap<String, String>> NETWORK_VARS = new ArrayList<>();
     static {
         NETWORK_VARS.add(NETWORK_0_VARS);
         NETWORK_VARS.add(NETWORK_1_VARS);
@@ -105,7 +105,8 @@
         NETWORK_VARS.add(NETWORK_3_VARS);
     }
 
-    // Taken from wpa_supplicant.conf actual test device Some fields modified for privacy.
+    // Taken from wpa_supplicant.conf of an actual test device. Some fields modified for privacy
+    // reasons.
     private static final String TEST_WPA_SUPPLICANT_CONF = ""
             + "ctrl_interface=/data/misc/wifi/sockets\n"
             + "disable_scan_offload=1\n"
@@ -157,16 +158,15 @@
 
     @Mock private WifiNative mWifiNative;
     @Mock private Context mContext;
-    private MockKeyStore mMockKeyStore;
-    private WifiConfigStore mWifiConfigStore;
+    private WifiSupplicantControl mWifiSupplicantControl;
 
+    /**
+     * Initialize |WifiSupplicantControl| instance before each test.
+     */
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-
-        mMockKeyStore = new MockKeyStore();
-        mWifiConfigStore = new WifiConfigStore(mContext, mWifiNative, mMockKeyStore.createMock(),
-                null, false, true);
+        mWifiSupplicantControl = new WifiSupplicantControl(mContext, mWifiNative, null);
     }
 
     /**
@@ -226,7 +226,7 @@
         BufferedReader reader = null;
         try {
             reader = new BufferedReader(new StringReader(TEST_WPA_SUPPLICANT_CONF));
-            result = mWifiConfigStore.readNetworkVariablesFromReader(reader, key);
+            result = mWifiSupplicantControl.readNetworkVariablesFromReader(reader, key);
         } catch (IOException e) {
             fail("Error reading test supplicant conf string");
         } finally {
diff --git a/tests/wifitests/src/com/android/server/wifi/nan/TlvBufferUtilsTest.java b/tests/wifitests/src/com/android/server/wifi/nan/TlvBufferUtilsTest.java
index 5c83185..9141841 100644
--- a/tests/wifitests/src/com/android/server/wifi/nan/TlvBufferUtilsTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/nan/TlvBufferUtilsTest.java
@@ -24,7 +24,6 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ErrorCollector;
-import org.junit.rules.ExpectedException;
 
 /**
  * Unit test harness for WifiNanManager class.
@@ -34,9 +33,6 @@
     @Rule
     public ErrorCollector collector = new ErrorCollector();
 
-    @Rule
-    public ExpectedException thrown = ExpectedException.none();
-
     /*
      * TlvBufferUtils Tests
      */
@@ -64,10 +60,20 @@
                 utilAreArraysEqual(tlv01.getArray(), tlv01.getActualLength(), new byte[] {
                         1, 2, 3, 0, 1, 2 }, 6),
                 equalTo(true));
+
+        collector.checkThat("tlv11-valid",
+                TlvBufferUtils.isValid(tlv11.getArray(), tlv11.getActualLength(), 1, 1),
+                equalTo(true));
+        collector.checkThat("tlv01-valid",
+                TlvBufferUtils.isValid(tlv01.getArray(), tlv01.getActualLength(), 0, 1),
+                equalTo(true));
     }
 
     @Test
     public void testTlvIterate() {
+        final String ascii = "ABC";
+        final String nonAscii = "何かもっと複雑な";
+
         TlvBufferUtils.TlvConstructor tlv22 = new TlvBufferUtils.TlvConstructor(2, 2);
         tlv22.allocate(18);
         tlv22.putInt(0, 2);
@@ -102,9 +108,10 @@
         }
 
         TlvBufferUtils.TlvConstructor tlv02 = new TlvBufferUtils.TlvConstructor(0, 2);
-        tlv02.allocate(15);
+        tlv02.allocate(100);
         tlv02.putByte(0, (byte) 2);
-        tlv02.putString(0, "ABC");
+        tlv02.putString(0, ascii);
+        tlv02.putString(0, nonAscii);
 
         TlvBufferUtils.TlvIterable tlv02It = new TlvBufferUtils.TlvIterable(0, 2, tlv02.getArray(),
                 tlv02.getActualLength());
@@ -115,101 +122,119 @@
                 collector.checkThat("tlv02-correct-iteration-DATA", (int) tlv.getByte(),
                         equalTo(2));
             } else if (count == 1) {
-                collector.checkThat("tlv02-correct-iteration-mLength", tlv.mLength, equalTo(3));
-                collector.checkThat("tlv02-correct-iteration-DATA", tlv.getString().equals("ABC"),
+                collector.checkThat("tlv02-correct-iteration-mLength", tlv.mLength,
+                        equalTo(ascii.length()));
+                collector.checkThat("tlv02-correct-iteration-DATA", tlv.getString().equals(ascii),
                         equalTo(true));
+            } else if (count == 2) {
+                collector.checkThat("tlv02-correct-iteration-mLength", tlv.mLength,
+                        equalTo(nonAscii.getBytes().length));
+                collector.checkThat("tlv02-correct-iteration-DATA",
+                        tlv.getString().equals(nonAscii), equalTo(true));
             } else {
                 collector.checkThat("Invalid number of iterations in loop - tlv02", true,
                         equalTo(false));
             }
             ++count;
         }
-        if (count != 2) {
-            collector.checkThat("Invalid number of iterations outside loop - tlv02", true,
-                    equalTo(false));
-        }
+        collector.checkThat("Invalid number of iterations outside loop - tlv02", count,
+                equalTo(3));
+
+        collector.checkThat("tlv22-valid",
+                TlvBufferUtils.isValid(tlv22.getArray(), tlv22.getActualLength(), 2, 2),
+                equalTo(true));
+        collector.checkThat("tlv02-valid",
+                TlvBufferUtils.isValid(tlv02.getArray(), tlv02.getActualLength(), 0, 2),
+                equalTo(true));
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testTlvInvalidSizeT1L0() {
-        thrown.expect(IllegalArgumentException.class);
         TlvBufferUtils.TlvConstructor tlv10 = new TlvBufferUtils.TlvConstructor(1, 0);
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testTlvInvalidSizeTm3L2() {
-        thrown.expect(IllegalArgumentException.class);
         TlvBufferUtils.TlvConstructor tlv10 = new TlvBufferUtils.TlvConstructor(-3, 2);
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testTlvInvalidSizeT1Lm2() {
-        thrown.expect(IllegalArgumentException.class);
         TlvBufferUtils.TlvConstructor tlv10 = new TlvBufferUtils.TlvConstructor(1, -2);
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testTlvInvalidSizeT1L3() {
-        thrown.expect(IllegalArgumentException.class);
         TlvBufferUtils.TlvConstructor tlv10 = new TlvBufferUtils.TlvConstructor(1, 3);
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testTlvInvalidSizeT3L1() {
-        thrown.expect(IllegalArgumentException.class);
         TlvBufferUtils.TlvConstructor tlv10 = new TlvBufferUtils.TlvConstructor(3, 1);
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testTlvItInvalidSizeT1L0() {
         final byte[] dummy = {
                 0, 1, 2 };
         final int dummyLength = 3;
-        thrown.expect(IllegalArgumentException.class);
         TlvBufferUtils.TlvIterable tlvIt10 = new TlvBufferUtils.TlvIterable(1, 0, dummy,
                 dummyLength);
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testTlvItInvalidSizeTm3L2() {
         final byte[] dummy = {
                 0, 1, 2 };
         final int dummyLength = 3;
-        thrown.expect(IllegalArgumentException.class);
         TlvBufferUtils.TlvIterable tlvIt10 = new TlvBufferUtils.TlvIterable(-3, 2, dummy,
                 dummyLength);
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testTlvItInvalidSizeT1Lm2() {
         final byte[] dummy = {
                 0, 1, 2 };
         final int dummyLength = 3;
-        thrown.expect(IllegalArgumentException.class);
         TlvBufferUtils.TlvIterable tlvIt10 = new TlvBufferUtils.TlvIterable(1, -2, dummy,
                 dummyLength);
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testTlvItInvalidSizeT1L3() {
         final byte[] dummy = {
                 0, 1, 2 };
         final int dummyLength = 3;
-        thrown.expect(IllegalArgumentException.class);
         TlvBufferUtils.TlvIterable tlvIt10 = new TlvBufferUtils.TlvIterable(1, 3, dummy,
                 dummyLength);
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testTlvItInvalidSizeT3L1() {
         final byte[] dummy = {
                 0, 1, 2 };
         final int dummyLength = 3;
-        thrown.expect(IllegalArgumentException.class);
         TlvBufferUtils.TlvIterable tlvIt10 = new TlvBufferUtils.TlvIterable(3, 1, dummy,
                 dummyLength);
     }
 
+    /**
+     * Validate that a malformed byte array fails the TLV validity test.
+     */
+    @Test
+    public void testTlvInvalidByteArray() {
+        TlvBufferUtils.TlvConstructor tlv01 = new TlvBufferUtils.TlvConstructor(0, 1);
+        tlv01.allocate(15);
+        tlv01.putByte(0, (byte) 2);
+        tlv01.putByteArray(2, new byte[]{0, 1, 2});
+
+        byte[] array = tlv01.getArray();
+        array[0] = 10;
+
+        collector.checkThat("tlv01-invalid",
+                TlvBufferUtils.isValid(array, tlv01.getActualLength(), 0, 1), equalTo(false));
+    }
+
     /*
      * Utilities
      */
diff --git a/tests/wifitests/src/com/android/server/wifi/nan/WifiNanDataPathStateManagerTest.java b/tests/wifitests/src/com/android/server/wifi/nan/WifiNanDataPathStateManagerTest.java
new file mode 100644
index 0000000..e4f714a
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/nan/WifiNanDataPathStateManagerTest.java
@@ -0,0 +1,735 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.nan;
+
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyShort;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.app.test.TestAlarmManager;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.LinkProperties;
+import android.net.NetworkCapabilities;
+import android.net.NetworkFactory;
+import android.net.NetworkInfo;
+import android.net.NetworkMisc;
+import android.net.NetworkRequest;
+import android.net.wifi.RttManager;
+import android.net.wifi.nan.ConfigRequest;
+import android.net.wifi.nan.IWifiNanEventCallback;
+import android.net.wifi.nan.IWifiNanManager;
+import android.net.wifi.nan.IWifiNanSessionCallback;
+import android.net.wifi.nan.PublishConfig;
+import android.net.wifi.nan.SubscribeConfig;
+import android.net.wifi.nan.WifiNanEventCallback;
+import android.net.wifi.nan.WifiNanManager;
+import android.net.wifi.nan.WifiNanPublishSession;
+import android.net.wifi.nan.WifiNanSessionCallback;
+import android.net.wifi.nan.WifiNanSubscribeSession;
+import android.os.IBinder;
+import android.os.INetworkManagementService;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.test.TestLooper;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Pair;
+
+import com.android.internal.util.AsyncChannel;
+
+import libcore.util.HexEncoding;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ErrorCollector;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.util.Arrays;
+
+/**
+ * Unit test harness for WifiNanDataPathStateManager class.
+ */
+@SmallTest
+public class WifiNanDataPathStateManagerTest {
+    private static final String sNanInterfacePrefix = "nan";
+
+    private TestLooper mMockLooper;
+    private WifiNanStateManager mDut;
+    @Mock private WifiNanNative mMockNative;
+    @Mock private Context mMockContext;
+    @Mock private IWifiNanManager mMockNanService;
+    @Mock private WifiNanPublishSession mMockPublishSession;
+    @Mock private WifiNanSubscribeSession mMockSubscribeSession;
+    @Mock private RttManager.RttListener mMockRttListener;
+    @Mock private ConnectivityManager mMockCm;
+    @Mock private INetworkManagementService mMockNwMgt;
+    @Mock private WifiNanDataPathStateManager.NetworkInterfaceWrapper mMockNetworkInterface;
+    @Mock private IWifiNanEventCallback mMockCallback;
+    @Mock IWifiNanSessionCallback mMockSessionCallback;
+    TestAlarmManager mAlarmManager;
+
+    @Rule
+    public ErrorCollector collector = new ErrorCollector();
+
+    /**
+     * Initialize mocks.
+     */
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mAlarmManager = new TestAlarmManager();
+        when(mMockContext.getSystemService(Context.ALARM_SERVICE))
+                .thenReturn(mAlarmManager.getAlarmManager());
+
+        when(mMockContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(mMockCm);
+
+        mMockLooper = new TestLooper();
+
+        mDut = installNewNanStateManager();
+        mDut.start(mMockContext, mMockLooper.getLooper());
+        mDut.startLate();
+
+        when(mMockNative.getCapabilities(anyShort())).thenReturn(true);
+        when(mMockNative.enableAndConfigure(anyShort(), any(ConfigRequest.class), anyBoolean()))
+                .thenReturn(true);
+        when(mMockNative.disable(anyShort())).thenReturn(true);
+        when(mMockNative.publish(anyShort(), anyInt(), any(PublishConfig.class))).thenReturn(true);
+        when(mMockNative.subscribe(anyShort(), anyInt(), any(SubscribeConfig.class)))
+                .thenReturn(true);
+        when(mMockNative.sendMessage(anyShort(), anyInt(), anyInt(), any(byte[].class),
+                any(byte[].class), anyInt(), anyInt())).thenReturn(true);
+        when(mMockNative.stopPublish(anyShort(), anyInt())).thenReturn(true);
+        when(mMockNative.stopSubscribe(anyShort(), anyInt())).thenReturn(true);
+        when(mMockNative.createNanNetworkInterface(anyShort(), anyString())).thenReturn(true);
+        when(mMockNative.deleteNanNetworkInterface(anyShort(), anyString())).thenReturn(true);
+        when(mMockNative.initiateDataPath(anyShort(), anyInt(), anyInt(), anyInt(),
+                any(byte[].class), anyString(), any(byte[].class), anyInt())).thenReturn(true);
+        when(mMockNative.respondToDataPathRequest(anyShort(), anyBoolean(), anyInt(), anyString(),
+                any(byte[].class), anyInt())).thenReturn(true);
+        when(mMockNative.endDataPath(anyShort(), anyInt())).thenReturn(true);
+
+        when(mMockNetworkInterface.configureAgentProperties(
+                any(WifiNanDataPathStateManager.NanNetworkRequestInformation.class), anyString(),
+                anyInt(), any(NetworkInfo.class), any(NetworkCapabilities.class),
+                any(LinkProperties.class))).thenReturn(true);
+
+        installMockWifiNanNative(mMockNative);
+        installDataPathStateManagerMocks();
+    }
+
+    /**
+     * Validates that creating and deleting all interfaces works based on capabilities.
+     */
+    @Test
+    public void testCreateDeleteAllInterfaces() throws Exception {
+        final int numNdis = 3;
+        final int failCreateInterfaceIndex = 1;
+
+        WifiNanNative.Capabilities capabilities = new WifiNanNative.Capabilities();
+        capabilities.maxNdiInterfaces = numNdis;
+
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        ArgumentCaptor<String> interfaceName = ArgumentCaptor.forClass(String.class);
+        InOrder inOrder = inOrder(mMockNative);
+
+        // (1) get capabilities
+        mDut.getCapabilities();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), capabilities);
+        mMockLooper.dispatchAll();
+
+        // (2) create all interfaces
+        mDut.createAllDataPathInterfaces();
+        mMockLooper.dispatchAll();
+        for (int i = 0; i < numNdis; ++i) {
+            inOrder.verify(mMockNative).createNanNetworkInterface(transactionId.capture(),
+                    interfaceName.capture());
+            collector.checkThat("interface created -- " + i, sNanInterfacePrefix + i,
+                    equalTo(interfaceName.getValue()));
+            mDut.onCreateDataPathInterfaceResponse(transactionId.getValue(), true, 0);
+            mMockLooper.dispatchAll();
+        }
+
+        // (3) delete all interfaces [one unsuccessfully] - note that will not necessarily be
+        // done sequentially
+        boolean[] done = new boolean[numNdis];
+        Arrays.fill(done, false);
+        mDut.deleteAllDataPathInterfaces();
+        mMockLooper.dispatchAll();
+        for (int i = 0; i < numNdis; ++i) {
+            inOrder.verify(mMockNative).deleteNanNetworkInterface(transactionId.capture(),
+                    interfaceName.capture());
+            int interfaceIndex = Integer.valueOf(
+                    interfaceName.getValue().substring(sNanInterfacePrefix.length()));
+            done[interfaceIndex] = true;
+            if (interfaceIndex == failCreateInterfaceIndex) {
+                mDut.onDeleteDataPathInterfaceResponse(transactionId.getValue(), false, 0);
+            } else {
+                mDut.onDeleteDataPathInterfaceResponse(transactionId.getValue(), true, 0);
+            }
+            mMockLooper.dispatchAll();
+        }
+        for (int i = 0; i < numNdis; ++i) {
+            collector.checkThat("interface deleted -- " + i, done[i], equalTo(true));
+        }
+
+        // (4) create all interfaces (should get a delete for the one which couldn't delete earlier)
+        mDut.createAllDataPathInterfaces();
+        mMockLooper.dispatchAll();
+        for (int i = 0; i < numNdis; ++i) {
+            if (i == failCreateInterfaceIndex) {
+                inOrder.verify(mMockNative).deleteNanNetworkInterface(transactionId.capture(),
+                        interfaceName.capture());
+                collector.checkThat("interface delete pre-create -- " + i, sNanInterfacePrefix + i,
+                        equalTo(interfaceName.getValue()));
+                mDut.onDeleteDataPathInterfaceResponse(transactionId.getValue(), true, 0);
+                mMockLooper.dispatchAll();
+            }
+            inOrder.verify(mMockNative).createNanNetworkInterface(transactionId.capture(),
+                    interfaceName.capture());
+            collector.checkThat("interface created -- " + i, sNanInterfacePrefix + i,
+                    equalTo(interfaceName.getValue()));
+            mDut.onCreateDataPathInterfaceResponse(transactionId.getValue(), true, 0);
+            mMockLooper.dispatchAll();
+        }
+
+        verifyNoMoreInteractions(mMockNative);
+    }
+
+    /*
+     * Initiator tests
+     */
+
+    /**
+     * Validate the success flow of the Initiator: using session network specifier with a non-null
+     * token.
+     */
+    @Test
+    public void testDataPathInitiatorMacTokenSuccess() throws Exception {
+        testDataPathInitiatorUtility(false, true, true, true);
+    }
+
+    /**
+     * Validate the fail flow of the Initiator: using session network specifier with a null
+     * token.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testDataPathInitiatorMacNoTokenFail() throws Exception {
+        testDataPathInitiatorUtility(false, true, false, true);
+    }
+
+    /**
+     * Validate the fail flow of the Initiator: using session network specifier with a 0
+     * peer ID.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testDataPathInitiatorNoMacFail() throws Exception {
+        testDataPathInitiatorUtility(false, false, true, true);
+    }
+
+    /**
+     * Validate the success flow of the Initiator: using a direct network specifier with a non-null
+     * peer mac and non-null token.
+     */
+    @Test
+    public void testDataPathInitiatorDirectMacTokenSuccess() throws Exception {
+        testDataPathInitiatorUtility(true, true, true, true);
+    }
+
+    /**
+     * Validate the fail flow of the Initiator: using a direct network specifier with a non-null
+     * peer mac and null token.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testDataPathInitiatorDirectMacNoTokenFail() throws Exception {
+        testDataPathInitiatorUtility(true, true, false, true);
+    }
+
+    /**
+     * Validate the fail flow of the Initiator: using a direct network specifier with a null peer
+     * mac and non-null token.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testDataPathInitiatorDirectNoMacTokenFail() throws Exception {
+        testDataPathInitiatorUtility(true, false, true, true);
+    }
+
+    /**
+     * Validate the fail flow of the Initiator: using a direct network specifier with a null peer
+     * mac and null token.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testDataPathInitiatorDirectNoMacNoTokenFail() throws Exception {
+        testDataPathInitiatorUtility(true, false, false, true);
+    }
+
+    /**
+     * Validate the fail flow of the Initiator: use a session network specifier with a non-null
+     * token, but don't get a confirmation.
+     */
+    @Test
+    public void testDataPathInitiatorNoConfirmationTimeoutFail() throws Exception {
+        testDataPathInitiatorUtility(false, true, true, false);
+    }
+
+    /*
+     * Responder tests
+     */
+
+    /**
+     * Validate the success flow of the Responder: using session network specifier with a non-null
+     * token.
+     */
+    @Test
+    public void testDataPathResonderMacTokenSuccess() throws Exception {
+        testDataPathResponderUtility(false, true, true, true);
+    }
+
+    /**
+     * Validate the success flow of the Responder: using session network specifier with a null
+     * token.
+     */
+    @Test
+    public void testDataPathResonderMacNoTokenSuccess() throws Exception {
+        testDataPathResponderUtility(false, true, false, true);
+    }
+
+    /**
+     * Validate the success flow of the Responder: using session network specifier with a
+     * token and no peer ID (i.e. 0).
+     */
+    @Test
+    public void testDataPathResonderMacTokenNoPeerIdSuccess() throws Exception {
+        testDataPathResponderUtility(false, false, true, true);
+    }
+
+    /**
+     * Validate the success flow of the Responder: using session network specifier with a null
+     * token and no peer ID (i.e. 0).
+     */
+    @Test
+    public void testDataPathResonderMacTokenNoPeerIdNoTokenSuccess() throws Exception {
+        testDataPathResponderUtility(false, false, false, true);
+    }
+
+    /**
+     * Validate the success flow of the Responder: using a direct network specifier with a non-null
+     * peer mac and non-null token.
+     */
+    @Test
+    public void testDataPathResonderDirectMacTokenSuccess() throws Exception {
+        testDataPathResponderUtility(true, true, true, true);
+    }
+
+    /**
+     * Validate the success flow of the Responder: using a direct network specifier with a non-null
+     * peer mac and null token.
+     */
+    @Test
+    public void testDataPathResonderDirectMacNoTokenSuccess() throws Exception {
+        testDataPathResponderUtility(true, true, false, true);
+    }
+
+    /**
+     * Validate the success flow of the Responder: using a direct network specifier with a null peer
+     * mac and non-null token.
+     */
+    @Test
+    public void testDataPathResonderDirectNoMacTokenSuccess() throws Exception {
+        testDataPathResponderUtility(true, false, true, true);
+    }
+
+    /**
+     * Validate the success flow of the Responder: using a direct network specifier with a null peer
+     * mac and null token.
+     */
+    @Test
+    public void testDataPathResonderDirectNoMacNoTokenSuccess() throws Exception {
+        testDataPathResponderUtility(true, false, false, true);
+    }
+
+    /**
+     * Validate the fail flow of the Responder: use a session network specifier with a non-null
+     * token, but don't get a confirmation.
+     */
+    @Test
+    public void testDataPathResponderNoConfirmationTimeoutFail() throws Exception {
+        testDataPathInitiatorUtility(false, true, true, false);
+    }
+
+    /*
+     * Utilities
+     */
+
+    private void testDataPathInitiatorUtility(boolean useDirect, boolean provideMac,
+            boolean provideToken, boolean getConfirmation) throws Exception {
+        final int clientId = 123;
+        final int pubSubId = 11234;
+        final int peerId = 1341234;
+        final int ndpId = 2;
+        final String token = "some token";
+        final String peerToken = "let's go!";
+        final byte[] peerDiscoveryMac = HexEncoding.decode("000102030405".toCharArray(), false);
+        final byte[] peerDataPathMac = HexEncoding.decode("0A0B0C0D0E0F".toCharArray(), false);
+
+        ArgumentCaptor<Messenger> messengerCaptor = ArgumentCaptor.forClass(Messenger.class);
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        InOrder inOrder = inOrder(mMockNative, mMockCm, mMockCallback, mMockSessionCallback);
+
+        // (0) initialize
+        Pair<Integer, Messenger> res = initDataPathEndPoint(clientId, pubSubId, peerId,
+                peerDiscoveryMac, inOrder);
+
+        // (1) request network
+        NetworkRequest nr;
+        if (useDirect) {
+            nr = getDirectNetworkRequest(clientId, WifiNanManager.WIFI_NAN_DATA_PATH_ROLE_INITIATOR,
+                    provideMac ? peerDiscoveryMac : null, provideToken ? token : null);
+        } else {
+            nr = getSessionNetworkRequest(clientId, res.first, provideMac ? peerId : 0,
+                    WifiNanManager.WIFI_NAN_DATA_PATH_ROLE_INITIATOR, provideToken ? token : null);
+        }
+
+        Message reqNetworkMsg = Message.obtain();
+        reqNetworkMsg.what = NetworkFactory.CMD_REQUEST_NETWORK;
+        reqNetworkMsg.obj = nr;
+        reqNetworkMsg.arg1 = 0;
+        res.second.send(reqNetworkMsg);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).initiateDataPath(transactionId.capture(),
+                eq(useDirect ? 0 : peerId), eq(WifiNanNative.CHANNEL_REQUEST_TYPE_REQUESTED),
+                eq(2437), eq(peerDiscoveryMac), eq("nan0"), eq(token.getBytes()),
+                eq(token.length()));
+        mDut.onInitiateDataPathResponseSuccess(transactionId.getValue(), ndpId);
+        mMockLooper.dispatchAll();
+
+        // (2) get confirmation OR timeout
+        if (getConfirmation) {
+            mDut.onDataPathConfirmNotification(ndpId, peerDataPathMac, true, 0,
+                    peerToken.getBytes(),
+                    peerToken.length());
+            mMockLooper.dispatchAll();
+            inOrder.verify(mMockCm).registerNetworkAgent(messengerCaptor.capture(),
+                    any(NetworkInfo.class), any(LinkProperties.class),
+                    any(NetworkCapabilities.class),
+                    anyInt(), any(NetworkMisc.class));
+        } else {
+            assertTrue(
+                    mAlarmManager.dispatch(WifiNanStateManager.HAL_DATA_PATH_CONFIRM_TIMEOUT_TAG));
+            mMockLooper.dispatchAll();
+            inOrder.verify(mMockNative).endDataPath(transactionId.capture(), eq(ndpId));
+            mDut.onEndDataPathResponse(transactionId.getValue(), true, 0);
+            mMockLooper.dispatchAll();
+        }
+
+        // (3) end data-path (unless didn't get confirmation)
+        if (getConfirmation) {
+            Message endNetworkReqMsg = Message.obtain();
+            endNetworkReqMsg.what = NetworkFactory.CMD_CANCEL_REQUEST;
+            endNetworkReqMsg.obj = nr;
+            res.second.send(endNetworkReqMsg);
+
+            Message endNetworkUsageMsg = Message.obtain();
+            endNetworkUsageMsg.what = AsyncChannel.CMD_CHANNEL_DISCONNECTED;
+            messengerCaptor.getValue().send(endNetworkUsageMsg);
+
+            mMockLooper.dispatchAll();
+            inOrder.verify(mMockNative).endDataPath(transactionId.capture(), eq(ndpId));
+            mDut.onEndDataPathResponse(transactionId.getValue(), true, 0);
+            mMockLooper.dispatchAll();
+        }
+
+        verifyNoMoreInteractions(mMockNative, mMockCm);
+    }
+
+    private void testDataPathResponderUtility(boolean useDirect, boolean provideMac,
+            boolean provideToken, boolean getConfirmation) throws Exception {
+        final int clientId = 123;
+        final int pubSubId = 11234;
+        final int peerId = 1341234;
+        final int ndpId = 2;
+        final String token = "some token";
+        final String peerToken = "let's go!";
+        final byte[] peerDiscoveryMac = HexEncoding.decode("000102030405".toCharArray(), false);
+        final byte[] peerDataPathMac = HexEncoding.decode("0A0B0C0D0E0F".toCharArray(), false);
+
+        ArgumentCaptor<Messenger> messengerCaptor = ArgumentCaptor.forClass(Messenger.class);
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        InOrder inOrder = inOrder(mMockNative, mMockCm, mMockCallback, mMockSessionCallback);
+
+        // (0) initialize
+        Pair<Integer, Messenger> res = initDataPathEndPoint(clientId, pubSubId, peerId,
+                peerDiscoveryMac, inOrder);
+
+        // (1) request network
+        NetworkRequest nr;
+        if (useDirect) {
+            nr = getDirectNetworkRequest(clientId, WifiNanManager.WIFI_NAN_DATA_PATH_ROLE_RESPONDER,
+                    provideMac ? peerDiscoveryMac : null, provideToken ? token : null);
+        } else {
+            nr = getSessionNetworkRequest(clientId, res.first, provideMac ? peerId : 0,
+                    WifiNanManager.WIFI_NAN_DATA_PATH_ROLE_RESPONDER, provideToken ? token : null);
+        }
+
+        Message reqNetworkMsg = Message.obtain();
+        reqNetworkMsg.what = NetworkFactory.CMD_REQUEST_NETWORK;
+        reqNetworkMsg.obj = nr;
+        reqNetworkMsg.arg1 = 0;
+        res.second.send(reqNetworkMsg);
+        mMockLooper.dispatchAll();
+
+        // (2) get request & respond
+        mDut.onDataPathRequestNotification(pubSubId, peerDiscoveryMac, ndpId, token.getBytes(),
+                token.length());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).respondToDataPathRequest(transactionId.capture(), eq(true),
+                eq(ndpId), eq("nan0"), eq(new byte[0]), eq(0));
+        mDut.onRespondToDataPathSetupRequestResponse(transactionId.getValue(), true, 0);
+        mMockLooper.dispatchAll();
+
+        // (3) get confirmation OR timeout
+        if (getConfirmation) {
+            mDut.onDataPathConfirmNotification(ndpId, peerDataPathMac, true, 0,
+                    peerToken.getBytes(),
+                    peerToken.length());
+            mMockLooper.dispatchAll();
+            inOrder.verify(mMockCm).registerNetworkAgent(messengerCaptor.capture(),
+                    any(NetworkInfo.class), any(LinkProperties.class),
+                    any(NetworkCapabilities.class),
+                    anyInt(), any(NetworkMisc.class));
+        } else {
+            assertTrue(
+                    mAlarmManager.dispatch(WifiNanStateManager.HAL_DATA_PATH_CONFIRM_TIMEOUT_TAG));
+            mMockLooper.dispatchAll();
+            inOrder.verify(mMockNative).endDataPath(transactionId.capture(), eq(ndpId));
+            mDut.onEndDataPathResponse(transactionId.getValue(), true, 0);
+            mMockLooper.dispatchAll();
+        }
+
+        // (4) end data-path (unless didn't get confirmation)
+        if (getConfirmation) {
+            Message endNetworkMsg = Message.obtain();
+            endNetworkMsg.what = NetworkFactory.CMD_CANCEL_REQUEST;
+            endNetworkMsg.obj = nr;
+            res.second.send(endNetworkMsg);
+
+            Message endNetworkUsageMsg = Message.obtain();
+            endNetworkUsageMsg.what = AsyncChannel.CMD_CHANNEL_DISCONNECTED;
+            messengerCaptor.getValue().send(endNetworkUsageMsg);
+
+            mMockLooper.dispatchAll();
+            inOrder.verify(mMockNative).endDataPath(transactionId.capture(), eq(ndpId));
+            mDut.onEndDataPathResponse(transactionId.getValue(), true, 0);
+            mMockLooper.dispatchAll();
+        }
+
+        verifyNoMoreInteractions(mMockNative, mMockCm);
+    }
+
+
+    private static WifiNanStateManager installNewNanStateManager()
+            throws Exception {
+        Constructor<WifiNanStateManager> ctr = WifiNanStateManager.class.getDeclaredConstructor();
+        ctr.setAccessible(true);
+        WifiNanStateManager nanStateManager = ctr.newInstance();
+
+        Field field = WifiNanStateManager.class.getDeclaredField("sNanStateManagerSingleton");
+        field.setAccessible(true);
+        field.set(null, nanStateManager);
+
+        return WifiNanStateManager.getInstance();
+    }
+
+    private static void installMockWifiNanNative(WifiNanNative obj) throws Exception {
+        Field field = WifiNanNative.class.getDeclaredField("sWifiNanNativeSingleton");
+        field.setAccessible(true);
+        field.set(null, obj);
+    }
+
+    private void installDataPathStateManagerMocks() throws Exception {
+        Field field = WifiNanStateManager.class.getDeclaredField("mDataPathMgr");
+        field.setAccessible(true);
+        Object mDataPathMgr = field.get(mDut);
+
+        field = WifiNanDataPathStateManager.class.getDeclaredField("mNwService");
+        field.setAccessible(true);
+        field.set(mDataPathMgr, mMockNwMgt);
+
+        field = WifiNanDataPathStateManager.class.getDeclaredField("mNiWrapper");
+        field.setAccessible(true);
+        field.set(mDataPathMgr, mMockNetworkInterface);
+    }
+
+    private NetworkRequest getSessionNetworkRequest(int clientId, int sessionId, int peerId,
+            int role, String token) throws Exception {
+        final WifiNanManager mgr = new WifiNanManager(mMockNanService);
+        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        final PublishConfig publishConfig = new PublishConfig.Builder().build();
+
+        ArgumentCaptor<IWifiNanEventCallback> clientProxyCallback = ArgumentCaptor
+                .forClass(IWifiNanEventCallback.class);
+        ArgumentCaptor<IWifiNanSessionCallback> sessionProxyCallback = ArgumentCaptor
+                .forClass(IWifiNanSessionCallback.class);
+        ArgumentCaptor<WifiNanPublishSession> publishSession = ArgumentCaptor
+                .forClass(WifiNanPublishSession.class);
+
+        WifiNanEventCallback mockCallback = mock(WifiNanEventCallback.class);
+        WifiNanSessionCallback mockSessionCallback = mock(WifiNanSessionCallback.class);
+
+        when(mMockNanService.connect(any(IBinder.class), any(IWifiNanEventCallback.class),
+                any(ConfigRequest.class))).thenReturn(clientId);
+
+        mgr.connect(mMockLooper.getLooper(), mockCallback, configRequest);
+        verify(mMockNanService).connect(any(IBinder.class), clientProxyCallback.capture(),
+                eq(configRequest));
+        clientProxyCallback.getValue().onConnectSuccess();
+        mMockLooper.dispatchAll();
+        mgr.publish(publishConfig, mockSessionCallback);
+        verify(mMockNanService).publish(eq(clientId), eq(publishConfig),
+                sessionProxyCallback.capture());
+        sessionProxyCallback.getValue().onSessionStarted(sessionId);
+        mMockLooper.dispatchAll();
+        verify(mockSessionCallback).onPublishStarted(publishSession.capture());
+
+        String ns = publishSession.getValue().createNetworkSpecifier(role, peerId,
+                (token == null) ? null : token.getBytes(), (token == null) ? 0 : token.length());
+
+        NetworkCapabilities nc = new NetworkCapabilities();
+        nc.clearAll();
+        nc.addTransportType(NetworkCapabilities.TRANSPORT_WIFI_NAN);
+        nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN).addCapability(
+                NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+        nc.setNetworkSpecifier(ns);
+        nc.setLinkUpstreamBandwidthKbps(1);
+        nc.setLinkDownstreamBandwidthKbps(1);
+        nc.setSignalStrength(1);
+
+        return new NetworkRequest(nc, 0, 0, NetworkRequest.Type.NONE);
+    }
+
+    private NetworkRequest getDirectNetworkRequest(int clientId, int role, byte[] peer,
+            String token) throws Exception {
+        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        final WifiNanManager mgr = new WifiNanManager(mMockNanService);
+
+        ArgumentCaptor<IWifiNanEventCallback> clientProxyCallback = ArgumentCaptor
+                .forClass(IWifiNanEventCallback.class);
+
+        WifiNanEventCallback mockCallback = mock(WifiNanEventCallback.class);
+
+        when(mMockNanService.connect(any(IBinder.class), any(IWifiNanEventCallback.class),
+                any(ConfigRequest.class))).thenReturn(clientId);
+
+        mgr.connect(mMockLooper.getLooper(), mockCallback, configRequest);
+        verify(mMockNanService).connect(any(IBinder.class), clientProxyCallback.capture(),
+                eq(configRequest));
+        clientProxyCallback.getValue().onConnectSuccess();
+        mMockLooper.dispatchAll();
+
+        String ns = mgr.createNetworkSpecifier(role, peer,
+                (token == null) ? null : token.getBytes(), (token == null) ? 0 : token.length());
+        NetworkCapabilities nc = new NetworkCapabilities();
+        nc.clearAll();
+        nc.addTransportType(NetworkCapabilities.TRANSPORT_WIFI_NAN);
+        nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN).addCapability(
+                NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+        nc.setNetworkSpecifier(ns);
+        nc.setLinkUpstreamBandwidthKbps(1);
+        nc.setLinkDownstreamBandwidthKbps(1);
+        nc.setSignalStrength(1);
+
+        return new NetworkRequest(nc, 0, 0, NetworkRequest.Type.REQUEST);
+    }
+
+    private Pair<Integer, Messenger> initDataPathEndPoint(int clientId, int pubSubId, int peerId,
+            byte[] peerDiscoveryMac, InOrder inOrder) throws Exception {
+        final int uid = 1000;
+        final String someMsg = "some arbitrary message from peer";
+        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        final PublishConfig publishConfig = new PublishConfig.Builder().build();
+
+        WifiNanNative.Capabilities capabilities = new WifiNanNative.Capabilities();
+        capabilities.maxNdiInterfaces = 1;
+
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Messenger> messengerCaptor = ArgumentCaptor.forClass(Messenger.class);
+        ArgumentCaptor<String> strCaptor = ArgumentCaptor.forClass(String.class);
+
+        // (0) start/registrations
+        inOrder.verify(mMockCm).registerNetworkFactory(messengerCaptor.capture(),
+                strCaptor.capture());
+        collector.checkThat("factory name", "WIFI_NAN_FACTORY", equalTo(strCaptor.getValue()));
+
+        // (1) get capabilities
+        mDut.getCapabilities();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), capabilities);
+        mMockLooper.dispatchAll();
+
+        // (2) enable usage (creates interfaces)
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).createNanNetworkInterface(transactionId.capture(),
+                strCaptor.capture());
+        collector.checkThat("interface created -- 0", sNanInterfacePrefix + 0,
+                equalTo(strCaptor.getValue()));
+        mDut.onCreateDataPathInterfaceResponse(transactionId.getValue(), true, 0);
+        mMockLooper.dispatchAll();
+
+        // (3) create client & session & rx message
+        mDut.connect(clientId, uid, mMockCallback, configRequest);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
+                eq(configRequest), eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockCallback).onConnectSuccess();
+        mDut.publish(clientId, publishConfig, mMockSessionCallback);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishConfig));
+        mDut.onSessionConfigSuccessResponse(transactionId.getValue(), true, pubSubId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockSessionCallback).onSessionStarted(sessionId.capture());
+        mDut.onMessageReceivedNotification(pubSubId, peerId, peerDiscoveryMac, someMsg.getBytes(),
+                someMsg.length());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockSessionCallback).onMessageReceived(peerId, someMsg.getBytes(),
+                someMsg.length());
+
+        return new Pair<>(sessionId.getValue(), messengerCaptor.getValue());
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/nan/WifiNanHalMock.java b/tests/wifitests/src/com/android/server/wifi/nan/WifiNanHalMock.java
index 4f3ba4d..694187e 100644
--- a/tests/wifitests/src/com/android/server/wifi/nan/WifiNanHalMock.java
+++ b/tests/wifitests/src/com/android/server/wifi/nan/WifiNanHalMock.java
@@ -39,6 +39,10 @@
         throw new IllegalStateException("Please mock this class!");
     }
 
+    public void configHalMockNative(short transactionId, String jsonArgs) {
+        throw new IllegalStateException("Please mock this class!");
+    }
+
     public void disableHalMockNative(short transactionId) {
         throw new IllegalStateException("Please mock this class!");
     }
@@ -63,6 +67,26 @@
         throw new IllegalStateException("Please mock this class!");
     }
 
+    public void createNanNetworkInterfaceMockNative(short transactionId, String jsonArgs) {
+        throw new IllegalStateException("Please mock this class!");
+    }
+
+    public void deleteNanNetworkInterfaceMockNative(short transactionId, String jsonArgs) {
+        throw new IllegalStateException("Please mock this class!");
+    }
+
+    public void initiateDataPathMockNative(short transactionId, String jsonArgs) {
+        throw new IllegalStateException("Please mock this class!");
+    }
+
+    public void respondToDataPathRequestMockNative(short transactionId, String jsonArgs) {
+        throw new IllegalStateException("Please mock this class!");
+    }
+
+    public void endDataPathMockNative(short transactionId, String jsonArgs) {
+        throw new IllegalStateException("Please mock this class!");
+    }
+
     /*
      * trigger callbacks - called by test harness with arguments passed by JSON
      * string.
@@ -82,15 +106,23 @@
 
     public static native void callDisabled(String jsonArgs);
 
+    public static native void callTransmitFollowup(String jsonArgs);
+
+    public static native void callDataPathRequest(String jsonArgs);
+
+    public static native void callDataPathConfirm(String jsonArgs);
+
+    public static native void callDataPathEnd(String jsonArgs);
+
     /**
      * initialize NAN mock
      */
     private static native int initNanHalMock();
 
-    public static void initNanHalMockLibrary() throws Exception {
-        Field field = WifiNanNative.class.getDeclaredField("sNanNativeInit");
+    public static void initNanHalMockLibrary(WifiNanNative instance) throws Exception {
+        Field field = WifiNanNative.class.getDeclaredField("mNativeHandlersIsInitialized");
         field.setAccessible(true);
-        field.setBoolean(null, true);
+        field.setBoolean(instance, true);
 
         initNanHalMock();
     }
diff --git a/tests/wifitests/src/com/android/server/wifi/nan/WifiNanHalTest.java b/tests/wifitests/src/com/android/server/wifi/nan/WifiNanHalTest.java
index 536c924..2fef83c 100644
--- a/tests/wifitests/src/com/android/server/wifi/nan/WifiNanHalTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/nan/WifiNanHalTest.java
@@ -18,16 +18,16 @@
 
 import static org.hamcrest.core.IsEqual.equalTo;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 
 import android.net.wifi.nan.ConfigRequest;
-import android.net.wifi.nan.PublishData;
-import android.net.wifi.nan.PublishSettings;
-import android.net.wifi.nan.SubscribeData;
-import android.net.wifi.nan.SubscribeSettings;
+import android.net.wifi.nan.PublishConfig;
+import android.net.wifi.nan.SubscribeConfig;
 import android.net.wifi.nan.TlvBufferUtils;
-import android.net.wifi.nan.WifiNanSessionListener;
+import android.net.wifi.nan.WifiNanEventCallback;
+import android.net.wifi.nan.WifiNanSessionCallback;
 import android.os.Bundle;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -67,8 +67,10 @@
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
 
+        resetWifiNanNative();
+
         HalMockUtils.initHalMockLibrary();
-        WifiNanHalMock.initNanHalMockLibrary();
+        WifiNanHalMock.initNanHalMockLibrary(mDut);
         WifiNanNative.initNanHandlersNative(WifiNative.class, WifiNative.sWlan0Index);
         HalMockUtils.setHalMockObject(mNanHalMock);
         installMockNanStateManager(mNanStateManager);
@@ -97,6 +99,14 @@
     }
 
     @Test
+    public void testConfigCall() throws JSONException {
+        final short transactionId = 31235;
+        final short masterPref = 157;
+
+        testConfig(transactionId, masterPref);
+    }
+
+    @Test
     public void testDisable() {
         final short transactionId = 5478;
 
@@ -113,6 +123,7 @@
         final String ssi = "some much longer and more arbitrary data";
         final int publishCount = 7;
         final int publishTtl = 66;
+        final boolean enableTerminateNotification = true;
 
         TlvBufferUtils.TlvConstructor tlvTx = new TlvBufferUtils.TlvConstructor(0, 1);
         tlvTx.allocate(150).putByte(0, (byte) 10).putInt(0, 100).putString(0, "some string")
@@ -122,8 +133,8 @@
         tlvRx.allocate(150).putByte(0, (byte) 66).putInt(0, 127).putString(0, "some other string")
                 .putZeroLengthElement(0).putByteArray(0, serviceName.getBytes());
 
-        testPublish(transactionId, publishId, PublishSettings.PUBLISH_TYPE_UNSOLICITED, serviceName,
-                ssi, tlvTx, tlvRx, publishCount, publishTtl);
+        testPublish(transactionId, publishId, PublishConfig.PUBLISH_TYPE_UNSOLICITED, serviceName,
+                ssi, tlvTx, tlvRx, publishCount, publishTtl, enableTerminateNotification);
     }
 
     @Test
@@ -134,6 +145,7 @@
         final String ssi = "some much longer arbitrary data";
         final int publishCount = 32;
         final int publishTtl = 33;
+        final boolean enableTerminateNotification = false;
 
         TlvBufferUtils.TlvConstructor tlvTx = new TlvBufferUtils.TlvConstructor(0, 1);
         tlvTx.allocate(150).putByte(0, (byte) 10).putInt(0, 100).putString(0, "some string")
@@ -143,8 +155,8 @@
         tlvRx.allocate(150).putByte(0, (byte) 66).putInt(0, 127).putString(0, "some other string")
                 .putZeroLengthElement(0).putByteArray(0, serviceName.getBytes());
 
-        testPublish(transactionId, publishId, PublishSettings.PUBLISH_TYPE_SOLICITED, serviceName,
-                ssi, tlvTx, tlvRx, publishCount, publishTtl);
+        testPublish(transactionId, publishId, PublishConfig.PUBLISH_TYPE_SOLICITED, serviceName,
+                ssi, tlvTx, tlvRx, publishCount, publishTtl, enableTerminateNotification);
     }
 
     @Test
@@ -169,6 +181,8 @@
         final String ssi = "some much longer arbitrary data";
         final int subscribeCount = 32;
         final int subscribeTtl = 33;
+        final int matchStyle = SubscribeConfig.MATCH_STYLE_ALL;
+        final boolean enableTerminateNotification = true;
 
         TlvBufferUtils.TlvConstructor tlvTx = new TlvBufferUtils.TlvConstructor(0, 1);
         tlvTx.allocate(150).putByte(0, (byte) 10).putInt(0, 100).putString(0, "some string")
@@ -178,8 +192,9 @@
         tlvRx.allocate(150).putByte(0, (byte) 66).putInt(0, 127).putString(0, "some other string")
                 .putZeroLengthElement(0).putByteArray(0, serviceName.getBytes());
 
-        testSubscribe(transactionId, subscribeId, SubscribeSettings.SUBSCRIBE_TYPE_PASSIVE,
-                serviceName, ssi, tlvTx, tlvRx, subscribeCount, subscribeTtl);
+        testSubscribe(transactionId, subscribeId, SubscribeConfig.SUBSCRIBE_TYPE_PASSIVE,
+                serviceName, ssi, tlvTx, tlvRx, subscribeCount, subscribeTtl, matchStyle,
+                enableTerminateNotification);
     }
 
     @Test
@@ -190,6 +205,8 @@
         final String ssi = "some much longer arbitrary data";
         final int subscribeCount = 32;
         final int subscribeTtl = 33;
+        final int matchStyle = SubscribeConfig.MATCH_STYLE_FIRST_ONLY;
+        final boolean enableTerminateNotification = false;
 
         TlvBufferUtils.TlvConstructor tlvTx = new TlvBufferUtils.TlvConstructor(0, 1);
         tlvTx.allocate(150).putByte(0, (byte) 10).putInt(0, 100).putString(0, "some string")
@@ -199,8 +216,9 @@
         tlvRx.allocate(150).putByte(0, (byte) 66).putInt(0, 127).putString(0, "some other string")
                 .putZeroLengthElement(0).putByteArray(0, serviceName.getBytes());
 
-        testSubscribe(transactionId, subscribeId, SubscribeSettings.SUBSCRIBE_TYPE_ACTIVE,
-                serviceName, ssi, tlvTx, tlvRx, subscribeCount, subscribeTtl);
+        testSubscribe(transactionId, subscribeId, SubscribeConfig.SUBSCRIBE_TYPE_ACTIVE,
+                serviceName, ssi, tlvTx, tlvRx, subscribeCount, subscribeTtl, matchStyle,
+                enableTerminateNotification);
     }
 
     @Test
@@ -224,9 +242,10 @@
         final int reqInstanceId = 11;
         final byte[] peer = HexEncoding.decode("000102030405".toCharArray(), false);
         final String msg = "Hello there - how are you doing?";
+        final int messageId = 10; /* garbage - not used by HAL */
 
         mDut.sendMessage(transactionId, pubSubId, reqInstanceId, peer, msg.getBytes(),
-                msg.length());
+                msg.length(), messageId);
 
         verify(mNanHalMock).transmitFollowupHalMockNative(eq(transactionId), mArgs.capture());
 
@@ -246,7 +265,7 @@
     }
 
     @Test
-    public void testNotifyCapabilities() throws JSONException {
+    public void testRespondWithCapabilities() throws JSONException {
         final short transactionId = 23;
         final int max_concurrent_nan_clusters = 1;
         final int max_publishes = 2;
@@ -260,6 +279,7 @@
         final int max_ndi_interfaces = 10;
         final int max_ndp_sessions = 11;
         final int max_app_info_len = 12;
+        final int max_queued_transmit_followup_msgs = 7;
 
         ArgumentCaptor<WifiNanNative.Capabilities> capabilitiesCapture = ArgumentCaptor
                 .forClass(WifiNanNative.Capabilities.class);
@@ -283,11 +303,13 @@
         args.putInt("body.nan_capabilities.max_ndi_interfaces", max_ndi_interfaces);
         args.putInt("body.nan_capabilities.max_ndp_sessions", max_ndp_sessions);
         args.putInt("body.nan_capabilities.max_app_info_len", max_app_info_len);
+        args.putInt("body.nan_capabilities.max_queued_transmit_followup_msgs",
+                max_queued_transmit_followup_msgs);
 
         WifiNanHalMock.callNotifyResponse(transactionId,
                 HalMockUtils.convertBundleToJson(args).toString());
 
-        verify(mNanStateManager).onCapabilitiesUpdate(eq(transactionId),
+        verify(mNanStateManager).onCapabilitiesUpdateResponse(eq(transactionId),
                 capabilitiesCapture.capture());
         WifiNanNative.Capabilities capabilities = capabilitiesCapture.getValue();
         collector.checkThat("max_concurrent_nan_clusters", capabilities.maxConcurrentNanClusters,
@@ -312,6 +334,9 @@
                 equalTo(max_ndp_sessions));
         collector.checkThat("max_app_info_len", capabilities.maxAppInfoLen,
                 equalTo(max_app_info_len));
+        collector.checkThat("max_queued_transmit_followup_msgs",
+                capabilities.maxQueuedTransmitMessages, equalTo(max_queued_transmit_followup_msgs));
+        verifyNoMoreInteractions(mNanStateManager);
     }
 
     @Test
@@ -326,7 +351,8 @@
         WifiNanHalMock.callNotifyResponse(transactionId,
                 HalMockUtils.convertBundleToJson(args).toString());
 
-        verify(mNanStateManager).onConfigCompleted(transactionId);
+        verify(mNanStateManager).onConfigSuccessResponse(transactionId);
+        verifyNoMoreInteractions(mNanStateManager);
     }
 
     @Test
@@ -341,8 +367,9 @@
         WifiNanHalMock.callNotifyResponse(transactionId,
                 HalMockUtils.convertBundleToJson(args).toString());
 
-        verify(mNanStateManager).onConfigFailed(transactionId,
-                WifiNanSessionListener.FAIL_REASON_INVALID_ARGS);
+        verify(mNanStateManager).onConfigFailedResponse(transactionId,
+                WifiNanEventCallback.REASON_INVALID_ARGS);
+        verifyNoMoreInteractions(mNanStateManager);
     }
 
     @Test
@@ -359,7 +386,8 @@
         WifiNanHalMock.callNotifyResponse(transactionId,
                 HalMockUtils.convertBundleToJson(args).toString());
 
-        verify(mNanStateManager).onPublishSuccess(transactionId, publishId);
+        verify(mNanStateManager).onSessionConfigSuccessResponse(transactionId, true, publishId);
+        verifyNoMoreInteractions(mNanStateManager);
     }
 
     @Test
@@ -376,14 +404,14 @@
         WifiNanHalMock.callNotifyResponse(transactionId,
                 HalMockUtils.convertBundleToJson(args).toString());
 
-        verify(mNanStateManager).onPublishFail(transactionId,
-                WifiNanSessionListener.FAIL_REASON_NO_RESOURCES);
+        verify(mNanStateManager).onSessionConfigFailResponse(transactionId, true,
+                WifiNanSessionCallback.REASON_NO_RESOURCES);
+        verifyNoMoreInteractions(mNanStateManager);
     }
 
     @Test
     public void testNotifyResponsePublishCancel() throws JSONException {
         final short transactionId = 23;
-        final int publishId = 127;
 
         Bundle args = new Bundle();
         args.putInt("status", WifiNanNative.NAN_STATUS_SUCCESS);
@@ -393,7 +421,7 @@
         WifiNanHalMock.callNotifyResponse(transactionId,
                 HalMockUtils.convertBundleToJson(args).toString());
 
-        verifyZeroInteractions(mNanStateManager);
+        verifyNoMoreInteractions(mNanStateManager);
     }
 
     @Test
@@ -410,7 +438,8 @@
         WifiNanHalMock.callNotifyResponse(transactionId,
                 HalMockUtils.convertBundleToJson(args).toString());
 
-        verify(mNanStateManager).onSubscribeSuccess(transactionId, subscribeId);
+        verify(mNanStateManager).onSessionConfigSuccessResponse(transactionId, false, subscribeId);
+        verifyNoMoreInteractions(mNanStateManager);
     }
 
     @Test
@@ -427,24 +456,24 @@
         WifiNanHalMock.callNotifyResponse(transactionId,
                 HalMockUtils.convertBundleToJson(args).toString());
 
-        verify(mNanStateManager).onSubscribeFail(transactionId,
-                WifiNanSessionListener.FAIL_REASON_OTHER);
+        verify(mNanStateManager).onSessionConfigFailResponse(transactionId, false,
+                WifiNanSessionCallback.REASON_OTHER);
+        verifyNoMoreInteractions(mNanStateManager);
     }
 
     @Test
     public void testNotifyResponseSubscribeCancel() throws JSONException {
         final short transactionId = 23;
-        final int subscribeId = 127;
 
         Bundle args = new Bundle();
-        args.putInt("status", WifiNanNative.NAN_STATUS_SUCCESS);
+        args.putInt("status", WifiNanNative.NAN_STATUS_DE_FAILURE);
         args.putInt("value", 0);
         args.putInt("response_type", WifiNanNative.NAN_RESPONSE_SUBSCRIBE_CANCEL);
 
         WifiNanHalMock.callNotifyResponse(transactionId,
                 HalMockUtils.convertBundleToJson(args).toString());
 
-        verifyZeroInteractions(mNanStateManager);
+        verifyNoMoreInteractions(mNanStateManager);
     }
 
     @Test
@@ -459,7 +488,8 @@
         WifiNanHalMock.callNotifyResponse(transactionId,
                 HalMockUtils.convertBundleToJson(args).toString());
 
-        verify(mNanStateManager).onMessageSendSuccess(transactionId);
+        verify(mNanStateManager).onMessageSendQueuedSuccessResponse(transactionId);
+        verifyNoMoreInteractions(mNanStateManager);
     }
 
     @Test
@@ -474,8 +504,130 @@
         WifiNanHalMock.callNotifyResponse(transactionId,
                 HalMockUtils.convertBundleToJson(args).toString());
 
-        verify(mNanStateManager).onMessageSendFail(transactionId,
-                WifiNanSessionListener.FAIL_REASON_OTHER);
+        verify(mNanStateManager).onMessageSendQueuedFailResponse(transactionId,
+                WifiNanSessionCallback.REASON_OTHER);
+        verifyNoMoreInteractions(mNanStateManager);
+    }
+
+    @Test
+    public void testNotifyResponseCreateDataPath() throws JSONException {
+        final short transactionId = 48;
+        final int reason  = WifiNanNative.NAN_STATUS_TIMEOUT;
+
+        Bundle args = new Bundle();
+        args.putInt("status", reason);
+        args.putInt("value", 0);
+        args.putInt("response_type", WifiNanNative.NAN_RESPONSE_DP_INTERFACE_CREATE);
+
+        WifiNanHalMock.callNotifyResponse(transactionId,
+                HalMockUtils.convertBundleToJson(args).toString());
+
+        verify(mNanStateManager).onCreateDataPathInterfaceResponse(transactionId, false, reason);
+        verifyNoMoreInteractions(mNanStateManager);
+    }
+
+    @Test
+    public void testNotifyResponseDeleteDataPath() throws JSONException {
+        final short transactionId = 49;
+        final int reason  = WifiNanNative.NAN_STATUS_DE_FAILURE;
+
+        Bundle args = new Bundle();
+        args.putInt("status", reason);
+        args.putInt("value", 0);
+        args.putInt("response_type", WifiNanNative.NAN_RESPONSE_DP_INTERFACE_DELETE);
+
+        WifiNanHalMock.callNotifyResponse(transactionId,
+                HalMockUtils.convertBundleToJson(args).toString());
+
+        verify(mNanStateManager).onDeleteDataPathInterfaceResponse(transactionId, false, reason);
+        verifyNoMoreInteractions(mNanStateManager);
+    }
+
+    @Test
+    public void testNotifyResponseInitiateDataPathSuccess() throws JSONException {
+        final short transactionId = 49;
+        final int ndpId = 1234;
+
+        Bundle args = new Bundle();
+        args.putInt("status", WifiNanNative.NAN_STATUS_SUCCESS);
+        args.putInt("value", 0);
+        args.putInt("response_type", WifiNanNative.NAN_RESPONSE_DP_INITIATOR_RESPONSE);
+        args.putInt("body.data_request_response.ndp_instance_id", ndpId);
+
+        WifiNanHalMock.callNotifyResponse(transactionId,
+                HalMockUtils.convertBundleToJson(args).toString());
+
+        verify(mNanStateManager).onInitiateDataPathResponseSuccess(transactionId, ndpId);
+        verifyNoMoreInteractions(mNanStateManager);
+    }
+
+    @Test
+    public void testNotifyResponseInitiateDataPathFail() throws JSONException {
+        final short transactionId = 49;
+        final int reason  = WifiNanNative.NAN_STATUS_NDP_DATA_INITIATOR_REQUEST_FAILED;
+
+        Bundle args = new Bundle();
+        args.putInt("status", reason);
+        args.putInt("value", 0);
+        args.putInt("response_type", WifiNanNative.NAN_RESPONSE_DP_INITIATOR_RESPONSE);
+        args.putInt("body.data_request_response.ndp_instance_id", 5555); // NOP
+
+        WifiNanHalMock.callNotifyResponse(transactionId,
+                HalMockUtils.convertBundleToJson(args).toString());
+
+        verify(mNanStateManager).onInitiateDataPathResponseFail(transactionId, reason);
+        verifyNoMoreInteractions(mNanStateManager);
+    }
+
+    @Test
+    public void testNotifyResponseRespondToDataPathSetupRequest() throws JSONException {
+        final short transactionId = 50;
+        final int reason  = WifiNanNative.NAN_STATUS_DISABLE_IN_PROGRESS;
+
+        Bundle args = new Bundle();
+        args.putInt("status", reason);
+        args.putInt("value", 0);
+        args.putInt("response_type", WifiNanNative.NAN_RESPONSE_DP_RESPONDER_RESPONSE);
+
+        WifiNanHalMock.callNotifyResponse(transactionId,
+                HalMockUtils.convertBundleToJson(args).toString());
+
+        verify(mNanStateManager).onRespondToDataPathSetupRequestResponse(transactionId, false,
+                reason);
+        verifyNoMoreInteractions(mNanStateManager);
+    }
+
+    @Test
+    public void testNotifyResponseEndDataPath() throws JSONException {
+        final short transactionId = 50;
+        final int reason  = WifiNanNative.NAN_STATUS_NDP_END_FAILED;
+
+        Bundle args = new Bundle();
+        args.putInt("status", reason);
+        args.putInt("value", 0);
+        args.putInt("response_type", WifiNanNative.NAN_RESPONSE_DP_END);
+
+        WifiNanHalMock.callNotifyResponse(transactionId,
+                HalMockUtils.convertBundleToJson(args).toString());
+
+        verify(mNanStateManager).onEndDataPathResponse(transactionId, false, reason);
+        verifyNoMoreInteractions(mNanStateManager);
+    }
+
+    @Test
+    public void testNotifyResponseUnknown() throws JSONException {
+        final int invalidTransactionId = 99999;
+        final short transactionId = 46;
+
+        Bundle args = new Bundle();
+        args.putInt("status", WifiNanNative.NAN_STATUS_SUCCESS);
+        args.putInt("value", 0);
+        args.putInt("response_type", invalidTransactionId);
+
+        WifiNanHalMock.callNotifyResponse(transactionId,
+                HalMockUtils.convertBundleToJson(args).toString());
+
+        verifyNoMoreInteractions(mNanStateManager);
     }
 
     @Test
@@ -488,8 +640,9 @@
 
         WifiNanHalMock.callPublishTerminated(HalMockUtils.convertBundleToJson(args).toString());
 
-        verify(mNanStateManager).onPublishTerminated(publishId,
-                WifiNanSessionListener.TERMINATE_REASON_DONE);
+        verify(mNanStateManager).onSessionTerminatedNotification(publishId,
+                WifiNanSessionCallback.TERMINATE_REASON_DONE, true);
+        verifyNoMoreInteractions(mNanStateManager);
     }
 
     @Test
@@ -502,8 +655,9 @@
 
         WifiNanHalMock.callSubscribeTerminated(HalMockUtils.convertBundleToJson(args).toString());
 
-        verify(mNanStateManager).onSubscribeTerminated(subscribeId,
-                WifiNanSessionListener.TERMINATE_REASON_FAIL);
+        verify(mNanStateManager).onSessionTerminatedNotification(subscribeId,
+                WifiNanSessionCallback.TERMINATE_REASON_FAIL, false);
+        verifyNoMoreInteractions(mNanStateManager);
     }
 
     @Test
@@ -523,8 +677,9 @@
 
         WifiNanHalMock.callFollowup(HalMockUtils.convertBundleToJson(args).toString());
 
-        verify(mNanStateManager).onMessageReceived(pubSubId, reqInstanceId, peer,
+        verify(mNanStateManager).onMessageReceivedNotification(pubSubId, reqInstanceId, peer,
                 message.getBytes(), message.length());
+        verifyNoMoreInteractions(mNanStateManager);
     }
 
     @Test
@@ -546,8 +701,9 @@
 
         WifiNanHalMock.callMatch(HalMockUtils.convertBundleToJson(args).toString());
 
-        verify(mNanStateManager).onMatch(pubSubId, reqInstanceId, peer, ssi.getBytes(),
+        verify(mNanStateManager).onMatchNotification(pubSubId, reqInstanceId, peer, ssi.getBytes(),
                 ssi.length(), filter.getBytes(), filter.length());
+        verifyNoMoreInteractions(mNanStateManager);
     }
 
     @Test
@@ -560,11 +716,12 @@
 
         WifiNanHalMock.callDiscEngEvent(HalMockUtils.convertBundleToJson(args).toString());
 
-        verify(mNanStateManager).onInterfaceAddressChange(mac);
+        verify(mNanStateManager).onInterfaceAddressChangeNotification(mac);
+        verifyNoMoreInteractions(mNanStateManager);
     }
 
     @Test
-    public void testClusterChange() throws JSONException {
+    public void testClusterJoined() throws JSONException {
         final byte[] mac = HexEncoding.decode("060504030201".toCharArray(), false);
 
         Bundle args = new Bundle();
@@ -573,8 +730,24 @@
 
         WifiNanHalMock.callDiscEngEvent(HalMockUtils.convertBundleToJson(args).toString());
 
-        verify(mNanStateManager).onClusterChange(WifiNanClientState.CLUSTER_CHANGE_EVENT_JOINED,
-                mac);
+        verify(mNanStateManager)
+                .onClusterChangeNotification(WifiNanClientState.CLUSTER_CHANGE_EVENT_JOINED, mac);
+        verifyNoMoreInteractions(mNanStateManager);
+    }
+
+    @Test
+    public void testClusterStarted() throws JSONException {
+        final byte[] mac = HexEncoding.decode("0A0B0C0B0A00".toCharArray(), false);
+
+        Bundle args = new Bundle();
+        args.putInt("event_type", WifiNanNative.NAN_EVENT_ID_STARTED_CLUSTER);
+        args.putByteArray("data", mac);
+
+        WifiNanHalMock.callDiscEngEvent(HalMockUtils.convertBundleToJson(args).toString());
+
+        verify(mNanStateManager)
+                .onClusterChangeNotification(WifiNanClientState.CLUSTER_CHANGE_EVENT_STARTED, mac);
+        verifyNoMoreInteractions(mNanStateManager);
     }
 
     @Test
@@ -584,7 +757,206 @@
 
         WifiNanHalMock.callDisabled(HalMockUtils.convertBundleToJson(args).toString());
 
-        verify(mNanStateManager).onNanDown(WifiNanSessionListener.FAIL_REASON_OTHER);
+        verify(mNanStateManager).onNanDownNotification(WifiNanEventCallback.REASON_OTHER);
+        verifyNoMoreInteractions(mNanStateManager);
+    }
+
+    @Test
+    public void testTransmitFollowupSuccess() throws JSONException {
+        final short transactionId = 123;
+
+        Bundle args = new Bundle();
+        args.putInt("id", transactionId);
+        args.putInt("reason", WifiNanNative.NAN_STATUS_SUCCESS);
+
+        WifiNanHalMock.callTransmitFollowup(HalMockUtils.convertBundleToJson(args).toString());
+
+        verify(mNanStateManager).onMessageSendSuccessNotification(transactionId);
+        verifyNoMoreInteractions(mNanStateManager);
+    }
+
+    @Test
+    public void testTransmitFollowupFail() throws JSONException {
+        final short transactionId = 5689;
+
+        Bundle args = new Bundle();
+        args.putInt("id", transactionId);
+        args.putInt("reason", WifiNanNative.NAN_STATUS_TX_FAIL);
+
+        WifiNanHalMock.callTransmitFollowup(HalMockUtils.convertBundleToJson(args).toString());
+
+        verify(mNanStateManager).onMessageSendFailNotification(transactionId,
+                WifiNanSessionCallback.REASON_TX_FAIL);
+        verifyNoMoreInteractions(mNanStateManager);
+    }
+
+    @Test
+    public void testCreateNanNetworkInterface() throws JSONException {
+        final short transactionId = 10;
+        final String interfaceName = "nan0";
+
+        mDut.createNanNetworkInterface(transactionId, interfaceName);
+
+        verify(mNanHalMock).createNanNetworkInterfaceMockNative(eq(transactionId), mArgs.capture());
+
+        Bundle argsData = HalMockUtils.convertJsonToBundle(mArgs.getValue());
+
+        collector.checkThat("iface_name", new String(argsData
+                .getByteArray("iface_name")), equalTo(interfaceName));
+    }
+
+    @Test
+    public void testDeleteNanNetworkInterface() throws JSONException {
+        final short transactionId = 10;
+        final String interfaceName = "nan0";
+
+        mDut.deleteNanNetworkInterface(transactionId, interfaceName);
+
+        verify(mNanHalMock).deleteNanNetworkInterfaceMockNative(eq(transactionId), mArgs.capture());
+
+        Bundle argsData = HalMockUtils.convertJsonToBundle(mArgs.getValue());
+
+        collector.checkThat("iface_name", new String(argsData.getByteArray("iface_name")),
+                equalTo(interfaceName));
+    }
+
+    @Test
+    public void testInitiateDataPath() throws JSONException {
+        final short transactionId = 123;
+        final int pubSubId = 55;
+        final int channelRequestType = 0;
+        final int channel = 2437;
+        final byte[] peer = HexEncoding.decode("0A0B0C0D0E0F".toCharArray(), false);
+        final String interfaceName = "nan1";
+        final String msg = "let me talk!";
+
+        mDut.initiateDataPath(transactionId, pubSubId, channelRequestType, channel, peer,
+                interfaceName, msg.getBytes(), msg.length());
+
+        verify(mNanHalMock).initiateDataPathMockNative(eq(transactionId), mArgs.capture());
+
+        Bundle argsData = HalMockUtils.convertJsonToBundle(mArgs.getValue());
+
+        collector.checkThat("service_instance_id", argsData.getInt("service_instance_id"),
+                equalTo(pubSubId));
+        collector.checkThat("channel_request_type", argsData.getInt("channel_request_type"),
+                equalTo(channelRequestType));
+        collector.checkThat("channel", argsData.getInt("channel"), equalTo(channel));
+        collector.checkThat("peer_disc_mac_addr", argsData.getByteArray("peer_disc_mac_addr"),
+                equalTo(peer));
+        collector.checkThat("ndp_iface", new String(argsData.getByteArray("ndp_iface")),
+                equalTo(interfaceName));
+        collector.checkThat("ndp_cfg.security_cfg", argsData.getInt("ndp_cfg.security_cfg"),
+                equalTo(0));
+        collector.checkThat("ndp_cfg.qos_cfg", argsData.getInt("ndp_cfg.qos_cfg"), equalTo(0));
+        collector.checkThat("app_info.ndp_app_info_len",
+                argsData.getInt("app_info.ndp_app_info_len"), equalTo(msg.length()));
+        collector.checkThat("app_info.ndp_app_info", argsData.getByteArray("app_info.ndp_app_info"),
+                equalTo(msg.getBytes()));
+    }
+
+    @Test
+    public void testRespondToDataPathRequest() throws JSONException {
+        final short transactionId = 123;
+        final boolean accept = true;
+        final int ndpId = 523;
+        final String interfaceName = "nan1";
+        final String msg = "fine - you can talk ...";
+
+        mDut.respondToDataPathRequest(transactionId, accept, ndpId, interfaceName, msg
+                .getBytes(), msg.length());
+
+        verify(mNanHalMock).respondToDataPathRequestMockNative(eq(transactionId), mArgs.capture());
+
+        Bundle argsData = HalMockUtils.convertJsonToBundle(mArgs.getValue());
+
+        collector.checkThat("ndp_instance_id", argsData.getInt("ndp_instance_id"), equalTo(ndpId));
+        collector.checkThat("ndp_iface", new String(argsData
+                .getByteArray("ndp_iface")), equalTo(interfaceName));
+        collector.checkThat("ndp_cfg.security_cfg", argsData
+                .getInt("ndp_cfg.security_cfg"), equalTo(0));
+        collector.checkThat("ndp_cfg.qos_cfg", argsData.getInt("ndp_cfg.qos_cfg"), equalTo(0));
+        collector.checkThat("app_info.ndp_app_info_len", argsData
+                .getInt("app_info.ndp_app_info_len"), equalTo(msg.length()));
+        collector.checkThat("app_info.ndp_app_info", argsData
+                .getByteArray("app_info.ndp_app_info"), equalTo(msg.getBytes()));
+        collector.checkThat("rsp_code", argsData.getInt("rsp_code"), equalTo(accept ? 0 : 1));
+    }
+
+    @Test
+    public void testEndDataPath() throws JSONException {
+        final short transactionId = 123;
+        final int ndpId = 523;
+
+        mDut.endDataPath(transactionId, ndpId);
+
+        verify(mNanHalMock).endDataPathMockNative(eq(transactionId), mArgs.capture());
+
+        Bundle argsData = HalMockUtils.convertJsonToBundle(mArgs.getValue());
+
+        collector.checkThat("num_ndp_instances", argsData.getInt("num_ndp_instances"), equalTo(1));
+        collector.checkThat("ndp_instance_id", argsData.getInt("ndp_instance_id"), equalTo(ndpId));
+    }
+
+    @Test
+    public void testOnDataRequest() throws JSONException {
+        final int pubSubId = 1234;
+        final byte[] peer = HexEncoding.decode("0A0B0C0D0E0F".toCharArray(), false);
+        final int ndpId = 752;
+        final String msg = "some request or other - doesn't have to be a String!";
+
+        Bundle args = new Bundle();
+        args.putInt("service_instance_id", pubSubId);
+        args.putByteArray("peer_disc_mac_addr", peer);
+        args.putInt("ndp_instance_id", ndpId);
+        args.putInt("ndp_cfg.security_cfg", 0);
+        args.putInt("ndp_cfg.qos_cfg", 0);
+        args.putInt("app_info.ndp_app_info_len", msg.length());
+        args.putByteArray("app_info.ndp_app_info", msg.getBytes());
+
+        WifiNanHalMock.callDataPathRequest(HalMockUtils.convertBundleToJson(args).toString());
+
+        verify(mNanStateManager).onDataPathRequestNotification(pubSubId, peer, ndpId,
+                msg.getBytes(), msg.length());
+        verifyNoMoreInteractions(mNanStateManager);
+    }
+
+    @Test
+    public void testOnDataConfirm() throws JSONException {
+        final int ndpId = 752;
+        final byte[] peer = HexEncoding.decode("0A0B0C0D0E0F".toCharArray(), false);
+        final String msg = "some request or other - doesn't have to be a String!";
+        final boolean accept = true;
+        final int reason = 13412;
+
+        Bundle args = new Bundle();
+        args.putInt("ndp_instance_id", ndpId);
+        args.putByteArray("peer_ndi_mac_addr", peer);
+        args.putInt("app_info.ndp_app_info_len", msg.length());
+        args.putByteArray("app_info.ndp_app_info", msg.getBytes());
+        args.putInt("rsp_code", accept ? 0 : 1);
+        args.putInt("reason_code", reason);
+
+        WifiNanHalMock.callDataPathConfirm(HalMockUtils.convertBundleToJson(args).toString());
+
+        verify(mNanStateManager).onDataPathConfirmNotification(ndpId, peer, accept, reason,
+                msg.getBytes(), msg.length());
+        verifyNoMoreInteractions(mNanStateManager);
+    }
+
+    @Test
+    public void testOnDataEnd() throws JSONException {
+        testOnDataEndMultiples(1, 752);
+    }
+
+    @Test
+    public void testOnDataEndMultiples() throws JSONException {
+        testOnDataEndMultiples(5, 842);
+    }
+
+    @Test
+    public void testOnDataEndZero() throws JSONException {
+        testOnDataEndMultiples(0, 2134);
     }
 
     /*
@@ -597,7 +969,7 @@
                 .setClusterHigh(clusterHigh).setMasterPreference(masterPref)
                 .setSupport5gBand(enable5g).build();
 
-        mDut.enableAndConfigure(transactionId, configRequest);
+        mDut.enableAndConfigure(transactionId, configRequest, true);
 
         verify(mNanHalMock).enableHalMockNative(eq(transactionId), mArgs.capture());
 
@@ -646,19 +1018,63 @@
                 equalTo(0));
     }
 
+    private void testConfig(short transactionId, int masterPref) throws JSONException {
+        ConfigRequest configRequest = new ConfigRequest.Builder().setMasterPreference(masterPref)
+                .build();
+
+        mDut.enableAndConfigure(transactionId, configRequest, false);
+
+        verify(mNanHalMock).configHalMockNative(eq(transactionId), mArgs.capture());
+
+        Bundle argsData = HalMockUtils.convertJsonToBundle(mArgs.getValue());
+
+        collector.checkThat("config_master_pref", argsData.getInt("config_master_pref"),
+                equalTo(1));
+        collector.checkThat("master_pref", argsData.getInt("master_pref"), equalTo(masterPref));
+
+        collector.checkThat("config_sid_beacon", argsData.getInt("config_sid_beacon"), equalTo(0));
+        collector.checkThat("sid_beacon", argsData.getInt("sid_beacon"), equalTo(0));
+        collector.checkThat("config_rssi_proximity", argsData.getInt("config_rssi_proximity"),
+                equalTo(0));
+        collector.checkThat("rssi_proximity", argsData.getInt("rssi_proximity"), equalTo(0));
+        collector.checkThat("config_5g_rssi_close_proximity",
+                argsData.getInt("config_5g_rssi_close_proximity"), equalTo(0));
+        collector.checkThat("rssi_close_proximity_5g_val",
+                argsData.getInt("rssi_close_proximity_5g_val"), equalTo(0));
+        collector.checkThat("config_rssi_window_size", argsData.getInt("config_rssi_window_size"),
+                equalTo(0));
+        collector.checkThat("rssi_window_size_val", argsData.getInt("rssi_window_size_val"),
+                equalTo(0));
+        collector.checkThat("config_cluster_attribute_val",
+                argsData.getInt("config_cluster_attribute_val"), equalTo(0));
+        collector.checkThat("config_scan_params", argsData.getInt("config_scan_params"),
+                equalTo(0));
+        collector.checkThat("config_random_factor_force",
+                argsData.getInt("config_random_factor_force"), equalTo(0));
+        collector.checkThat("random_factor_force_val", argsData.getInt("random_factor_force_val"),
+                equalTo(0));
+        collector.checkThat("config_hop_count_force", argsData.getInt("config_hop_count_force"),
+                equalTo(0));
+        collector.checkThat("hop_count_force_val", argsData.getInt("hop_count_force_val"),
+                equalTo(0));
+        collector.checkThat("config_conn_capability", argsData.getInt("config_conn_capability"),
+                equalTo(0));
+        collector.checkThat("num_config_discovery_attr",
+                argsData.getInt("num_config_discovery_attr"), equalTo(0));
+        collector.checkThat("config_fam", argsData.getInt("config_fam"), equalTo(0));
+    }
+
     private void testPublish(short transactionId, int publishId, int publishType,
             String serviceName, String ssi, TlvBufferUtils.TlvConstructor tlvTx,
-            TlvBufferUtils.TlvConstructor tlvRx, int publishCount, int publishTtl)
-                    throws JSONException {
-        PublishData publishData = new PublishData.Builder().setServiceName(serviceName)
-                .setServiceSpecificInfo(ssi)
-                .setTxFilter(tlvTx.getArray(), tlvTx.getActualLength())
-                .setRxFilter(tlvRx.getArray(), tlvRx.getActualLength()).build();
+            TlvBufferUtils.TlvConstructor tlvRx, int publishCount, int publishTtl,
+            boolean enableTerminateNotification) throws JSONException {
+        PublishConfig publishConfig = new PublishConfig.Builder().setServiceName(serviceName)
+                .setServiceSpecificInfo(ssi).setTxFilter(tlvTx.getArray(), tlvTx.getActualLength())
+                .setRxFilter(tlvRx.getArray(), tlvRx.getActualLength()).setPublishType(publishType)
+                .setPublishCount(publishCount).setTtlSec(publishTtl)
+                .setEnableTerminateNotification(enableTerminateNotification).build();
 
-        PublishSettings publishSettings = new PublishSettings.Builder().setPublishType(publishType)
-                .setPublishCount(publishCount).setTtlSec(publishTtl).build();
-
-        mDut.publish(transactionId, publishId, publishData, publishSettings);
+        mDut.publish(transactionId, publishId, publishConfig);
 
         verify(mNanHalMock).publishHalMockNative(eq(transactionId), mArgs.capture());
 
@@ -668,7 +1084,7 @@
         collector.checkThat("ttl", argsData.getInt("ttl"), equalTo(publishTtl));
         collector.checkThat("publish_type", argsData.getInt("publish_type"), equalTo(publishType));
         collector.checkThat("tx_type", argsData.getInt("tx_type"),
-                equalTo(publishType == PublishSettings.PUBLISH_TYPE_UNSOLICITED ? 0 : 1));
+                equalTo(publishType == PublishConfig.PUBLISH_TYPE_UNSOLICITED ? 0 : 1));
         collector.checkThat("publish_count", argsData.getInt("publish_count"),
                 equalTo(publishCount));
         collector.checkThat("service_name_len", argsData.getInt("service_name_len"),
@@ -692,22 +1108,23 @@
         collector.checkThat("rssi_threshold_flag", argsData.getInt("rssi_threshold_flag"),
                 equalTo(0));
         collector.checkThat("connmap", argsData.getInt("connmap"), equalTo(0));
+        collector.checkThat("recv_indication_cfg", argsData.getInt("recv_indication_cfg"),
+                equalTo(enableTerminateNotification ? 0x0 : 0x1));
     }
 
     private void testSubscribe(short transactionId, int subscribeId, int subscribeType,
             String serviceName, String ssi, TlvBufferUtils.TlvConstructor tlvTx,
-            TlvBufferUtils.TlvConstructor tlvRx, int subscribeCount, int subscribeTtl)
-                    throws JSONException {
-        SubscribeData subscribeData = new SubscribeData.Builder().setServiceName(serviceName)
+            TlvBufferUtils.TlvConstructor tlvRx, int subscribeCount, int subscribeTtl,
+            int matchStyle, boolean enableTerminateNotification) throws JSONException {
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().setServiceName(serviceName)
                 .setServiceSpecificInfo(ssi)
                 .setTxFilter(tlvTx.getArray(), tlvTx.getActualLength())
-                .setRxFilter(tlvRx.getArray(), tlvRx.getActualLength()).build();
-
-        SubscribeSettings subscribeSettings = new SubscribeSettings.Builder()
+                .setRxFilter(tlvRx.getArray(), tlvRx.getActualLength())
                 .setSubscribeType(subscribeType).setSubscribeCount(subscribeCount)
-                .setTtlSec(subscribeTtl).build();
+                .setTtlSec(subscribeTtl).setMatchStyle(matchStyle)
+                .setEnableTerminateNotification(enableTerminateNotification).build();
 
-        mDut.subscribe(transactionId, subscribeId, subscribeData, subscribeSettings);
+        mDut.subscribe(transactionId, subscribeId, subscribeConfig);
 
         verify(mNanHalMock).subscribeHalMockNative(eq(transactionId), mArgs.capture());
 
@@ -727,7 +1144,7 @@
         collector.checkThat("ssiRequiredForMatchIndication",
                 argsData.getInt("ssiRequiredForMatchIndication"), equalTo(0));
         collector.checkThat("subscribe_match_indicator",
-                argsData.getInt("subscribe_match_indicator"), equalTo(0));
+                argsData.getInt("subscribe_match_indicator"), equalTo(matchStyle));
         collector.checkThat("subscribe_count", argsData.getInt("subscribe_count"),
                 equalTo(subscribeCount));
         collector.checkThat("service_name_len", argsData.getInt("service_name_len"),
@@ -751,6 +1168,27 @@
         collector.checkThat("connmap", argsData.getInt("connmap"), equalTo(0));
         collector.checkThat("num_intf_addr_present", argsData.getInt("num_intf_addr_present"),
                 equalTo(0));
+        collector.checkThat("recv_indication_cfg", argsData.getInt("recv_indication_cfg"),
+                equalTo(enableTerminateNotification ? 0x0 : 0x1));
+    }
+
+    private void testOnDataEndMultiples(int numInstances, int ndpIdBase) throws JSONException {
+        ArgumentCaptor<Integer> ndpIdsCaptor = ArgumentCaptor.forClass(Integer.class);
+
+        Bundle args = new Bundle();
+        args.putInt("num_ndp_instances", numInstances);
+        args.putInt("ndp_instance_id", ndpIdBase);
+
+        WifiNanHalMock.callDataPathEnd(HalMockUtils.convertBundleToJson(args).toString());
+
+        verify(mNanStateManager, times(numInstances)).onDataPathEndNotification(
+                ndpIdsCaptor.capture());
+        verifyNoMoreInteractions(mNanStateManager);
+
+        for (int i = 0; i < numInstances; ++i) {
+            collector.checkThat("ndp id #" + i, ndpIdsCaptor.getAllValues().get(i),
+                    equalTo(ndpIdBase + i));
+        }
     }
 
     private static void installMockNanStateManager(WifiNanStateManager nanStateManager)
@@ -759,4 +1197,10 @@
         field.setAccessible(true);
         field.set(null, nanStateManager);
     }
+
+    private static void resetWifiNanNative() throws Exception {
+        Field field = WifiNanNative.class.getDeclaredField("sWifiNanNativeSingleton");
+        field.setAccessible(true);
+        field.set(null, null);
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/nan/WifiNanManagerTest.java b/tests/wifitests/src/com/android/server/wifi/nan/WifiNanManagerTest.java
index bd4d43b..e143fd8 100644
--- a/tests/wifitests/src/com/android/server/wifi/nan/WifiNanManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/nan/WifiNanManagerTest.java
@@ -18,107 +18,609 @@
 
 import static org.hamcrest.core.IsEqual.equalTo;
 import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
 
+import android.net.wifi.RttManager;
 import android.net.wifi.nan.ConfigRequest;
-import android.net.wifi.nan.PublishData;
-import android.net.wifi.nan.PublishSettings;
-import android.net.wifi.nan.SubscribeData;
-import android.net.wifi.nan.SubscribeSettings;
+import android.net.wifi.nan.IWifiNanEventCallback;
+import android.net.wifi.nan.IWifiNanManager;
+import android.net.wifi.nan.IWifiNanSessionCallback;
+import android.net.wifi.nan.PublishConfig;
+import android.net.wifi.nan.SubscribeConfig;
+import android.net.wifi.nan.WifiNanEventCallback;
+import android.net.wifi.nan.WifiNanManager;
+import android.net.wifi.nan.WifiNanPublishSession;
+import android.net.wifi.nan.WifiNanSessionCallback;
+import android.net.wifi.nan.WifiNanSubscribeSession;
+import android.os.IBinder;
 import android.os.Parcel;
+import android.os.test.TestLooper;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Base64;
 
+import libcore.util.HexEncoding;
+
+import org.json.JSONObject;
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ErrorCollector;
-import org.junit.rules.ExpectedException;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 /**
  * Unit test harness for WifiNanManager class.
  */
 @SmallTest
 public class WifiNanManagerTest {
+    private WifiNanManager mDut;
+    private TestLooper mMockLooper;
+
     @Rule
     public ErrorCollector collector = new ErrorCollector();
 
-    @Rule
-    public ExpectedException thrown = ExpectedException.none();
+    @Mock
+    public WifiNanEventCallback mockCallback;
+
+    @Mock
+    public WifiNanSessionCallback mockSessionCallback;
+
+    @Mock
+    public IWifiNanManager mockNanService;
+
+    @Mock
+    public WifiNanPublishSession mockPublishSession;
+
+    @Mock
+    public WifiNanSubscribeSession mockSubscribeSession;
+
+    @Mock
+    public RttManager.RttListener mockRttListener;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mDut = new WifiNanManager(mockNanService);
+        mMockLooper = new TestLooper();
+    }
+
+    /*
+     * Straight pass-through tests
+     */
+
+    /**
+     * Validate pass-through of enableUsage() API.
+     */
+    @Test
+    public void testEnableUsage() throws Exception {
+        mDut.enableUsage();
+
+        verify(mockNanService).enableUsage();
+    }
+
+    /**
+     * Validate pass-through of disableUsage() API.
+     */
+    @Test
+    public void testDisableUsage() throws Exception {
+        mDut.disableUsage();
+
+        verify(mockNanService).disableUsage();
+    }
+
+    /**
+     * Validate pass-through of isUsageEnabled() API.
+     */
+    @Test
+    public void testIsUsageEnable() throws Exception {
+        mDut.isUsageEnabled();
+
+        verify(mockNanService).isUsageEnabled();
+    }
+
+    /*
+     * WifiNanEventCallbackProxy Tests
+     */
+
+    /**
+     * Validate the successful connect flow: (1) try subscribing (2) connect +
+     * success (3) publish, (4) disconnect (5) try publishing (6) connect again
+     */
+    @Test
+    public void testConnectFlow() throws Exception {
+        final int clientId = 4565;
+
+        when(mockNanService.connect(any(IBinder.class), any(IWifiNanEventCallback.class),
+                any(ConfigRequest.class))).thenReturn(clientId);
+
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mockNanService);
+        ArgumentCaptor<IWifiNanEventCallback> clientProxyCallback = ArgumentCaptor
+                .forClass(IWifiNanEventCallback.class);
+        ArgumentCaptor<IBinder> binder = ArgumentCaptor.forClass(IBinder.class);
+
+        // (1) try subscribing on an unconnected manager: fails silently
+        mDut.subscribe(new SubscribeConfig.Builder().build(), mockSessionCallback);
+
+        // (2) connect + success
+        mDut.connect(mMockLooper.getLooper(), mockCallback);
+        inOrder.verify(mockNanService).connect(binder.capture(),
+                clientProxyCallback.capture(), (ConfigRequest) isNull());
+        clientProxyCallback.getValue().onConnectSuccess();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
+
+        // (3) publish - should succeed
+        PublishConfig publishConfig = new PublishConfig.Builder().build();
+        mDut.publish(publishConfig, mockSessionCallback);
+        inOrder.verify(mockNanService).publish(eq(clientId), eq(publishConfig),
+                any(IWifiNanSessionCallback.class));
+
+        // (4) disconnect
+        mDut.disconnect();
+        inOrder.verify(mockNanService).disconnect(eq(clientId), eq(binder.getValue()));
+
+        // (5) try publishing again - fails silently
+        mDut.publish(new PublishConfig.Builder().build(), mockSessionCallback);
+
+        // (6) connect
+        mDut.connect(mMockLooper.getLooper(), mockCallback);
+        inOrder.verify(mockNanService).connect(binder.capture(), any(IWifiNanEventCallback.class),
+                (ConfigRequest) isNull());
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockNanService);
+    }
+
+    /**
+     * Validate the failed connect flow: (1) connect + failure, (2) try
+     * publishing (3) connect + success (4) subscribe
+     */
+    @Test
+    public void testConnectFailure() throws Exception {
+        final int clientId = 4565;
+        final int reason = WifiNanEventCallback.REASON_OTHER;
+
+        when(mockNanService.connect(any(IBinder.class), any(IWifiNanEventCallback.class),
+                any(ConfigRequest.class))).thenReturn(clientId);
+
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mockNanService);
+        ArgumentCaptor<IWifiNanEventCallback> clientProxyCallback = ArgumentCaptor
+                .forClass(IWifiNanEventCallback.class);
+
+        // (1) connect + failure
+        mDut.connect(mMockLooper.getLooper(), mockCallback);
+        inOrder.verify(mockNanService).connect(any(IBinder.class), clientProxyCallback.capture(),
+                (ConfigRequest) isNull());
+        clientProxyCallback.getValue().onConnectFail(reason);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectFail(reason);
+
+        // (2) try publishing - silent failure (since already know that no
+        // connection)
+        mDut.publish(new PublishConfig.Builder().build(), mockSessionCallback);
+
+        // (3) connect + success
+        mDut.connect(mMockLooper.getLooper(), mockCallback);
+        inOrder.verify(mockNanService).connect(any(IBinder.class), clientProxyCallback.capture(),
+                (ConfigRequest) isNull());
+        clientProxyCallback.getValue().onConnectSuccess();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
+
+        // (4) subscribe: should succeed
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
+        mDut.subscribe(subscribeConfig, mockSessionCallback);
+        inOrder.verify(mockNanService).subscribe(eq(clientId), eq(subscribeConfig),
+                any(IWifiNanSessionCallback.class));
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockNanService);
+    }
+
+    /**
+     * Validate that cannot call connect on an existing connection: (1) connect
+     * + success, (2) try connect again
+     */
+    @Test
+    public void testInvalidConnectSequence() throws Exception {
+        final int clientId = 4565;
+
+        when(mockNanService.connect(any(IBinder.class), any(IWifiNanEventCallback.class),
+                any(ConfigRequest.class))).thenReturn(clientId);
+
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mockNanService);
+        ArgumentCaptor<IWifiNanEventCallback> clientProxyCallback = ArgumentCaptor
+                .forClass(IWifiNanEventCallback.class);
+
+        // (1) connect + success
+        mDut.connect(mMockLooper.getLooper(), mockCallback);
+        inOrder.verify(mockNanService).connect(any(IBinder.class), clientProxyCallback.capture(),
+                (ConfigRequest) isNull());
+        clientProxyCallback.getValue().onConnectSuccess();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
+
+        // (2) connect - forward to service (though will fail silently)
+        mDut.connect(mMockLooper.getLooper(), mockCallback);
+        inOrder.verify(mockNanService).connect(any(IBinder.class), clientProxyCallback.capture(),
+                (ConfigRequest) isNull());
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockNanService);
+    }
+
+    /*
+     * WifiNanSessionCallbackProxy Tests
+     */
+
+    /**
+     * Validate the publish flow: (0) connect + success, (1) publish, (2)
+     * success creates session, (3) pass through everything, (4) update publish
+     * through session, (5) terminate locally, (6) try another command -
+     * ignored.
+     */
+    @Test
+    public void testPublishFlow() throws Exception {
+        final int clientId = 4565;
+        final int sessionId = 123;
+        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        final PublishConfig publishConfig = new PublishConfig.Builder().build();
+        final int peerId = 873;
+        final String string1 = "hey from here...";
+        final String string2 = "some other arbitrary string...";
+        final int messageId = 2123;
+        final int reason = WifiNanSessionCallback.REASON_OTHER;
+
+        when(mockNanService.connect(any(IBinder.class), any(IWifiNanEventCallback.class),
+                eq(configRequest))).thenReturn(clientId);
+
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mockNanService,
+                mockPublishSession);
+        ArgumentCaptor<IWifiNanEventCallback> clientProxyCallback = ArgumentCaptor
+                .forClass(IWifiNanEventCallback.class);
+        ArgumentCaptor<IWifiNanSessionCallback> sessionProxyCallback = ArgumentCaptor
+                .forClass(IWifiNanSessionCallback.class);
+        ArgumentCaptor<WifiNanPublishSession> publishSession = ArgumentCaptor
+                .forClass(WifiNanPublishSession.class);
+
+        // (0) connect + success
+        mDut.connect(mMockLooper.getLooper(), mockCallback, configRequest);
+        inOrder.verify(mockNanService).connect(any(IBinder.class),
+                clientProxyCallback.capture(), eq(configRequest));
+        clientProxyCallback.getValue().onConnectSuccess();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
+
+        // (1) publish
+        mDut.publish(publishConfig, mockSessionCallback);
+        inOrder.verify(mockNanService).publish(eq(clientId), eq(publishConfig),
+                sessionProxyCallback.capture());
+
+        // (2) publish session created
+        sessionProxyCallback.getValue().onSessionStarted(sessionId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onPublishStarted(publishSession.capture());
+
+        // (3) ...
+        publishSession.getValue().sendMessage(peerId, string1.getBytes(), string1.length(),
+                messageId);
+        sessionProxyCallback.getValue().onMatch(peerId, string1.getBytes(),
+                string1.length(), string2.getBytes(), string2.length());
+        sessionProxyCallback.getValue().onMessageReceived(peerId, string1.getBytes(),
+                string1.length());
+        sessionProxyCallback.getValue().onMessageSendFail(messageId, reason);
+        sessionProxyCallback.getValue().onMessageSendSuccess(messageId);
+        mMockLooper.dispatchAll();
+
+        inOrder.verify(mockNanService).sendMessage(eq(clientId), eq(sessionId), eq(peerId),
+                eq(string1.getBytes()), eq(string1.length()), eq(messageId), eq(0));
+        inOrder.verify(mockSessionCallback).onMatch(eq(peerId), eq(string1.getBytes()),
+                eq(string1.length()), eq(string2.getBytes()), eq(string2.length()));
+        inOrder.verify(mockSessionCallback).onMessageReceived(eq(peerId), eq(string1.getBytes()),
+                eq(string1.length()));
+        inOrder.verify(mockSessionCallback).onMessageSendFail(eq(messageId), eq(reason));
+        inOrder.verify(mockSessionCallback).onMessageSendSuccess(eq(messageId));
+
+        // (4) update publish
+        publishSession.getValue().updatePublish(publishConfig);
+        sessionProxyCallback.getValue().onSessionConfigFail(reason);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockNanService).updatePublish(eq(clientId), eq(sessionId),
+                eq(publishConfig));
+        inOrder.verify(mockSessionCallback).onSessionConfigFail(eq(reason));
+
+        // (5) terminate
+        publishSession.getValue().terminate();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockNanService).terminateSession(clientId, sessionId);
+
+        // (6) try an update (nothing)
+        publishSession.getValue().updatePublish(publishConfig);
+        mMockLooper.dispatchAll();
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockNanService,
+                mockPublishSession);
+    }
+
+    /**
+     * Validate race condition of session terminate and session action: (1)
+     * connect, (2) publish success + terminate, (3) update.
+     */
+    @Test
+    public void testPublishRemoteTerminate() throws Exception {
+        final int clientId = 4565;
+        final int sessionId = 123;
+        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        final PublishConfig publishConfig = new PublishConfig.Builder().build();
+        final int reason = WifiNanSessionCallback.TERMINATE_REASON_DONE;
+
+        when(mockNanService.connect(any(IBinder.class), any(IWifiNanEventCallback.class),
+                eq(configRequest))).thenReturn(clientId);
+
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mockNanService,
+                mockPublishSession);
+        ArgumentCaptor<IWifiNanEventCallback> clientProxyCallback = ArgumentCaptor
+                .forClass(IWifiNanEventCallback.class);
+        ArgumentCaptor<IWifiNanSessionCallback> sessionProxyCallback = ArgumentCaptor
+                .forClass(IWifiNanSessionCallback.class);
+        ArgumentCaptor<WifiNanPublishSession> publishSession = ArgumentCaptor
+                .forClass(WifiNanPublishSession.class);
+
+        // (1) connect successfully
+        mDut.connect(mMockLooper.getLooper(), mockCallback, configRequest);
+        inOrder.verify(mockNanService).connect(any(IBinder.class), clientProxyCallback.capture(),
+                eq(configRequest));
+        clientProxyCallback.getValue().onConnectSuccess();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
+
+        // (2) publish: successfully - then terminated
+        mDut.publish(publishConfig, mockSessionCallback);
+        inOrder.verify(mockNanService).publish(eq(clientId), eq(publishConfig),
+                sessionProxyCallback.capture());
+        sessionProxyCallback.getValue().onSessionStarted(sessionId);
+        sessionProxyCallback.getValue().onSessionTerminated(reason);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onPublishStarted(publishSession.capture());
+        inOrder.verify(mockSessionCallback).onSessionTerminated(reason);
+
+        // (3) failure when trying to update: NOP
+        publishSession.getValue().updatePublish(publishConfig);
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockNanService,
+                mockPublishSession);
+    }
+
+    /**
+     * Validate the subscribe flow: (0) connect + success, (1) subscribe, (2)
+     * success creates session, (3) pass through everything, (4) update
+     * subscribe through session, (5) terminate locally, (6) try another command
+     * - ignored.
+     */
+    @Test
+    public void testSubscribeFlow() throws Exception {
+        final int clientId = 4565;
+        final int sessionId = 123;
+        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        final SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
+        final int peerId = 873;
+        final String string1 = "hey from here...";
+        final String string2 = "some other arbitrary string...";
+        final int messageId = 2123;
+        final int reason = WifiNanSessionCallback.REASON_OTHER;
+
+        when(mockNanService.connect(any(IBinder.class), any(IWifiNanEventCallback.class),
+                eq(configRequest))).thenReturn(clientId);
+
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mockNanService,
+                mockSubscribeSession);
+        ArgumentCaptor<IWifiNanEventCallback> clientProxyCallback = ArgumentCaptor
+                .forClass(IWifiNanEventCallback.class);
+        ArgumentCaptor<IWifiNanSessionCallback> sessionProxyCallback = ArgumentCaptor
+                .forClass(IWifiNanSessionCallback.class);
+        ArgumentCaptor<WifiNanSubscribeSession> subscribeSession = ArgumentCaptor
+                .forClass(WifiNanSubscribeSession.class);
+
+        // (0) connect + success
+        mDut.connect(mMockLooper.getLooper(), mockCallback, configRequest);
+        inOrder.verify(mockNanService).connect(any(IBinder.class), clientProxyCallback.capture(),
+                eq(configRequest));
+        clientProxyCallback.getValue().onConnectSuccess();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
+
+        // (1) subscribe
+        mDut.subscribe(subscribeConfig, mockSessionCallback);
+        inOrder.verify(mockNanService).subscribe(eq(clientId), eq(subscribeConfig),
+                sessionProxyCallback.capture());
+
+        // (2) subscribe session created
+        sessionProxyCallback.getValue().onSessionStarted(sessionId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSubscribeStarted(subscribeSession.capture());
+
+        // (3) ...
+        subscribeSession.getValue().sendMessage(peerId, string1.getBytes(), string1.length(),
+                messageId);
+        sessionProxyCallback.getValue().onMatch(peerId, string1.getBytes(), string1.length(),
+                string2.getBytes(), string2.length());
+        sessionProxyCallback.getValue().onMessageReceived(peerId, string1.getBytes(),
+                string1.length());
+        sessionProxyCallback.getValue().onMessageSendFail(messageId, reason);
+        sessionProxyCallback.getValue().onMessageSendSuccess(messageId);
+        mMockLooper.dispatchAll();
+
+        inOrder.verify(mockNanService).sendMessage(eq(clientId), eq(sessionId), eq(peerId),
+                eq(string1.getBytes()), eq(string1.length()), eq(messageId), eq(0));
+        inOrder.verify(mockSessionCallback).onMatch(eq(peerId), eq(string1.getBytes()),
+                eq(string1.length()), eq(string2.getBytes()), eq(string2.length()));
+        inOrder.verify(mockSessionCallback).onMessageReceived(eq(peerId), eq(string1.getBytes()),
+                eq(string1.length()));
+        inOrder.verify(mockSessionCallback).onMessageSendFail(eq(messageId), eq(reason));
+        inOrder.verify(mockSessionCallback).onMessageSendSuccess(eq(messageId));
+
+        // (4) update subscribe
+        subscribeSession.getValue().updateSubscribe(subscribeConfig);
+        sessionProxyCallback.getValue().onSessionConfigFail(reason);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockNanService).updateSubscribe(eq(clientId), eq(sessionId),
+                eq(subscribeConfig));
+        inOrder.verify(mockSessionCallback).onSessionConfigFail(eq(reason));
+
+        // (5) terminate
+        subscribeSession.getValue().terminate();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockNanService).terminateSession(clientId, sessionId);
+
+        // (6) try an update (nothing)
+        subscribeSession.getValue().updateSubscribe(subscribeConfig);
+        mMockLooper.dispatchAll();
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockNanService,
+                mockSubscribeSession);
+    }
+
+    /**
+     * Validate race condition of session terminate and session action: (1)
+     * connect, (2) subscribe success + terminate, (3) update.
+     */
+    @Test
+    public void testSubscribeRemoteTerminate() throws Exception {
+        final int clientId = 4565;
+        final int sessionId = 123;
+        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        final SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
+        final int reason = WifiNanSessionCallback.TERMINATE_REASON_DONE;
+
+        when(mockNanService.connect(any(IBinder.class), any(IWifiNanEventCallback.class),
+                eq(configRequest))).thenReturn(clientId);
+
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mockNanService,
+                mockSubscribeSession);
+        ArgumentCaptor<IWifiNanEventCallback> clientProxyCallback = ArgumentCaptor
+                .forClass(IWifiNanEventCallback.class);
+        ArgumentCaptor<IWifiNanSessionCallback> sessionProxyCallback = ArgumentCaptor
+                .forClass(IWifiNanSessionCallback.class);
+        ArgumentCaptor<WifiNanSubscribeSession> subscribeSession = ArgumentCaptor
+                .forClass(WifiNanSubscribeSession.class);
+
+        // (1) connect successfully
+        mDut.connect(mMockLooper.getLooper(), mockCallback, configRequest);
+        inOrder.verify(mockNanService).connect(any(IBinder.class), clientProxyCallback.capture(),
+                eq(configRequest));
+        clientProxyCallback.getValue().onConnectSuccess();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
+
+        // (2) subscribe: successfully - then terminated
+        mDut.subscribe(subscribeConfig, mockSessionCallback);
+        inOrder.verify(mockNanService).subscribe(eq(clientId), eq(subscribeConfig),
+                sessionProxyCallback.capture());
+        sessionProxyCallback.getValue().onSessionStarted(sessionId);
+        sessionProxyCallback.getValue().onSessionTerminated(reason);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSubscribeStarted(subscribeSession.capture());
+        inOrder.verify(mockSessionCallback).onSessionTerminated(reason);
+
+        // (3) failure when trying to update: NOP
+        subscribeSession.getValue().updateSubscribe(subscribeConfig);
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockNanService,
+                mockSubscribeSession);
+    }
 
     /*
      * ConfigRequest Tests
      */
 
     @Test
+    public void testConfigRequestBuilderDefaults() {
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+
+        collector.checkThat("mClusterHigh", ConfigRequest.CLUSTER_ID_MAX,
+                equalTo(configRequest.mClusterHigh));
+        collector.checkThat("mClusterLow", ConfigRequest.CLUSTER_ID_MIN,
+                equalTo(configRequest.mClusterLow));
+        collector.checkThat("mMasterPreference", 0,
+                equalTo(configRequest.mMasterPreference));
+        collector.checkThat("mSupport5gBand", false, equalTo(configRequest.mSupport5gBand));
+        collector.checkThat("mEnableIdentityChangeCallback", false,
+                equalTo(configRequest.mEnableIdentityChangeCallback));
+    }
+
+    @Test
     public void testConfigRequestBuilder() {
         final int clusterHigh = 100;
         final int clusterLow = 5;
         final int masterPreference = 55;
         final boolean supportBand5g = true;
+        final boolean enableIdentityChangeCallback = true;
 
         ConfigRequest configRequest = new ConfigRequest.Builder().setClusterHigh(clusterHigh)
                 .setClusterLow(clusterLow).setMasterPreference(masterPreference)
-                .setSupport5gBand(supportBand5g).build();
+                .setSupport5gBand(supportBand5g)
+                .setEnableIdentityChangeCallback(enableIdentityChangeCallback).build();
 
         collector.checkThat("mClusterHigh", clusterHigh, equalTo(configRequest.mClusterHigh));
         collector.checkThat("mClusterLow", clusterLow, equalTo(configRequest.mClusterLow));
         collector.checkThat("mMasterPreference", masterPreference,
                 equalTo(configRequest.mMasterPreference));
         collector.checkThat("mSupport5gBand", supportBand5g, equalTo(configRequest.mSupport5gBand));
+        collector.checkThat("mEnableIdentityChangeCallback", enableIdentityChangeCallback,
+                equalTo(configRequest.mEnableIdentityChangeCallback));
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testConfigRequestBuilderMasterPrefNegative() {
-        thrown.expect(IllegalArgumentException.class);
         ConfigRequest.Builder builder = new ConfigRequest.Builder();
         builder.setMasterPreference(-1);
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testConfigRequestBuilderMasterPrefReserved1() {
-        thrown.expect(IllegalArgumentException.class);
         new ConfigRequest.Builder().setMasterPreference(1);
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testConfigRequestBuilderMasterPrefReserved255() {
-        thrown.expect(IllegalArgumentException.class);
         new ConfigRequest.Builder().setMasterPreference(255);
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testConfigRequestBuilderMasterPrefTooLarge() {
-        thrown.expect(IllegalArgumentException.class);
         new ConfigRequest.Builder().setMasterPreference(256);
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testConfigRequestBuilderClusterLowNegative() {
-        thrown.expect(IllegalArgumentException.class);
         new ConfigRequest.Builder().setClusterLow(-1);
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testConfigRequestBuilderClusterHighNegative() {
-        thrown.expect(IllegalArgumentException.class);
         new ConfigRequest.Builder().setClusterHigh(-1);
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testConfigRequestBuilderClusterLowAboveMax() {
-        thrown.expect(IllegalArgumentException.class);
         new ConfigRequest.Builder().setClusterLow(ConfigRequest.CLUSTER_ID_MAX + 1);
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testConfigRequestBuilderClusterHighAboveMax() {
-        thrown.expect(IllegalArgumentException.class);
         new ConfigRequest.Builder().setClusterHigh(ConfigRequest.CLUSTER_ID_MAX + 1);
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testConfigRequestBuilderClusterLowLargerThanHigh() {
-        thrown.expect(IllegalArgumentException.class);
-        ConfigRequest configRequest = new ConfigRequest.Builder().setClusterLow(100)
-                .setClusterHigh(5).build();
+        new ConfigRequest.Builder().setClusterLow(100).setClusterHigh(5).build();
     }
 
     @Test
@@ -127,10 +629,12 @@
         final int clusterLow = 25;
         final int masterPreference = 177;
         final boolean supportBand5g = true;
+        final boolean enableIdentityChangeCallback = true;
 
         ConfigRequest configRequest = new ConfigRequest.Builder().setClusterHigh(clusterHigh)
                 .setClusterLow(clusterLow).setMasterPreference(masterPreference)
-                .setSupport5gBand(supportBand5g).build();
+                .setSupport5gBand(supportBand5g)
+                .setEnableIdentityChangeCallback(enableIdentityChangeCallback).build();
 
         Parcel parcelW = Parcel.obtain();
         configRequest.writeToParcel(parcelW, 0);
@@ -146,237 +650,430 @@
     }
 
     /*
-     * SubscribeData Tests
+     * SubscribeConfig Tests
      */
 
     @Test
-    public void testSubscribeDataBuilder() {
+    public void testSubscribeConfigBuilderDefaults() {
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
+
+        collector.checkThat("mServiceName", subscribeConfig.mServiceName, equalTo(null));
+        collector.checkThat("mServiceSpecificInfoLength",
+                subscribeConfig.mServiceSpecificInfoLength, equalTo(0));
+        collector.checkThat("mTxFilterLength", subscribeConfig.mTxFilterLength, equalTo(0));
+        collector.checkThat("mRxFilterLength", subscribeConfig.mRxFilterLength, equalTo(0));
+        collector.checkThat("mSubscribeType", subscribeConfig.mSubscribeType,
+                equalTo(SubscribeConfig.SUBSCRIBE_TYPE_PASSIVE));
+        collector.checkThat("mSubscribeCount", subscribeConfig.mSubscribeCount, equalTo(0));
+        collector.checkThat("mTtlSec", subscribeConfig.mTtlSec, equalTo(0));
+        collector.checkThat("mMatchStyle", subscribeConfig.mMatchStyle,
+                equalTo(SubscribeConfig.MATCH_STYLE_ALL));
+        collector.checkThat("mEnableTerminateNotification",
+                subscribeConfig.mEnableTerminateNotification, equalTo(true));
+    }
+
+    @Test
+    public void testSubscribeConfigBuilder() {
         final String serviceName = "some_service_or_other";
         final String serviceSpecificInfo = "long arbitrary string with some info";
         final byte[] txFilter = {
                 0, 1, 16, 1, 22 };
         final byte[] rxFilter = {
                 1, 127, 0, 1, -5, 1, 22 };
-
-        SubscribeData subscribeData = new SubscribeData.Builder().setServiceName(serviceName)
-                .setServiceSpecificInfo(serviceSpecificInfo).setTxFilter(txFilter, txFilter.length)
-                .setRxFilter(rxFilter, rxFilter.length).build();
-
-        collector.checkThat("mServiceName", serviceName, equalTo(subscribeData.mServiceName));
-        String mServiceSpecificInfo = new String(subscribeData.mServiceSpecificInfo, 0,
-                subscribeData.mServiceSpecificInfoLength);
-        collector.checkThat("mServiceSpecificInfo",
-                utilAreArraysEqual(serviceSpecificInfo.getBytes(), serviceSpecificInfo.length(),
-                        subscribeData.mServiceSpecificInfo,
-                        subscribeData.mServiceSpecificInfoLength),
-                equalTo(true));
-        collector.checkThat("mTxFilter", utilAreArraysEqual(txFilter, txFilter.length,
-                subscribeData.mTxFilter, subscribeData.mTxFilterLength), equalTo(true));
-        collector.checkThat("mRxFilter", utilAreArraysEqual(rxFilter, rxFilter.length,
-                subscribeData.mRxFilter, subscribeData.mRxFilterLength), equalTo(true));
-    }
-
-    @Test
-    public void testSubscribeDataParcel() {
-        final String serviceName = "some_service_or_other";
-        final String serviceSpecificInfo = "long arbitrary string with some info";
-        final byte[] txFilter = {
-                0, 1, 16, 1, 22 };
-        final byte[] rxFilter = {
-                1, 127, 0, 1, -5, 1, 22 };
-
-        SubscribeData subscribeData = new SubscribeData.Builder().setServiceName(serviceName)
-                .setServiceSpecificInfo(serviceSpecificInfo).setTxFilter(txFilter, txFilter.length)
-                .setTxFilter(rxFilter, rxFilter.length).build();
-
-        Parcel parcelW = Parcel.obtain();
-        subscribeData.writeToParcel(parcelW, 0);
-        byte[] bytes = parcelW.marshall();
-        parcelW.recycle();
-
-        Parcel parcelR = Parcel.obtain();
-        parcelR.unmarshall(bytes, 0, bytes.length);
-        parcelR.setDataPosition(0);
-        SubscribeData rereadSubscribeData = SubscribeData.CREATOR.createFromParcel(parcelR);
-
-        assertEquals(subscribeData, rereadSubscribeData);
-    }
-
-    /*
-     * SubscribeSettings Tests
-     */
-
-    @Test
-    public void testSubscribeSettingsBuilder() {
-        final int subscribeType = SubscribeSettings.SUBSCRIBE_TYPE_PASSIVE;
+        final int subscribeType = SubscribeConfig.SUBSCRIBE_TYPE_PASSIVE;
         final int subscribeCount = 10;
         final int subscribeTtl = 15;
+        final int matchStyle = SubscribeConfig.MATCH_STYLE_FIRST_ONLY;
+        final boolean enableTerminateNotification = false;
 
-        SubscribeSettings subscribeSetting = new SubscribeSettings.Builder()
-                .setSubscribeType(subscribeType).setSubscribeCount(subscribeCount)
-                .setTtlSec(subscribeTtl).build();
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().setServiceName(serviceName)
+                .setServiceSpecificInfo(serviceSpecificInfo).setTxFilter(txFilter, txFilter.length)
+                .setRxFilter(rxFilter, rxFilter.length).setSubscribeType(subscribeType)
+                .setSubscribeCount(subscribeCount).setTtlSec(subscribeTtl).setMatchStyle(matchStyle)
+                .setEnableTerminateNotification(enableTerminateNotification).build();
 
+        collector.checkThat("mServiceName", serviceName.getBytes(),
+                equalTo(subscribeConfig.mServiceName));
+        collector.checkThat("mServiceSpecificInfo",
+                utilAreArraysEqual(serviceSpecificInfo.getBytes(), serviceSpecificInfo.length(),
+                        subscribeConfig.mServiceSpecificInfo,
+                        subscribeConfig.mServiceSpecificInfoLength),
+                equalTo(true));
+        collector.checkThat("mTxFilter", utilAreArraysEqual(txFilter, txFilter.length,
+                subscribeConfig.mTxFilter, subscribeConfig.mTxFilterLength), equalTo(true));
+        collector.checkThat("mRxFilter", utilAreArraysEqual(rxFilter, rxFilter.length,
+                subscribeConfig.mRxFilter, subscribeConfig.mRxFilterLength), equalTo(true));
         collector.checkThat("mSubscribeType", subscribeType,
-                equalTo(subscribeSetting.mSubscribeType));
+                equalTo(subscribeConfig.mSubscribeType));
         collector.checkThat("mSubscribeCount", subscribeCount,
-                equalTo(subscribeSetting.mSubscribeCount));
-        collector.checkThat("mTtlSec", subscribeTtl, equalTo(subscribeSetting.mTtlSec));
+                equalTo(subscribeConfig.mSubscribeCount));
+        collector.checkThat("mTtlSec", subscribeTtl, equalTo(subscribeConfig.mTtlSec));
+        collector.checkThat("mMatchStyle", matchStyle, equalTo(subscribeConfig.mMatchStyle));
+        collector.checkThat("mEnableTerminateNotification", enableTerminateNotification,
+                equalTo(subscribeConfig.mEnableTerminateNotification));
     }
 
     @Test
-    public void testSubscribeSettingsBuilderBadSubscribeType() {
-        thrown.expect(IllegalArgumentException.class);
-        new SubscribeSettings.Builder().setSubscribeType(10);
-    }
-
-    @Test
-    public void testSubscribeSettingsBuilderNegativeCount() {
-        thrown.expect(IllegalArgumentException.class);
-        new SubscribeSettings.Builder().setSubscribeCount(-1);
-    }
-
-    @Test
-    public void testSubscribeSettingsBuilderNegativeTtl() {
-        thrown.expect(IllegalArgumentException.class);
-        new SubscribeSettings.Builder().setTtlSec(-100);
-    }
-
-    @Test
-    public void testSubscribeSettingsParcel() {
-        final int subscribeType = SubscribeSettings.SUBSCRIBE_TYPE_PASSIVE;
+    public void testSubscribeConfigParcel() {
+        final String serviceName = "some_service_or_other";
+        final String serviceSpecificInfo = "long arbitrary string with some info";
+        final byte[] txFilter = {
+                0, 1, 16, 1, 22 };
+        final byte[] rxFilter = {
+                1, 127, 0, 1, -5, 1, 22 };
+        final int subscribeType = SubscribeConfig.SUBSCRIBE_TYPE_PASSIVE;
         final int subscribeCount = 10;
         final int subscribeTtl = 15;
+        final int matchStyle = SubscribeConfig.MATCH_STYLE_FIRST_ONLY;
+        final boolean enableTerminateNotification = true;
 
-        SubscribeSettings subscribeSetting = new SubscribeSettings.Builder()
-                .setSubscribeType(subscribeType).setSubscribeCount(subscribeCount)
-                .setTtlSec(subscribeTtl).build();
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().setServiceName(serviceName)
+                .setServiceSpecificInfo(serviceSpecificInfo).setTxFilter(txFilter, txFilter.length)
+                .setTxFilter(rxFilter, rxFilter.length).setSubscribeType(subscribeType)
+                .setSubscribeCount(subscribeCount).setTtlSec(subscribeTtl).setMatchStyle(matchStyle)
+                .setEnableTerminateNotification(enableTerminateNotification).build();
 
         Parcel parcelW = Parcel.obtain();
-        subscribeSetting.writeToParcel(parcelW, 0);
+        subscribeConfig.writeToParcel(parcelW, 0);
         byte[] bytes = parcelW.marshall();
         parcelW.recycle();
 
         Parcel parcelR = Parcel.obtain();
         parcelR.unmarshall(bytes, 0, bytes.length);
         parcelR.setDataPosition(0);
-        SubscribeSettings rereadSubscribeSettings = SubscribeSettings.CREATOR
-                .createFromParcel(parcelR);
+        SubscribeConfig rereadSubscribeConfig = SubscribeConfig.CREATOR.createFromParcel(parcelR);
 
-        assertEquals(subscribeSetting, rereadSubscribeSettings);
+        assertEquals(subscribeConfig, rereadSubscribeConfig);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSubscribeConfigBuilderBadSubscribeType() {
+        new SubscribeConfig.Builder().setSubscribeType(10);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSubscribeConfigBuilderNegativeCount() {
+        new SubscribeConfig.Builder().setSubscribeCount(-1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSubscribeConfigBuilderNegativeTtl() {
+        new SubscribeConfig.Builder().setTtlSec(-100);
+    }
+
+    /**
+     * Validate that a bad match style configuration throws an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testSubscribeConfigBuilderBadMatchStyle() {
+        new SubscribeConfig.Builder().setMatchStyle(10);
     }
 
     /*
-     * PublishData Tests
+     * PublishConfig Tests
      */
 
     @Test
-    public void testPublishDataBuilder() {
+    public void testPublishConfigBuilderDefaults() {
+        PublishConfig publishConfig = new PublishConfig.Builder().build();
+
+        collector.checkThat("mServiceName", publishConfig.mServiceName, equalTo(null));
+        collector.checkThat("mServiceSpecificInfoLength", publishConfig.mServiceSpecificInfoLength,
+                equalTo(0));
+        collector.checkThat("mTxFilterLength", publishConfig.mTxFilterLength, equalTo(0));
+        collector.checkThat("mRxFilterLength", publishConfig.mRxFilterLength, equalTo(0));
+        collector.checkThat("mPublishType", publishConfig.mPublishType,
+                equalTo(PublishConfig.PUBLISH_TYPE_UNSOLICITED));
+        collector.checkThat("mPublishCount", publishConfig.mPublishCount, equalTo(0));
+        collector.checkThat("mTtlSec", publishConfig.mTtlSec, equalTo(0));
+        collector.checkThat("mEnableTerminateNotification",
+                publishConfig.mEnableTerminateNotification, equalTo(true));
+    }
+
+    @Test
+    public void testPublishConfigBuilder() {
         final String serviceName = "some_service_or_other";
         final String serviceSpecificInfo = "long arbitrary string with some info";
         final byte[] txFilter = {
                 0, 1, 16, 1, 22 };
         final byte[] rxFilter = {
                 1, 127, 0, 1, -5, 1, 22 };
+        final int publishType = PublishConfig.PUBLISH_TYPE_SOLICITED;
+        final int publishCount = 10;
+        final int publishTtl = 15;
+        final boolean enableTerminateNotification = false;
 
-        PublishData publishData = new PublishData.Builder().setServiceName(serviceName)
+        PublishConfig publishConfig = new PublishConfig.Builder().setServiceName(serviceName)
                 .setServiceSpecificInfo(serviceSpecificInfo).setTxFilter(txFilter, txFilter.length)
-                .setRxFilter(rxFilter, rxFilter.length).build();
+                .setRxFilter(rxFilter, rxFilter.length).setPublishType(publishType)
+                .setPublishCount(publishCount).setTtlSec(publishTtl)
+                .setEnableTerminateNotification(enableTerminateNotification).build();
 
-        collector.checkThat("mServiceName", serviceName, equalTo(publishData.mServiceName));
-        String mServiceSpecificInfo = new String(publishData.mServiceSpecificInfo, 0,
-                publishData.mServiceSpecificInfoLength);
+        collector.checkThat("mServiceName", serviceName.getBytes(),
+                equalTo(publishConfig.mServiceName));
         collector.checkThat("mServiceSpecificInfo",
                 utilAreArraysEqual(serviceSpecificInfo.getBytes(), serviceSpecificInfo.length(),
-                        publishData.mServiceSpecificInfo, publishData.mServiceSpecificInfoLength),
+                        publishConfig.mServiceSpecificInfo,
+                        publishConfig.mServiceSpecificInfoLength),
                 equalTo(true));
         collector.checkThat("mTxFilter", utilAreArraysEqual(txFilter, txFilter.length,
-                publishData.mTxFilter, publishData.mTxFilterLength), equalTo(true));
+                publishConfig.mTxFilter, publishConfig.mTxFilterLength), equalTo(true));
         collector.checkThat("mRxFilter", utilAreArraysEqual(rxFilter, rxFilter.length,
-                publishData.mRxFilter, publishData.mRxFilterLength), equalTo(true));
+                publishConfig.mRxFilter, publishConfig.mRxFilterLength), equalTo(true));
+        collector.checkThat("mPublishType", publishType, equalTo(publishConfig.mPublishType));
+        collector.checkThat("mPublishCount", publishCount, equalTo(publishConfig.mPublishCount));
+        collector.checkThat("mTtlSec", publishTtl, equalTo(publishConfig.mTtlSec));
+        collector.checkThat("mEnableTerminateNotification", enableTerminateNotification,
+                equalTo(publishConfig.mEnableTerminateNotification));
     }
 
     @Test
-    public void testPublishDataParcel() {
+    public void testPublishConfigParcel() {
         final String serviceName = "some_service_or_other";
         final String serviceSpecificInfo = "long arbitrary string with some info";
         final byte[] txFilter = {
                 0, 1, 16, 1, 22 };
         final byte[] rxFilter = {
                 1, 127, 0, 1, -5, 1, 22 };
+        final int publishType = PublishConfig.PUBLISH_TYPE_SOLICITED;
+        final int publishCount = 10;
+        final int publishTtl = 15;
+        final boolean enableTerminateNotification = false;
 
-        PublishData publishData = new PublishData.Builder().setServiceName(serviceName)
+        PublishConfig publishConfig = new PublishConfig.Builder().setServiceName(serviceName)
                 .setServiceSpecificInfo(serviceSpecificInfo).setTxFilter(txFilter, txFilter.length)
-                .setTxFilter(rxFilter, rxFilter.length).build();
+                .setTxFilter(rxFilter, rxFilter.length).setPublishType(publishType)
+                .setPublishCount(publishCount).setTtlSec(publishTtl)
+                .setEnableTerminateNotification(enableTerminateNotification).build();
 
         Parcel parcelW = Parcel.obtain();
-        publishData.writeToParcel(parcelW, 0);
+        publishConfig.writeToParcel(parcelW, 0);
         byte[] bytes = parcelW.marshall();
         parcelW.recycle();
 
         Parcel parcelR = Parcel.obtain();
         parcelR.unmarshall(bytes, 0, bytes.length);
         parcelR.setDataPosition(0);
-        PublishData rereadPublishData = PublishData.CREATOR.createFromParcel(parcelR);
+        PublishConfig rereadPublishConfig = PublishConfig.CREATOR.createFromParcel(parcelR);
 
-        assertEquals(publishData, rereadPublishData);
+        assertEquals(publishConfig, rereadPublishConfig);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testPublishConfigBuilderBadPublishType() {
+        new PublishConfig.Builder().setPublishType(5);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testPublishConfigBuilderNegativeCount() {
+        new PublishConfig.Builder().setPublishCount(-4);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testPublishConfigBuilderNegativeTtl() {
+        new PublishConfig.Builder().setTtlSec(-10);
     }
 
     /*
-     * PublishSettings Tests
+     * Ranging tests
      */
 
+    /**
+     * Validate ranging + success flow: (1) connect, (2) create a (publish) session, (3) start
+     * ranging, (4) ranging success callback, (5) ranging aborted callback ignored (since
+     * listener removed).
+     */
     @Test
-    public void testPublishSettingsBuilder() {
-        final int publishType = PublishSettings.PUBLISH_TYPE_SOLICITED;
-        final int publishCount = 10;
-        final int publishTtl = 15;
+    public void testRangingCallbacks() throws Exception {
+        final int clientId = 4565;
+        final int sessionId = 123;
+        final int rangingId = 3482;
+        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        final PublishConfig publishConfig = new PublishConfig.Builder().build();
+        final RttManager.RttParams rttParams = new RttManager.RttParams();
+        rttParams.deviceType = RttManager.RTT_PEER_NAN;
+        rttParams.bssid = Integer.toString(1234);
+        final RttManager.RttResult rttResults = new RttManager.RttResult();
+        rttResults.distance = 10;
 
-        PublishSettings publishSetting = new PublishSettings.Builder().setPublishType(publishType)
-                .setPublishCount(publishCount).setTtlSec(publishTtl).build();
+        when(mockNanService.connect(any(IBinder.class), any(IWifiNanEventCallback.class),
+                eq(configRequest))).thenReturn(clientId);
+        when(mockNanService.startRanging(anyInt(), anyInt(),
+                any(RttManager.ParcelableRttParams.class))).thenReturn(rangingId);
 
-        collector.checkThat("mPublishType", publishType, equalTo(publishSetting.mPublishType));
-        collector.checkThat("mPublishCount", publishCount, equalTo(publishSetting.mPublishCount));
-        collector.checkThat("mTtlSec", publishTtl, equalTo(publishSetting.mTtlSec));
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mockNanService,
+                mockPublishSession, mockRttListener);
+        ArgumentCaptor<IWifiNanEventCallback> clientProxyCallback = ArgumentCaptor
+                .forClass(IWifiNanEventCallback.class);
+        ArgumentCaptor<IWifiNanSessionCallback> sessionProxyCallback = ArgumentCaptor
+                .forClass(IWifiNanSessionCallback.class);
+        ArgumentCaptor<WifiNanPublishSession> publishSession = ArgumentCaptor
+                .forClass(WifiNanPublishSession.class);
+        ArgumentCaptor<RttManager.ParcelableRttParams> rttParamCaptor = ArgumentCaptor
+                .forClass(RttManager.ParcelableRttParams.class);
+        ArgumentCaptor<RttManager.RttResult[]> rttResultsCaptor = ArgumentCaptor
+                .forClass(RttManager.RttResult[].class);
+
+        // (1) connect successfully
+        mDut.connect(mMockLooper.getLooper(), mockCallback, configRequest);
+        inOrder.verify(mockNanService).connect(any(IBinder.class), clientProxyCallback.capture(),
+                eq(configRequest));
+        clientProxyCallback.getValue().onConnectSuccess();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
+
+        // (2) publish successfully
+        mDut.publish(publishConfig, mockSessionCallback);
+        inOrder.verify(mockNanService).publish(eq(clientId), eq(publishConfig),
+                sessionProxyCallback.capture());
+        sessionProxyCallback.getValue().onSessionStarted(sessionId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onPublishStarted(publishSession.capture());
+
+        // (3) start ranging
+        publishSession.getValue().startRanging(new RttManager.RttParams[]{rttParams},
+                mockRttListener);
+        inOrder.verify(mockNanService).startRanging(eq(clientId), eq(sessionId),
+                rttParamCaptor.capture());
+        collector.checkThat("RttParams.deviceType", rttParams.deviceType,
+                equalTo(rttParamCaptor.getValue().mParams[0].deviceType));
+        collector.checkThat("RttParams.bssid", rttParams.bssid,
+                equalTo(rttParamCaptor.getValue().mParams[0].bssid));
+
+        // (4) ranging success callback
+        clientProxyCallback.getValue().onRangingSuccess(rangingId,
+                new RttManager.ParcelableRttResults(new RttManager.RttResult[] { rttResults }));
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockRttListener).onSuccess(rttResultsCaptor.capture());
+        collector.checkThat("RttResult.distance", rttResults.distance,
+                equalTo(rttResultsCaptor.getValue()[0].distance));
+
+        // (5) ranging aborted callback (should be ignored since listener cleared on first callback)
+        clientProxyCallback.getValue().onRangingAborted(rangingId);
+        mMockLooper.dispatchAll();
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockNanService,
+                mockPublishSession, mockRttListener);
     }
 
+    /*
+     * Data-path tests
+     */
+
+    /**
+     * Validate that correct network specifier is generated for client-based data-path.
+     */
     @Test
-    public void testPublishSettingsBuilderBadPublishType() {
-        thrown.expect(IllegalArgumentException.class);
-        new PublishSettings.Builder().setPublishType(5);
+    public void testNetworkSpecifierWithClient() throws Exception {
+        final int clientId = 4565;
+        final int sessionId = 123;
+        final int peerId = 123412;
+        final int role = WifiNanManager.WIFI_NAN_DATA_PATH_ROLE_INITIATOR;
+        final String token = "Some arbitrary token string - can really be anything";
+        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        final PublishConfig publishConfig = new PublishConfig.Builder().build();
+
+        String tokenB64 = Base64.encodeToString(token.getBytes(), Base64.DEFAULT);
+
+        ArgumentCaptor<IWifiNanEventCallback> clientProxyCallback = ArgumentCaptor
+                .forClass(IWifiNanEventCallback.class);
+        ArgumentCaptor<IWifiNanSessionCallback> sessionProxyCallback = ArgumentCaptor
+                .forClass(IWifiNanSessionCallback.class);
+        ArgumentCaptor<WifiNanPublishSession> publishSession = ArgumentCaptor
+                .forClass(WifiNanPublishSession.class);
+
+        when(mockNanService.connect(any(IBinder.class), any(IWifiNanEventCallback.class),
+                any(ConfigRequest.class))).thenReturn(clientId);
+
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mockNanService,
+                mockPublishSession, mockRttListener);
+
+        // (1) connect successfully
+        mDut.connect(mMockLooper.getLooper(), mockCallback, configRequest);
+        inOrder.verify(mockNanService).connect(any(IBinder.class), clientProxyCallback.capture(),
+                eq(configRequest));
+        clientProxyCallback.getValue().onConnectSuccess();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
+
+        // (2) publish successfully
+        mDut.publish(publishConfig, mockSessionCallback);
+        inOrder.verify(mockNanService).publish(eq(clientId), eq(publishConfig),
+                sessionProxyCallback.capture());
+        sessionProxyCallback.getValue().onSessionStarted(sessionId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onPublishStarted(publishSession.capture());
+
+        // (3) request a network specifier from the session
+        String networkSpecifier = publishSession.getValue().createNetworkSpecifier(role, peerId,
+                token.getBytes(), token.length());
+
+        // validate format
+        JSONObject jsonObject = new JSONObject(networkSpecifier);
+        collector.checkThat("role", role,
+                equalTo(jsonObject.getInt(WifiNanManager.NETWORK_SPECIFIER_KEY_ROLE)));
+        collector.checkThat("client_id", clientId,
+                equalTo(jsonObject.getInt(WifiNanManager.NETWORK_SPECIFIER_KEY_CLIENT_ID)));
+        collector.checkThat("session_id", sessionId,
+                equalTo(jsonObject.getInt(WifiNanManager.NETWORK_SPECIFIER_KEY_SESSION_ID)));
+        collector.checkThat("peer_id", peerId,
+                equalTo(jsonObject.getInt(WifiNanManager.NETWORK_SPECIFIER_KEY_PEER_ID)));
+        collector.checkThat("token", tokenB64,
+                equalTo(jsonObject.getString(WifiNanManager.NETWORK_SPECIFIER_KEY_TOKEN)));
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockNanService,
+                mockPublishSession, mockRttListener);
     }
 
+    /**
+     * Validate that correct network specifier is generated for a direct data-path (i.e.
+     * specifying MAC address as opposed to a client-based oqaque specification).
+     */
     @Test
-    public void testPublishSettingsBuilderNegativeCount() {
-        thrown.expect(IllegalArgumentException.class);
-        new PublishSettings.Builder().setPublishCount(-4);
-    }
+    public void testNetworkSpecifierDirect() throws Exception {
+        final int clientId = 134;
+        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        final byte[] someMac = HexEncoding.decode("000102030405".toCharArray(), false);
+        final int role = WifiNanManager.WIFI_NAN_DATA_PATH_ROLE_INITIATOR;
+        final String token = "Some arbitrary token string - can really be anything";
 
-    @Test
-    public void testPublishSettingsBuilderNegativeTtl() {
-        thrown.expect(IllegalArgumentException.class);
-        new PublishSettings.Builder().setTtlSec(-10);
-    }
+        String tokenB64 = Base64.encodeToString(token.getBytes(), Base64.DEFAULT);
 
-    @Test
-    public void testPublishSettingsParcel() {
-        final int publishType = PublishSettings.PUBLISH_TYPE_SOLICITED;
-        final int publishCount = 10;
-        final int publishTtl = 15;
+        ArgumentCaptor<IWifiNanEventCallback> clientProxyCallback = ArgumentCaptor
+                .forClass(IWifiNanEventCallback.class);
 
-        PublishSettings configSetting = new PublishSettings.Builder().setPublishType(publishType)
-                .setPublishCount(publishCount).setTtlSec(publishTtl).build();
+        when(mockNanService.connect(any(IBinder.class), any(IWifiNanEventCallback.class),
+                any(ConfigRequest.class))).thenReturn(clientId);
 
-        Parcel parcelW = Parcel.obtain();
-        configSetting.writeToParcel(parcelW, 0);
-        byte[] bytes = parcelW.marshall();
-        parcelW.recycle();
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mockNanService,
+                mockPublishSession, mockRttListener);
 
-        Parcel parcelR = Parcel.obtain();
-        parcelR.unmarshall(bytes, 0, bytes.length);
-        parcelR.setDataPosition(0);
-        PublishSettings rereadPublishSettings = PublishSettings.CREATOR.createFromParcel(parcelR);
+        // (1) connect successfully
+        mDut.connect(mMockLooper.getLooper(), mockCallback, configRequest);
+        inOrder.verify(mockNanService).connect(any(IBinder.class), clientProxyCallback.capture(),
+                eq(configRequest));
+        clientProxyCallback.getValue().onConnectSuccess();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
 
-        assertEquals(configSetting, rereadPublishSettings);
+        /* (2) request a direct network specifier*/
+        String networkSpecifier = mDut.createNetworkSpecifier(role, someMac, token.getBytes(),
+                token.length());
+
+        /* validate format*/
+        JSONObject jsonObject = new JSONObject(networkSpecifier);
+        collector.checkThat("role", role,
+                equalTo(jsonObject.getInt(WifiNanManager.NETWORK_SPECIFIER_KEY_ROLE)));
+        collector.checkThat("client_id", clientId,
+                equalTo(jsonObject.getInt(WifiNanManager.NETWORK_SPECIFIER_KEY_CLIENT_ID)));
+        collector.checkThat("peer_mac", someMac, equalTo(HexEncoding.decode(
+                jsonObject.getString(WifiNanManager.NETWORK_SPECIFIER_KEY_PEER_MAC).toCharArray(),
+                false)));
+        collector.checkThat("token", tokenB64,
+                equalTo(jsonObject.getString(WifiNanManager.NETWORK_SPECIFIER_KEY_TOKEN)));
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockNanService,
+                mockPublishSession, mockRttListener);
     }
 
     /*
diff --git a/tests/wifitests/src/com/android/server/wifi/nan/WifiNanRttStateManagerTest.java b/tests/wifitests/src/com/android/server/wifi/nan/WifiNanRttStateManagerTest.java
new file mode 100644
index 0000000..35a600a
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/nan/WifiNanRttStateManagerTest.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.nan;
+
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.hamcrest.core.IsNull.nullValue;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.wifi.IRttManager;
+import android.net.wifi.RttManager;
+import android.os.Handler;
+import android.os.Message;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.internal.util.test.BidirectionalAsyncChannelServer;
+import android.os.test.TestLooper;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ErrorCollector;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit test harness for WifiNanManager class.
+ */
+@SmallTest
+public class WifiNanRttStateManagerTest {
+    private WifiNanRttStateManager mDut;
+    private TestLooper mTestLooper;
+
+    @Mock
+    private Context mMockContext;
+
+    @Mock
+    private Handler mMockHandler;
+
+    @Mock
+    private IRttManager mMockRttService;
+
+    @Rule
+    public ErrorCollector collector = new ErrorCollector();
+
+    /**
+     * Initialize mocks.
+     */
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mDut = new WifiNanRttStateManager();
+        mTestLooper = new TestLooper();
+        BidirectionalAsyncChannelServer server = new BidirectionalAsyncChannelServer(
+                mMockContext, mTestLooper.getLooper(), mMockHandler);
+        when(mMockRttService.getMessenger()).thenReturn(server.getMessenger());
+
+        mDut.startWithRttService(mMockContext, mTestLooper.getLooper(), mMockRttService);
+    }
+
+    /**
+     * Validates that startRanging flow works: (1) start ranging, (2) get success callback - pass
+     * to client (while nulling BSSID info), (3) get fail callback - ignored (since client
+     * cleaned-out after first callback).
+     */
+    @Test
+    public void testStartRanging() throws Exception {
+        final int rangingId = 1234;
+        WifiNanClientState mockClient = mock(WifiNanClientState.class);
+        RttManager.RttParams[] params = new RttManager.RttParams[1];
+        params[0] = new RttManager.RttParams();
+        RttManager.ParcelableRttResults results =
+                new RttManager.ParcelableRttResults(new RttManager.RttResult[2]);
+        results.mResults[0] = new RttManager.RttResult();
+        results.mResults[0].bssid = "something non-null";
+        results.mResults[1] = new RttManager.RttResult();
+        results.mResults[1].bssid = "really really non-null";
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        ArgumentCaptor<RttManager.ParcelableRttResults> rttResultsCaptor =
+                ArgumentCaptor.forClass(RttManager.ParcelableRttResults.class);
+
+        InOrder inOrder = inOrder(mMockHandler, mockClient);
+
+        // (1) start ranging
+        mDut.startRanging(rangingId, mockClient, params);
+        mTestLooper.dispatchAll();
+        inOrder.verify(mMockHandler).handleMessage(messageCaptor.capture());
+        Message msg = messageCaptor.getValue();
+        collector.checkThat("msg.what=RttManager.CMD_OP_START_RANGING", msg.what,
+                equalTo(RttManager.CMD_OP_START_RANGING));
+        collector.checkThat("rangingId", msg.arg2, equalTo(rangingId));
+        collector.checkThat("RTT params", ((RttManager.ParcelableRttParams) msg.obj).mParams,
+                equalTo(params));
+
+        // (2) get success callback - pass to client
+        Message successMessage = Message.obtain();
+        successMessage.what = RttManager.CMD_OP_SUCCEEDED;
+        successMessage.arg2 = rangingId;
+        successMessage.obj = results;
+        msg.replyTo.send(successMessage);
+        mTestLooper.dispatchAll();
+        inOrder.verify(mockClient).onRangingSuccess(eq(rangingId), rttResultsCaptor.capture());
+        collector.checkThat("ParcelableRttResults object", results,
+                equalTo(rttResultsCaptor.getValue()));
+        collector.checkThat("RttResults[0].bssid null",
+                rttResultsCaptor.getValue().mResults[0].bssid, nullValue());
+        collector.checkThat("RttResults[1].bssid null",
+                rttResultsCaptor.getValue().mResults[1].bssid, nullValue());
+
+        // (3) get fail callback - ignored
+        Message failMessage = Message.obtain();
+        failMessage.what = RttManager.CMD_OP_ABORTED;
+        failMessage.arg2 = rangingId;
+        msg.replyTo.send(failMessage);
+        mTestLooper.dispatchAll();
+
+        verifyNoMoreInteractions(mMockHandler, mockClient);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/nan/WifiNanServiceImplTest.java b/tests/wifitests/src/com/android/server/wifi/nan/WifiNanServiceImplTest.java
new file mode 100644
index 0000000..3fb2abb
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/nan/WifiNanServiceImplTest.java
@@ -0,0 +1,518 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.nan;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.wifi.RttManager;
+import android.net.wifi.nan.ConfigRequest;
+import android.net.wifi.nan.IWifiNanEventCallback;
+import android.net.wifi.nan.IWifiNanSessionCallback;
+import android.net.wifi.nan.PublishConfig;
+import android.net.wifi.nan.SubscribeConfig;
+import android.os.IBinder;
+import android.os.Looper;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.lang.reflect.Field;
+
+/**
+ * Unit test harness for WifiNanStateManager.
+ */
+@SmallTest
+public class WifiNanServiceImplTest {
+    private WifiNanServiceImplSpy mDut;
+    private int mDefaultUid = 1500;
+
+    @Mock
+    private Context mContextMock;
+    @Mock
+    private PackageManager mPackageManagerMock;
+    @Mock
+    private WifiNanStateManager mNanStateManagerMock;
+    @Mock
+    private IBinder mBinderMock;
+    @Mock
+    private IWifiNanEventCallback mCallbackMock;
+    @Mock
+    private IWifiNanSessionCallback mSessionCallbackMock;
+
+    /**
+     * Using instead of spy to avoid native crash failures - possibly due to
+     * spy's copying of state.
+     */
+    private class WifiNanServiceImplSpy extends WifiNanServiceImpl {
+        public int fakeUid;
+
+        WifiNanServiceImplSpy(Context context) {
+            super(context);
+        }
+
+        /**
+         * Return the fake UID instead of the real one: pseudo-spy
+         * implementation.
+         */
+        @Override
+        public int getMockableCallingUid() {
+            return fakeUid;
+        }
+    }
+
+    /**
+     * Initializes mocks.
+     */
+    @Before
+    public void setup() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        when(mContextMock.getApplicationContext()).thenReturn(mContextMock);
+        when(mContextMock.getPackageManager()).thenReturn(mPackageManagerMock);
+        when(mPackageManagerMock.hasSystemFeature(PackageManager.FEATURE_WIFI_NAN))
+                .thenReturn(true);
+
+        installMockNanStateManager();
+
+        mDut = new WifiNanServiceImplSpy(mContextMock);
+        mDut.fakeUid = mDefaultUid;
+    }
+
+    /**
+     * Validate start() function: passes a valid looper.
+     */
+    @Test
+    public void testStart() {
+        mDut.start();
+
+        verify(mNanStateManagerMock).start(eq(mContextMock), any(Looper.class));
+    }
+
+    /**
+     * Validate enableUsage() function
+     */
+    @Test
+    public void testEnableUsage() {
+        mDut.enableUsage();
+
+        verify(mNanStateManagerMock).enableUsage();
+    }
+
+    /**
+     * Validate disableUsage() function
+     */
+    @Test
+    public void testDisableUsage() throws Exception {
+        mDut.enableUsage();
+        doConnect();
+        mDut.disableUsage();
+
+        verify(mNanStateManagerMock).disableUsage();
+    }
+
+    /**
+     * Validate isUsageEnabled() function
+     */
+    @Test
+    public void testIsUsageEnabled() {
+        mDut.isUsageEnabled();
+
+        verify(mNanStateManagerMock).isUsageEnabled();
+    }
+
+
+    /**
+     * Validate connect() - returns and uses a client ID.
+     */
+    @Test
+    public void testConnect() {
+        doConnect();
+    }
+
+    /**
+     * Validate connect() when a non-null config is passed.
+     */
+    @Test
+    public void testConnectWithConfig() {
+        ConfigRequest configRequest = new ConfigRequest.Builder().setMasterPreference(55).build();
+
+        int returnedClientId = mDut.connect(mBinderMock, mCallbackMock, configRequest);
+
+        ArgumentCaptor<Integer> clientId = ArgumentCaptor.forClass(Integer.class);
+        verify(mNanStateManagerMock).connect(clientId.capture(), anyInt(), eq(mCallbackMock),
+                eq(configRequest));
+        assertEquals(returnedClientId, (int) clientId.getValue());
+    }
+
+    /**
+     * Validate disconnect() - correct pass-through args.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testDisconnect() throws Exception {
+        int clientId = doConnect();
+
+        mDut.disconnect(clientId, mBinderMock);
+
+        verify(mNanStateManagerMock).disconnect(clientId);
+        validateInternalStateCleanedUp(clientId);
+    }
+
+    /**
+     * Validate that security exception thrown when attempting operation using
+     * an invalid client ID.
+     */
+    @Test(expected = SecurityException.class)
+    public void testFailOnInvalidClientId() {
+        mDut.disconnect(-1, mBinderMock);
+    }
+
+    /**
+     * Validate that security exception thrown when attempting operation using a
+     * client ID which was already cleared-up.
+     */
+    @Test(expected = SecurityException.class)
+    public void testFailOnClearedUpClientId() throws Exception {
+        int clientId = doConnect();
+
+        mDut.disconnect(clientId, mBinderMock);
+
+        verify(mNanStateManagerMock).disconnect(clientId);
+        validateInternalStateCleanedUp(clientId);
+
+        mDut.disconnect(clientId, mBinderMock);
+    }
+
+    /**
+     * Validate that trying to use a client ID from a UID which is different
+     * from the one that created it fails - and that the internal state is not
+     * modified so that a valid call (from the correct UID) will subsequently
+     * succeed.
+     */
+    @Test
+    public void testFailOnAccessClientIdFromWrongUid() throws Exception {
+        int clientId = doConnect();
+
+        mDut.fakeUid = mDefaultUid + 1;
+
+        /*
+         * Not using thrown.expect(...) since want to test that subsequent
+         * access works.
+         */
+        boolean failsAsExpected = false;
+        try {
+            mDut.disconnect(clientId, mBinderMock);
+        } catch (SecurityException e) {
+            failsAsExpected = true;
+        }
+
+        mDut.fakeUid = mDefaultUid;
+
+        PublishConfig publishConfig = new PublishConfig.Builder().setServiceName("valid.value")
+                .build();
+        mDut.publish(clientId, publishConfig, mSessionCallbackMock);
+
+        verify(mNanStateManagerMock).publish(clientId, publishConfig, mSessionCallbackMock);
+        assertTrue("SecurityException for invalid access from wrong UID thrown", failsAsExpected);
+    }
+
+    /**
+     * Validates that on binder death we get a disconnect().
+     */
+    @Test
+    public void testBinderDeath() throws Exception {
+        ArgumentCaptor<IBinder.DeathRecipient> deathRecipient = ArgumentCaptor
+                .forClass(IBinder.DeathRecipient.class);
+
+        int clientId = doConnect();
+
+        verify(mBinderMock).linkToDeath(deathRecipient.capture(), eq(0));
+        deathRecipient.getValue().binderDied();
+        verify(mNanStateManagerMock).disconnect(clientId);
+        validateInternalStateCleanedUp(clientId);
+    }
+
+    /**
+     * Validates that sequential connect() calls return increasing client IDs.
+     */
+    @Test
+    public void testClientIdIncrementing() {
+        int loopCount = 100;
+
+        int prevId = 0;
+        for (int i = 0; i < loopCount; ++i) {
+            int id = mDut.connect(mBinderMock, mCallbackMock, null);
+            if (i != 0) {
+                assertTrue("Client ID incrementing", id > prevId);
+            }
+            prevId = id;
+        }
+    }
+
+    /**
+     * Validate terminateSession() - correct pass-through args.
+     */
+    @Test
+    public void testTerminateSession() {
+        int sessionId = 1024;
+        int clientId = doConnect();
+
+        mDut.terminateSession(clientId, sessionId);
+
+        verify(mNanStateManagerMock).terminateSession(clientId, sessionId);
+    }
+
+    /**
+     * Validate publish() - correct pass-through args.
+     */
+    @Test
+    public void testPublish() {
+        PublishConfig publishConfig = new PublishConfig.Builder().setServiceName("something.valid")
+                .build();
+        int clientId = doConnect();
+        IWifiNanSessionCallback mockCallback = mock(IWifiNanSessionCallback.class);
+
+        mDut.publish(clientId, publishConfig, mockCallback);
+
+        verify(mNanStateManagerMock).publish(clientId, publishConfig, mockCallback);
+    }
+
+    /**
+     * Validate that publish() verifies the input PublishConfig and fails on an invalid service
+     * name.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testPublishBadServiceName() {
+        PublishConfig publishConfig = new PublishConfig.Builder().setServiceName(
+                "Including invalid characters - spaces").build();
+        int clientId = doConnect();
+        IWifiNanSessionCallback mockCallback = mock(IWifiNanSessionCallback.class);
+
+        mDut.publish(clientId, publishConfig, mockCallback);
+
+        verify(mNanStateManagerMock).publish(clientId, publishConfig, mockCallback);
+    }
+
+    /**
+     * Validate updatePublish() - correct pass-through args.
+     */
+    @Test
+    public void testUpdatePublish() {
+        int sessionId = 1232;
+        PublishConfig publishConfig = new PublishConfig.Builder().setServiceName("something.valid")
+                .build();
+        int clientId = doConnect();
+
+        mDut.updatePublish(clientId, sessionId, publishConfig);
+
+        verify(mNanStateManagerMock).updatePublish(clientId, sessionId, publishConfig);
+    }
+
+    /**
+     * Validate subscribe() - correct pass-through args.
+     */
+    @Test
+    public void testSubscribe() {
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder()
+                .setServiceName("something.valid").build();
+        int clientId = doConnect();
+        IWifiNanSessionCallback mockCallback = mock(IWifiNanSessionCallback.class);
+
+        mDut.subscribe(clientId, subscribeConfig, mockCallback);
+
+        verify(mNanStateManagerMock).subscribe(clientId, subscribeConfig, mockCallback);
+    }
+
+    /**
+     * Validate that subscribe() verifies the input SubscribeConfig and fails on an invalid service
+     * name.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testSubscribeBadServiceName() {
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().setServiceName(
+                "InvalidServiceCharacters__").build();
+        int clientId = doConnect();
+        IWifiNanSessionCallback mockCallback = mock(IWifiNanSessionCallback.class);
+
+        mDut.subscribe(clientId, subscribeConfig, mockCallback);
+
+        verify(mNanStateManagerMock).subscribe(clientId, subscribeConfig, mockCallback);
+    }
+
+    /**
+     * Validate updateSubscribe() - correct pass-through args.
+     */
+    @Test
+    public void testUpdateSubscribe() {
+        int sessionId = 1232;
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder()
+                .setServiceName("something.valid").build();
+        int clientId = doConnect();
+
+        mDut.updateSubscribe(clientId, sessionId, subscribeConfig);
+
+        verify(mNanStateManagerMock).updateSubscribe(clientId, sessionId, subscribeConfig);
+    }
+
+    /**
+     * Validate sendMessage() - correct pass-through args.
+     */
+    @Test
+    public void testSendMessage() {
+        int sessionId = 2394;
+        int peerId = 2032;
+        byte[] message = new byte[23];
+        int messageId = 2043;
+        int clientId = doConnect();
+
+        mDut.sendMessage(clientId, sessionId, peerId, message, message.length, messageId, 0);
+
+        verify(mNanStateManagerMock).sendMessage(clientId, sessionId, peerId, message,
+                message.length, messageId, 0);
+    }
+
+    /**
+     * Validate startRanging() - correct pass-through args
+     */
+    @Test
+    public void testStartRanging() {
+        int clientId = doConnect();
+        int sessionId = 65345;
+        RttManager.ParcelableRttParams params =
+                new RttManager.ParcelableRttParams(new RttManager.RttParams[1]);
+
+        ArgumentCaptor<RttManager.RttParams[]> paramsCaptor =
+                ArgumentCaptor.forClass(RttManager.RttParams[].class);
+
+        int rangingId = mDut.startRanging(clientId, sessionId, params);
+
+        verify(mNanStateManagerMock).startRanging(eq(clientId), eq(sessionId),
+                paramsCaptor.capture(), eq(rangingId));
+
+        assertArrayEquals(paramsCaptor.getValue(), params.mParams);
+    }
+
+    /**
+     * Validates that sequential startRanging() calls return increasing ranging IDs.
+     */
+    @Test
+    public void testRangingIdIncrementing() {
+        int loopCount = 100;
+        int clientId = doConnect();
+        int sessionId = 65345;
+        RttManager.ParcelableRttParams params =
+                new RttManager.ParcelableRttParams(new RttManager.RttParams[1]);
+
+        int prevRangingId = 0;
+        for (int i = 0; i < loopCount; ++i) {
+            int rangingId = mDut.startRanging(clientId, sessionId, params);
+            if (i != 0) {
+                assertTrue("Client ID incrementing", rangingId > prevRangingId);
+            }
+            prevRangingId = rangingId;
+        }
+    }
+
+    /**
+     * Validates that startRanging() requires a non-empty list
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testStartRangingZeroArgs() {
+        int clientId = doConnect();
+        int sessionId = 65345;
+        RttManager.ParcelableRttParams params =
+                new RttManager.ParcelableRttParams(new RttManager.RttParams[0]);
+
+        ArgumentCaptor<RttManager.RttParams[]> paramsCaptor =
+                ArgumentCaptor.forClass(RttManager.RttParams[].class);
+
+        int rangingId = mDut.startRanging(clientId, sessionId, params);
+    }
+
+    /*
+     * Tests of internal state of WifiNanServiceImpl: very limited (not usually
+     * a good idea). However, these test that the internal state is cleaned-up
+     * appropriately. Alternatively would cause issues with memory leaks or
+     * information leak between sessions.
+     */
+
+    private void validateInternalStateCleanedUp(int clientId) throws Exception {
+        int uidEntry = getInternalStateUid(clientId);
+        assertEquals(-1, uidEntry);
+
+        IBinder.DeathRecipient dr = getInternalStateDeathRecipient(clientId);
+        assertEquals(null, dr);
+    }
+
+    /*
+     * Utilities
+     */
+
+    private int doConnect() {
+        int returnedClientId = mDut.connect(mBinderMock, mCallbackMock, null);
+
+        ArgumentCaptor<Integer> clientId = ArgumentCaptor.forClass(Integer.class);
+        verify(mNanStateManagerMock).connect(clientId.capture(), anyInt(), eq(mCallbackMock),
+                eq(new ConfigRequest.Builder().build()));
+        assertEquals(returnedClientId, (int) clientId.getValue());
+
+        return returnedClientId;
+    }
+
+    private void installMockNanStateManager()
+            throws Exception {
+        Field field = WifiNanStateManager.class.getDeclaredField("sNanStateManagerSingleton");
+        field.setAccessible(true);
+        field.set(null, mNanStateManagerMock);
+    }
+
+    private int getInternalStateUid(int clientId) throws Exception {
+        Field field = WifiNanServiceImpl.class.getDeclaredField("mUidByClientId");
+        field.setAccessible(true);
+        @SuppressWarnings("unchecked")
+        SparseIntArray uidByClientId = (SparseIntArray) field.get(mDut);
+
+        return uidByClientId.get(clientId, -1);
+    }
+
+    private IBinder.DeathRecipient getInternalStateDeathRecipient(int clientId) throws Exception {
+        Field field = WifiNanServiceImpl.class.getDeclaredField("mDeathRecipientsByClientId");
+        field.setAccessible(true);
+        @SuppressWarnings("unchecked")
+        SparseArray<IBinder.DeathRecipient> deathRecipientsByClientId =
+                            (SparseArray<IBinder.DeathRecipient>) field.get(mDut);
+
+        return deathRecipientsByClientId.get(clientId);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/nan/WifiNanStateManagerTest.java b/tests/wifitests/src/com/android/server/wifi/nan/WifiNanStateManagerTest.java
index 49abd27..f21ee62 100644
--- a/tests/wifitests/src/com/android/server/wifi/nan/WifiNanStateManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/nan/WifiNanStateManagerTest.java
@@ -17,28 +17,42 @@
 package com.android.server.wifi.nan;
 
 import static org.hamcrest.core.IsEqual.equalTo;
+import static org.hamcrest.core.IsNull.notNullValue;
 import static org.hamcrest.core.IsNull.nullValue;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyShort;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
 
+import android.app.test.MockAnswerUtil;
+import android.app.test.TestAlarmManager;
+import android.content.Context;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.wifi.RttManager;
 import android.net.wifi.nan.ConfigRequest;
-import android.net.wifi.nan.IWifiNanEventListener;
-import android.net.wifi.nan.IWifiNanSessionListener;
-import android.net.wifi.nan.PublishData;
-import android.net.wifi.nan.PublishSettings;
-import android.net.wifi.nan.SubscribeData;
-import android.net.wifi.nan.SubscribeSettings;
-import android.net.wifi.nan.WifiNanEventListener;
-import android.net.wifi.nan.WifiNanSessionListener;
+import android.net.wifi.nan.IWifiNanEventCallback;
+import android.net.wifi.nan.IWifiNanSessionCallback;
+import android.net.wifi.nan.PublishConfig;
+import android.net.wifi.nan.SubscribeConfig;
+import android.net.wifi.nan.WifiNanEventCallback;
+import android.net.wifi.nan.WifiNanManager;
+import android.net.wifi.nan.WifiNanSessionCallback;
+import android.os.Message;
+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.MockLooper;
+import android.util.SparseIntArray;
 
 import libcore.util.HexEncoding;
 
@@ -53,436 +67,825 @@
 
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Random;
 
 /**
  * Unit test harness for WifiNanStateManager.
  */
 @SmallTest
 public class WifiNanStateManagerTest {
-    private MockLooper mMockLooper;
+    private TestLooper mMockLooper;
+    private Random mRandomNg = new Random(15687);
     private WifiNanStateManager mDut;
     @Mock private WifiNanNative mMockNative;
+    @Mock private Context mMockContext;
+    @Mock private WifiNanRttStateManager mMockNanRttStateManager;
+    TestAlarmManager mAlarmManager;
+    @Mock private WifiNanDataPathStateManager mMockNanDataPathStatemanager;
 
     @Rule
     public ErrorCollector collector = new ErrorCollector();
 
+    /**
+     * Pre-test configuration. Initialize and install mocks.
+     */
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
-        mMockLooper = new MockLooper();
+        mAlarmManager = new TestAlarmManager();
+        when(mMockContext.getSystemService(Context.ALARM_SERVICE))
+                .thenReturn(mAlarmManager.getAlarmManager());
 
-        mDut = installNewNanStateManagerAndResetState();
-        mDut.start(mMockLooper.getLooper());
+        when(mMockContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(
+                mock(ConnectivityManager.class));
+
+        mMockLooper = new TestLooper();
+
+        mDut = installNewNanStateManager();
+        mDut.start(mMockContext, mMockLooper.getLooper());
+        installMocksInStateManager(mDut, mMockNanRttStateManager, mMockNanDataPathStatemanager);
+
+        when(mMockNative.enableAndConfigure(anyShort(), any(ConfigRequest.class), anyBoolean()))
+                .thenReturn(true);
+        when(mMockNative.disable(anyShort())).thenReturn(true);
+        when(mMockNative.publish(anyShort(), anyInt(), any(PublishConfig.class))).thenReturn(true);
+        when(mMockNative.subscribe(anyShort(), anyInt(), any(SubscribeConfig.class)))
+                .thenReturn(true);
+        when(mMockNative.sendMessage(anyShort(), anyInt(), anyInt(), any(byte[].class),
+                any(byte[].class), anyInt(), anyInt())).thenReturn(true);
+        when(mMockNative.stopPublish(anyShort(), anyInt())).thenReturn(true);
+        when(mMockNative.stopSubscribe(anyShort(), anyInt())).thenReturn(true);
+        when(mMockNative.getCapabilities(anyShort())).thenReturn(true);
 
         installMockWifiNanNative(mMockNative);
     }
 
+    /**
+     * Validate that NAN data-path interfaces are brought up and down correctly.
+     */
     @Test
-    public void testNanEventsDelivered() throws Exception {
-        final int uid = 1005;
-        final int clusterLow1 = 5;
-        final int clusterHigh1 = 100;
-        final int masterPref1 = 111;
-        final int clusterLow2 = 7;
-        final int clusterHigh2 = 155;
-        final int masterPref2 = 0;
-        final int reason = WifiNanSessionListener.FAIL_REASON_NO_RESOURCES;
+    public void testNanDataPathInterfaceUpDown() throws Exception {
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        InOrder inOrder = inOrder(mMockContext, mMockNative, mMockNanDataPathStatemanager);
+
+        // (1) enable usage
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        validateCorrectNanStatusChangeBroadcast(inOrder, true);
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNanDataPathStatemanager).createAllInterfaces();
+        collector.checkThat("usage enabled", mDut.isUsageEnabled(), equalTo(true));
+
+        // (2) disable usage
+        mDut.disableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).disable((short) 0);
+        inOrder.verify(mMockNative).deInitNan();
+        inOrder.verify(mMockNanDataPathStatemanager).onNanDownCleanupDataPaths();
+        validateCorrectNanStatusChangeBroadcast(inOrder, false);
+        inOrder.verify(mMockNanDataPathStatemanager).deleteAllInterfaces();
+        collector.checkThat("usage disabled", mDut.isUsageEnabled(), equalTo(false));
+
+        verifyNoMoreInteractions(mMockNative, mMockNanDataPathStatemanager);
+    }
+
+    /**
+     * Validate that APIs aren't functional when usage is disabled.
+     */
+    @Test
+    public void testDisableUsageDisablesApis() throws Exception {
+        final int clientId = 12314;
+        final int uid = 1000;
+        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+
+        IWifiNanEventCallback mockCallback = mock(IWifiNanEventCallback.class);
+        InOrder inOrder = inOrder(mMockContext, mMockNative, mockCallback);
+
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+
+        // (1) check initial state
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        validateCorrectNanStatusChangeBroadcast(inOrder, true);
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
+        mMockLooper.dispatchAll();
+        collector.checkThat("usage enabled", mDut.isUsageEnabled(), equalTo(true));
+
+        // (2) disable usage and validate state
+        mDut.disableUsage();
+        mMockLooper.dispatchAll();
+        collector.checkThat("usage disabled", mDut.isUsageEnabled(), equalTo(false));
+        inOrder.verify(mMockNative).disable((short) 0);
+        inOrder.verify(mMockNative).deInitNan();
+        validateCorrectNanStatusChangeBroadcast(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)
+        mDut.connect(clientId, uid, mockCallback, configRequest);
+        mMockLooper.dispatchAll();
+
+        verifyNoMoreInteractions(mMockNative, mockCallback);
+    }
+
+    /**
+     * Validate that when API usage is disabled while in the middle of a connection that internal
+     * state is cleaned-up, and that all subsequent operations are NOP. Then enable usage again and
+     * validate that operates correctly.
+     */
+    @Test
+    public void testDisableUsageFlow() throws Exception {
+        final int clientId = 12341;
+        final int uid = 1000;
+        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+
+        IWifiNanEventCallback mockCallback = mock(IWifiNanEventCallback.class);
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        InOrder inOrder = inOrder(mMockContext, mMockNative, mockCallback);
+
+        // (1) check initial state
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        validateCorrectNanStatusChangeBroadcast(inOrder, true);
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
+        mMockLooper.dispatchAll();
+
+        collector.checkThat("usage enabled", mDut.isUsageEnabled(), equalTo(true));
+
+        // (2) connect (successfully)
+        mDut.connect(clientId, uid, mockCallback, configRequest);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
+
+        // (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).deInitNan();
+        validateCorrectNanStatusChangeBroadcast(inOrder, false);
+        validateInternalClientInfoCleanedUp(clientId);
+
+        // (4) try connecting again and validate that just get an onNanDown
+        mDut.connect(clientId, uid, mockCallback, configRequest);
+        mMockLooper.dispatchAll();
+
+        // (5) disable usage again and validate that not much happens
+        mDut.disableUsage();
+        mMockLooper.dispatchAll();
+        collector.checkThat("usage disabled", mDut.isUsageEnabled(), equalTo(false));
+
+        // (6) enable usage
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        collector.checkThat("usage enabled", mDut.isUsageEnabled(), equalTo(true));
+        validateCorrectNanStatusChangeBroadcast(inOrder, true);
+
+        // (7) connect (should be successful)
+        mDut.connect(clientId, uid, mockCallback, configRequest);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
+
+        verifyNoMoreInteractions(mMockNative, mockCallback);
+    }
+
+    /**
+     * Validates that all events are delivered with correct arguments. Validates
+     * that IdentityChanged not delivered if configuration disables delivery.
+     */
+    @Test
+    public void testNanEventsDelivery() throws Exception {
+        final int clientId1 = 1005;
+        final int clientId2 = 1007;
+        final int clusterLow = 5;
+        final int clusterHigh = 100;
+        final int masterPref = 111;
+        final int uid = 1000;
+        final int reason = WifiNanEventCallback.REASON_OTHER;
         final byte[] someMac = HexEncoding.decode("000102030405".toCharArray(), false);
 
-        ConfigRequest configRequest1 = new ConfigRequest.Builder().setClusterLow(clusterLow1)
-                .setClusterHigh(clusterHigh1).setMasterPreference(masterPref1).build();
+        ConfigRequest configRequest1 = new ConfigRequest.Builder().setClusterLow(clusterLow)
+                .setClusterHigh(clusterHigh).setMasterPreference(masterPref)
+                .setEnableIdentityChangeCallback(false).build();
 
-        ConfigRequest configRequest2 = new ConfigRequest.Builder().setClusterLow(clusterLow2)
-                .setClusterHigh(clusterHigh2).setMasterPreference(masterPref2).build();
+        ConfigRequest configRequest2 = new ConfigRequest.Builder().setClusterLow(clusterLow)
+                .setClusterHigh(clusterHigh).setMasterPreference(masterPref)
+                .setEnableIdentityChangeCallback(true).build();
 
-        IWifiNanEventListener mockListener = mock(IWifiNanEventListener.class);
-        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
-        InOrder inOrder = inOrder(mockListener, mMockNative);
+        IWifiNanEventCallback mockCallback1 = mock(IWifiNanEventCallback.class);
+        IWifiNanEventCallback mockCallback2 = mock(IWifiNanEventCallback.class);
+        ArgumentCaptor<Short> transactionIdCapture = ArgumentCaptor.forClass(Short.class);
+        InOrder inOrder = inOrder(mockCallback1, mockCallback2, mMockNative);
 
-        mDut.connect(uid, mockListener,
-                WifiNanEventListener.LISTEN_CONFIG_COMPLETED
-                        | WifiNanEventListener.LISTEN_CONFIG_FAILED
-                        | WifiNanEventListener.LISTEN_IDENTITY_CHANGED
-                        | WifiNanEventListener.LISTEN_NAN_DOWN);
-        mDut.requestConfig(uid, configRequest1);
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionIdCapture.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionIdCapture.getValue(), getCapabilities());
         mMockLooper.dispatchAll();
 
-        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest1));
-        short transactionId1 = transactionId.getValue();
+        // (1) connect 1st and 2nd clients
+        mDut.connect(clientId1, uid, mockCallback1, configRequest1);
+        mDut.connect(clientId2, uid, mockCallback2, configRequest2);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionIdCapture.capture(),
+                eq(configRequest1), eq(true));
+        short transactionId = transactionIdCapture.getValue();
+        mDut.onConfigSuccessResponse(transactionId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback1).onConnectSuccess();
 
-        mDut.requestConfig(uid, configRequest2);
+        // (2) finish connection of 2nd client
+        inOrder.verify(mMockNative).enableAndConfigure(transactionIdCapture.capture(),
+                eq(configRequest2), eq(false));
+        transactionId = transactionIdCapture.getValue();
+        mDut.onConfigSuccessResponse(transactionId);
+
+        // (3) deliver NAN events
+        mDut.onClusterChangeNotification(WifiNanClientState.CLUSTER_CHANGE_EVENT_STARTED, someMac);
+        mDut.onInterfaceAddressChangeNotification(someMac);
+        mDut.onNanDownNotification(reason);
         mMockLooper.dispatchAll();
 
-        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest2));
-        short transactionId2 = transactionId.getValue();
+        inOrder.verify(mockCallback2).onConnectSuccess();
+        inOrder.verify(mockCallback2).onIdentityChanged(someMac);
 
-        mDut.onClusterChange(WifiNanClientState.CLUSTER_CHANGE_EVENT_STARTED, someMac);
-        mDut.onConfigCompleted(transactionId1);
-        mDut.onConfigFailed(transactionId2, reason);
-        mDut.onInterfaceAddressChange(someMac);
-        mDut.onNanDown(reason);
-        mMockLooper.dispatchAll();
+        validateInternalClientInfoCleanedUp(clientId1);
+        validateInternalClientInfoCleanedUp(clientId2);
 
-        inOrder.verify(mockListener).onIdentityChanged();
-        inOrder.verify(mockListener).onConfigCompleted(configRequest1);
-        inOrder.verify(mockListener).onConfigFailed(configRequest2, reason);
-        inOrder.verify(mockListener).onIdentityChanged();
-        inOrder.verify(mockListener).onNanDown(reason);
-        verifyNoMoreInteractions(mockListener);
-
-        validateInternalTransactionInfoCleanedUp(transactionId1);
-        validateInternalTransactionInfoCleanedUp(transactionId2);
+        verifyNoMoreInteractions(mockCallback1, mockCallback2, mMockNative);
     }
 
+    /**
+     * Validate that when the HAL doesn't respond we get a TIMEOUT (which
+     * results in a failure response) at which point we can process additional
+     * commands. Steps: (1) connect, (2) publish - timeout, (3) publish +
+     * success.
+     */
     @Test
-    public void testNanEventsNotDelivered() throws Exception {
-        final int uid = 1005;
-        final int clusterLow1 = 5;
-        final int clusterHigh1 = 100;
-        final int masterPref1 = 111;
-        final int clusterLow2 = 7;
-        final int clusterHigh2 = 155;
-        final int masterPref2 = 0;
-        final int reason = WifiNanSessionListener.FAIL_REASON_NO_RESOURCES;
-        final byte[] someMac = HexEncoding.decode("000102030405".toCharArray(), false);
+    public void testHalNoResponseTimeout() throws Exception {
+        final int clientId = 12341;
+        final int uid = 1000;
+        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        final PublishConfig publishConfig = new PublishConfig.Builder().build();
 
-        ConfigRequest configRequest1 = new ConfigRequest.Builder().setClusterLow(clusterLow1)
-                .setClusterHigh(clusterHigh1).setMasterPreference(masterPref1).build();
-
-        ConfigRequest configRequest2 = new ConfigRequest.Builder().setClusterLow(clusterLow2)
-                .setClusterHigh(clusterHigh2).setMasterPreference(masterPref2).build();
-
-        IWifiNanEventListener mockListener = mock(IWifiNanEventListener.class);
+        IWifiNanEventCallback mockCallback = mock(IWifiNanEventCallback.class);
+        IWifiNanSessionCallback mockSessionCallback = mock(IWifiNanSessionCallback.class);
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
-        InOrder inOrder = inOrder(mockListener, mMockNative);
+        InOrder inOrder = inOrder(mMockNative, mockCallback, mockSessionCallback);
 
-        mDut.connect(uid, mockListener, 0);
-        mDut.requestConfig(uid, configRequest1);
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
         mMockLooper.dispatchAll();
 
-        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest1));
-        short transactionId1 = transactionId.getValue();
-
-        mDut.requestConfig(uid, configRequest2);
+        // (1) connect (successfully)
+        mDut.connect(clientId, uid, mockCallback, configRequest);
         mMockLooper.dispatchAll();
-
-        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest2));
-        short transactionId2 = transactionId.getValue();
-
-        mDut.onClusterChange(WifiNanClientState.CLUSTER_CHANGE_EVENT_JOINED, someMac);
-        mDut.onConfigCompleted(transactionId1);
-        mDut.onConfigFailed(transactionId2, reason);
-        mDut.onInterfaceAddressChange(someMac);
-        mDut.onNanDown(reason);
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
 
-        verifyZeroInteractions(mockListener);
+        // (2) publish + timeout
+        mDut.publish(clientId, publishConfig, mockSessionCallback);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).publish(anyShort(), eq(0), eq(publishConfig));
+        assertTrue(mAlarmManager.dispatch(WifiNanStateManager.HAL_COMMAND_TIMEOUT_TAG));
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback)
+                .onSessionConfigFail(WifiNanSessionCallback.REASON_OTHER);
+        validateInternalNoSessions(clientId);
 
-        validateInternalTransactionInfoCleanedUp(transactionId1);
-        validateInternalTransactionInfoCleanedUp(transactionId2);
+        // (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);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSessionStarted(anyInt());
+
+        verifyNoMoreInteractions(mMockNative, mockCallback, mockSessionCallback);
     }
 
+    /**
+     * Validates publish flow: (1) initial publish (2) fail. Expected: get a
+     * failure callback.
+     */
     @Test
-    public void testPublish() throws Exception {
-        final int uid = 1005;
-        final int sessionId = 20;
-        final String serviceName = "some-service-name";
-        final String ssi = "some much longer and more arbitrary data";
-        final int publishCount = 7;
-        final int reasonFail = WifiNanSessionListener.FAIL_REASON_NO_RESOURCES;
-        final int reasonTerminate = WifiNanSessionListener.TERMINATE_REASON_DONE;
-        final int publishId1 = 15;
-        final int publishId2 = 22;
+    public void testPublishFail() throws Exception {
+        final int clientId = 1005;
+        final int uid = 1000;
+        final int reasonFail = WifiNanSessionCallback.REASON_NO_RESOURCES;
 
-        PublishData publishData = new PublishData.Builder().setServiceName(serviceName)
-                .setServiceSpecificInfo(ssi).build();
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        PublishConfig publishConfig = new PublishConfig.Builder().build();
 
-        PublishSettings publishSettings = new PublishSettings.Builder()
-                .setPublishType(PublishSettings.PUBLISH_TYPE_UNSOLICITED)
-                .setPublishCount(publishCount).build();
-
-        IWifiNanSessionListener mockListener = mock(IWifiNanSessionListener.class);
+        IWifiNanEventCallback mockCallback = mock(IWifiNanEventCallback.class);
+        IWifiNanSessionCallback mockSessionCallback = mock(IWifiNanSessionCallback.class);
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
-        InOrder inOrder = inOrder(mockListener, mMockNative);
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mMockNative);
 
-        int allEvents = WifiNanSessionListener.LISTEN_PUBLISH_FAIL
-                | WifiNanSessionListener.LISTEN_PUBLISH_TERMINATED
-                | WifiNanSessionListener.LISTEN_SUBSCRIBE_FAIL
-                | WifiNanSessionListener.LISTEN_SUBSCRIBE_TERMINATED
-                | WifiNanSessionListener.LISTEN_MATCH
-                | WifiNanSessionListener.LISTEN_MESSAGE_SEND_SUCCESS
-                | WifiNanSessionListener.LISTEN_MESSAGE_SEND_FAIL
-                | WifiNanSessionListener.LISTEN_MESSAGE_RECEIVED;
-
-        mDut.connect(uid, null, 0);
-        mDut.createSession(uid, sessionId, mockListener, allEvents);
-
-        // publish - fail
-        mDut.publish(uid, sessionId, publishData, publishSettings);
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
         mMockLooper.dispatchAll();
 
-        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishData),
-                eq(publishSettings));
-
-        mDut.onPublishFail(transactionId.getValue(), reasonFail);
+        // (0) connect
+        mDut.connect(clientId, uid, mockCallback, configRequest);
         mMockLooper.dispatchAll();
-
-        inOrder.verify(mockListener).onPublishFail(reasonFail);
-        validateInternalTransactionInfoCleanedUp(transactionId.getValue());
-
-        // publish - success/terminate
-        mDut.publish(uid, sessionId, publishData, publishSettings);
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
+                eq(configRequest), eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
 
-        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishData),
-                eq(publishSettings));
-
-        mDut.onPublishSuccess(transactionId.getValue(), publishId1);
+        // (1) initial publish
+        mDut.publish(clientId, publishConfig, mockSessionCallback);
         mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishConfig));
 
-        mDut.onPublishTerminated(publishId1, reasonTerminate);
+        // (2) publish failure
+        mDut.onSessionConfigFailResponse(transactionId.getValue(), true, reasonFail);
         mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSessionConfigFail(reasonFail);
+        validateInternalNoSessions(clientId);
 
-        inOrder.verify(mockListener).onPublishTerminated(reasonTerminate);
-        validateInternalTransactionInfoCleanedUp(transactionId.getValue());
-
-        // re-publish
-        mDut.publish(uid, sessionId, publishData, publishSettings);
-        mMockLooper.dispatchAll();
-
-        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishData),
-                eq(publishSettings));
-
-        mDut.onPublishSuccess(transactionId.getValue(), publishId2);
-        mDut.publish(uid, sessionId, publishData, publishSettings);
-        mMockLooper.dispatchAll();
-
-        validateInternalTransactionInfoCleanedUp(transactionId.getValue());
-        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(publishId2),
-                eq(publishData), eq(publishSettings));
-        verifyNoMoreInteractions(mockListener, mMockNative);
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mMockNative);
     }
 
+    /**
+     * Validates the publish flow: (1) initial publish (2) success (3)
+     * termination (e.g. DONE) (4) update session attempt (5) terminateSession
+     * (6) update session attempt. Expected: session ID callback + session
+     * cleaned-up.
+     */
     @Test
-    public void testPublishNoCallbacks() throws Exception {
-        final int uid = 1005;
-        final int sessionId = 20;
-        final String serviceName = "some-service-name";
-        final String ssi = "some much longer and more arbitrary data";
-        final int publishCount = 7;
-        final int reasonFail = WifiNanSessionListener.FAIL_REASON_NO_RESOURCES;
-        final int reasonTerminate = WifiNanSessionListener.TERMINATE_REASON_DONE;
+    public void testPublishSuccessTerminated() throws Exception {
+        final int clientId = 2005;
+        final int uid = 1000;
+        final int reasonTerminate = WifiNanSessionCallback.TERMINATE_REASON_DONE;
         final int publishId = 15;
 
-        PublishData publishData = new PublishData.Builder().setServiceName(serviceName)
-                .setServiceSpecificInfo(ssi).build();
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        PublishConfig publishConfig = new PublishConfig.Builder().build();
 
-        PublishSettings publishSettings = new PublishSettings.Builder()
-                .setPublishType(PublishSettings.PUBLISH_TYPE_UNSOLICITED)
-                .setPublishCount(publishCount).build();
-
-        IWifiNanSessionListener mockListener = mock(IWifiNanSessionListener.class);
+        IWifiNanEventCallback mockCallback = mock(IWifiNanEventCallback.class);
+        IWifiNanSessionCallback mockSessionCallback = mock(IWifiNanSessionCallback.class);
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
-        InOrder inOrder = inOrder(mockListener, mMockNative);
+        ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mMockNative);
 
-        int allEvents = WifiNanSessionListener.LISTEN_PUBLISH_FAIL
-                | WifiNanSessionListener.LISTEN_PUBLISH_TERMINATED
-                | WifiNanSessionListener.LISTEN_SUBSCRIBE_FAIL
-                | WifiNanSessionListener.LISTEN_SUBSCRIBE_TERMINATED
-                | WifiNanSessionListener.LISTEN_MATCH
-                | WifiNanSessionListener.LISTEN_MESSAGE_SEND_SUCCESS
-                | WifiNanSessionListener.LISTEN_MESSAGE_SEND_FAIL
-                | WifiNanSessionListener.LISTEN_MESSAGE_RECEIVED;
-
-        mDut.connect(uid, null, 0);
-        mDut.createSession(uid, sessionId, mockListener,
-                allEvents & ~WifiNanSessionListener.LISTEN_PUBLISH_FAIL
-                        & ~WifiNanSessionListener.LISTEN_PUBLISH_TERMINATED);
-
-        // publish - fail
-        mDut.publish(uid, sessionId, publishData, publishSettings);
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
         mMockLooper.dispatchAll();
 
-        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishData),
-                eq(publishSettings));
+        // (0) connect
+        mDut.connect(clientId, uid, mockCallback, configRequest);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
+                eq(configRequest), eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
 
-        mDut.onPublishFail(transactionId.getValue(), reasonFail);
+        // (1) initial publish
+        mDut.publish(clientId, publishConfig, mockSessionCallback);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishConfig));
+
+        // (2) publish success
+        mDut.onSessionConfigSuccessResponse(transactionId.getValue(), true, publishId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
+
+        // (3) publish termination (from firmware - not app!)
+        mDut.onSessionTerminatedNotification(publishId, reasonTerminate, true);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSessionTerminated(reasonTerminate);
+
+        // (4) app update session (race condition: app didn't get termination
+        // yet)
+        mDut.updatePublish(clientId, sessionId.getValue(), publishConfig);
         mMockLooper.dispatchAll();
 
-        validateInternalTransactionInfoCleanedUp(transactionId.getValue());
-
-        // publish - success/terminate
-        mDut.publish(uid, sessionId, publishData, publishSettings);
+        // (5) app terminates session
+        mDut.terminateSession(clientId, sessionId.getValue());
         mMockLooper.dispatchAll();
 
-        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishData),
-                eq(publishSettings));
-
-        mDut.onPublishSuccess(transactionId.getValue(), publishId);
+        // (6) app updates session (app already knows that terminated - will get
+        // a local FAIL).
+        mDut.updatePublish(clientId, sessionId.getValue(), publishConfig);
         mMockLooper.dispatchAll();
 
-        mDut.onPublishTerminated(publishId, reasonTerminate);
-        mMockLooper.dispatchAll();
+        validateInternalSessionInfoCleanedUp(clientId, sessionId.getValue());
 
-        validateInternalTransactionInfoCleanedUp(transactionId.getValue());
-        verifyNoMoreInteractions(mockListener, mMockNative);
+        verifyNoMoreInteractions(mockSessionCallback, mMockNative);
     }
 
+    /**
+     * Validate the publish flow: (1) initial publish + (2) success + (3) update
+     * + (4) update fails + (5) update + (6). Expected: session is still alive
+     * after update failure so second update succeeds (no callbacks).
+     */
     @Test
-    public void testSubscribe() throws Exception {
-        final int uid = 1005;
-        final int sessionId = 20;
-        final String serviceName = "some-service-name";
-        final String ssi = "some much longer and more arbitrary data";
-        final int subscribeCount = 7;
-        final int reasonFail = WifiNanSessionListener.FAIL_REASON_NO_RESOURCES;
-        final int reasonTerminate = WifiNanSessionListener.TERMINATE_REASON_DONE;
-        final int subscribeId1 = 15;
-        final int subscribeId2 = 10;
+    public void testPublishUpdateFail() throws Exception {
+        final int clientId = 2005;
+        final int uid = 1000;
+        final int publishId = 15;
+        final int reasonFail = WifiNanSessionCallback.REASON_INVALID_ARGS;
 
-        SubscribeData subscribeData = new SubscribeData.Builder().setServiceName(serviceName)
-                .setServiceSpecificInfo(ssi).build();
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        PublishConfig publishConfig = new PublishConfig.Builder().build();
 
-        SubscribeSettings subscribeSettings = new SubscribeSettings.Builder()
-                .setSubscribeType(SubscribeSettings.SUBSCRIBE_TYPE_PASSIVE)
-                .setSubscribeCount(subscribeCount).build();
-
-        IWifiNanSessionListener mockListener = mock(IWifiNanSessionListener.class);
+        IWifiNanEventCallback mockCallback = mock(IWifiNanEventCallback.class);
+        IWifiNanSessionCallback mockSessionCallback = mock(IWifiNanSessionCallback.class);
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
-        InOrder inOrder = inOrder(mockListener, mMockNative);
+        ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mMockNative);
 
-        int allEvents = WifiNanSessionListener.LISTEN_PUBLISH_FAIL
-                | WifiNanSessionListener.LISTEN_PUBLISH_TERMINATED
-                | WifiNanSessionListener.LISTEN_SUBSCRIBE_FAIL
-                | WifiNanSessionListener.LISTEN_SUBSCRIBE_TERMINATED
-                | WifiNanSessionListener.LISTEN_MATCH
-                | WifiNanSessionListener.LISTEN_MESSAGE_SEND_SUCCESS
-                | WifiNanSessionListener.LISTEN_MESSAGE_SEND_FAIL
-                | WifiNanSessionListener.LISTEN_MESSAGE_RECEIVED;
-
-        mDut.connect(uid, null, 0);
-        mDut.createSession(uid, sessionId, mockListener, allEvents);
-
-        // subscribe - fail
-        mDut.subscribe(uid, sessionId, subscribeData, subscribeSettings);
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
         mMockLooper.dispatchAll();
 
-        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeData),
-                eq(subscribeSettings));
-
-        mDut.onSubscribeFail(transactionId.getValue(), reasonFail);
+        // (0) connect
+        mDut.connect(clientId, uid, mockCallback, configRequest);
         mMockLooper.dispatchAll();
-
-        validateInternalTransactionInfoCleanedUp(transactionId.getValue());
-        inOrder.verify(mockListener).onSubscribeFail(reasonFail);
-
-        // subscribe - success/terminate
-        mDut.subscribe(uid, sessionId, subscribeData, subscribeSettings);
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
 
-        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeData),
-                eq(subscribeSettings));
-
-        mDut.onSubscribeSuccess(transactionId.getValue(), subscribeId1);
+        // (1) initial publish
+        mDut.publish(clientId, publishConfig, mockSessionCallback);
         mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishConfig));
 
-        mDut.onSubscribeTerminated(subscribeId1, reasonTerminate);
+        // (2) publish success
+        mDut.onSessionConfigSuccessResponse(transactionId.getValue(), true, publishId);
         mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
 
-        validateInternalTransactionInfoCleanedUp(transactionId.getValue());
-        inOrder.verify(mockListener).onSubscribeTerminated(reasonTerminate);
-
-        // re-subscribe
-        mDut.subscribe(uid, sessionId, subscribeData, subscribeSettings);
+        // (3) update publish
+        mDut.updatePublish(clientId, sessionId.getValue(), publishConfig);
         mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(publishId),
+                eq(publishConfig));
 
-        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeData),
-                eq(subscribeSettings));
-
-        mDut.onSubscribeSuccess(transactionId.getValue(), subscribeId2);
-        mDut.subscribe(uid, sessionId, subscribeData, subscribeSettings);
+        // (4) update fails
+        mDut.onSessionConfigFailResponse(transactionId.getValue(), true, reasonFail);
         mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSessionConfigFail(reasonFail);
 
-        validateInternalTransactionInfoCleanedUp(transactionId.getValue());
-        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(subscribeId2),
-                eq(subscribeData), eq(subscribeSettings));
-        verifyNoMoreInteractions(mockListener, mMockNative);
+        // (5) another update publish
+        mDut.updatePublish(clientId, sessionId.getValue(), publishConfig);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(publishId),
+                eq(publishConfig));
+
+        // (6) update succeeds
+        mDut.onSessionConfigSuccessResponse(transactionId.getValue(), true, publishId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSessionConfigSuccess();
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mMockNative);
     }
 
+    /**
+     * Validate race condition: publish pending but session terminated (due to
+     * disconnect - can't terminate such a session directly from app). Need to
+     * make sure that once publish succeeds (failure isn't a problem) the
+     * session is immediately terminated since no-one is listening for it.
+     */
     @Test
-    public void testSubscribeNoCallbacks() throws Exception {
-        final int uid = 1005;
-        final int sessionId = 20;
-        final String serviceName = "some-service-name";
-        final String ssi = "some much longer and more arbitrary data";
-        final int subscribeCount = 7;
-        final int reasonFail = WifiNanSessionListener.FAIL_REASON_NO_RESOURCES;
-        final int reasonTerminate = WifiNanSessionListener.TERMINATE_REASON_DONE;
+    public void testDisconnectWhilePublishPending() throws Exception {
+        final int clientId = 2005;
+        final int uid = 1000;
+        final int publishId = 15;
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        PublishConfig publishConfig = new PublishConfig.Builder().build();
+
+        IWifiNanEventCallback mockCallback = mock(IWifiNanEventCallback.class);
+        IWifiNanSessionCallback mockSessionCallback = mock(IWifiNanSessionCallback.class);
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mMockNative);
+
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
+        mMockLooper.dispatchAll();
+
+        // (0) connect
+        mDut.connect(clientId, uid, mockCallback, configRequest);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
+
+        // (1) initial publish
+        mDut.publish(clientId, publishConfig, mockSessionCallback);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishConfig));
+
+        // (2) disconnect (but doesn't get executed until get response for
+        // publish command)
+        mDut.disconnect(clientId);
+        mMockLooper.dispatchAll();
+
+        // (3) publish success
+        mDut.onSessionConfigSuccessResponse(transactionId.getValue(), true, publishId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSessionStarted(anyInt());
+        inOrder.verify(mMockNative).stopPublish(transactionId.capture(), eq(publishId));
+        inOrder.verify(mMockNative).disable((short) 0);
+
+        validateInternalClientInfoCleanedUp(clientId);
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mMockNative);
+    }
+
+    /**
+     * Validates subscribe flow: (1) initial subscribe (2) fail. Expected: get a
+     * failure callback.
+     */
+    @Test
+    public void testSubscribeFail() throws Exception {
+        final int clientId = 1005;
+        final int uid = 1000;
+        final int reasonFail = WifiNanSessionCallback.REASON_NO_RESOURCES;
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
+
+        IWifiNanEventCallback mockCallback = mock(IWifiNanEventCallback.class);
+        IWifiNanSessionCallback mockSessionCallback = mock(IWifiNanSessionCallback.class);
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mMockNative);
+
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
+        mMockLooper.dispatchAll();
+
+        // (0) connect
+        mDut.connect(clientId, uid, mockCallback, configRequest);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
+
+        // (1) initial subscribe
+        mDut.subscribe(clientId, subscribeConfig, mockSessionCallback);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeConfig));
+
+        // (2) subscribe failure
+        mDut.onSessionConfigFailResponse(transactionId.getValue(), false, reasonFail);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSessionConfigFail(reasonFail);
+        validateInternalNoSessions(clientId);
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mMockNative);
+    }
+
+    /**
+     * Validates the subscribe flow: (1) initial subscribe (2) success (3)
+     * termination (e.g. DONE) (4) update session attempt (5) terminateSession
+     * (6) update session attempt. Expected: session ID callback + session
+     * cleaned-up
+     */
+    @Test
+    public void testSubscribeSuccessTerminated() throws Exception {
+        final int clientId = 2005;
+        final int uid = 1000;
+        final int reasonTerminate = WifiNanSessionCallback.TERMINATE_REASON_DONE;
         final int subscribeId = 15;
 
-        SubscribeData subscribeData = new SubscribeData.Builder().setServiceName(serviceName)
-                .setServiceSpecificInfo(ssi).build();
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
 
-        SubscribeSettings subscribeSettings = new SubscribeSettings.Builder()
-                .setSubscribeType(SubscribeSettings.SUBSCRIBE_TYPE_PASSIVE)
-                .setSubscribeCount(subscribeCount).build();
-
-        IWifiNanSessionListener mockListener = mock(IWifiNanSessionListener.class);
+        IWifiNanEventCallback mockCallback = mock(IWifiNanEventCallback.class);
+        IWifiNanSessionCallback mockSessionCallback = mock(IWifiNanSessionCallback.class);
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
-        InOrder inOrder = inOrder(mockListener, mMockNative);
+        ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mMockNative);
 
-        int allEvents = WifiNanSessionListener.LISTEN_PUBLISH_FAIL
-                | WifiNanSessionListener.LISTEN_PUBLISH_TERMINATED
-                | WifiNanSessionListener.LISTEN_SUBSCRIBE_FAIL
-                | WifiNanSessionListener.LISTEN_SUBSCRIBE_TERMINATED
-                | WifiNanSessionListener.LISTEN_MATCH
-                | WifiNanSessionListener.LISTEN_MESSAGE_SEND_SUCCESS
-                | WifiNanSessionListener.LISTEN_MESSAGE_SEND_FAIL
-                | WifiNanSessionListener.LISTEN_MESSAGE_RECEIVED;
-
-        mDut.connect(uid, null, 0);
-        mDut.createSession(uid, sessionId, mockListener,
-                allEvents & ~WifiNanSessionListener.LISTEN_SUBSCRIBE_FAIL
-                        & ~WifiNanSessionListener.LISTEN_SUBSCRIBE_TERMINATED);
-
-        // subscribe - fail
-        mDut.subscribe(uid, sessionId, subscribeData, subscribeSettings);
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
         mMockLooper.dispatchAll();
 
-        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeData),
-                eq(subscribeSettings));
+        // (0) connect
+        mDut.connect(clientId, uid, mockCallback, configRequest);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
 
-        mDut.onSubscribeFail(transactionId.getValue(), reasonFail);
+        // (1) initial subscribe
+        mDut.subscribe(clientId, subscribeConfig, mockSessionCallback);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeConfig));
+
+        // (2) subscribe success
+        mDut.onSessionConfigSuccessResponse(transactionId.getValue(), false, subscribeId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
+
+        // (3) subscribe termination (from firmware - not app!)
+        mDut.onSessionTerminatedNotification(subscribeId, reasonTerminate, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSessionTerminated(reasonTerminate);
+
+        // (4) app update session (race condition: app didn't get termination
+        // yet)
+        mDut.updateSubscribe(clientId, sessionId.getValue(), subscribeConfig);
         mMockLooper.dispatchAll();
 
-        validateInternalTransactionInfoCleanedUp(transactionId.getValue());
-
-        // subscribe - success/terminate
-        mDut.subscribe(uid, sessionId, subscribeData, subscribeSettings);
+        // (5) app terminates session
+        mDut.terminateSession(clientId, sessionId.getValue());
         mMockLooper.dispatchAll();
 
-        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeData),
-                eq(subscribeSettings));
-
-        mDut.onSubscribeSuccess(transactionId.getValue(), subscribeId);
+        // (6) app updates session
+        mDut.updateSubscribe(clientId, sessionId.getValue(), subscribeConfig);
         mMockLooper.dispatchAll();
 
-        mDut.onSubscribeTerminated(subscribeId, reasonTerminate);
-        mMockLooper.dispatchAll();
+        validateInternalSessionInfoCleanedUp(clientId, sessionId.getValue());
 
-        validateInternalTransactionInfoCleanedUp(transactionId.getValue());
-        verifyNoMoreInteractions(mockListener, mMockNative);
+        verifyNoMoreInteractions(mockSessionCallback, mMockNative);
     }
 
+    /**
+     * Validate the subscribe flow: (1) initial subscribe + (2) success + (3)
+     * update + (4) update fails + (5) update + (6). Expected: session is still
+     * alive after update failure so second update succeeds (no callbacks).
+     */
+    @Test
+    public void testSubscribeUpdateFail() throws Exception {
+        final int clientId = 2005;
+        final int uid = 1000;
+        final int subscribeId = 15;
+        final int reasonFail = WifiNanSessionCallback.REASON_INVALID_ARGS;
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
+
+        IWifiNanEventCallback mockCallback = mock(IWifiNanEventCallback.class);
+        IWifiNanSessionCallback mockSessionCallback = mock(IWifiNanSessionCallback.class);
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mMockNative);
+
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
+        mMockLooper.dispatchAll();
+
+        // (0) connect
+        mDut.connect(clientId, uid, mockCallback, configRequest);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
+
+        // (1) initial subscribe
+        mDut.subscribe(clientId, subscribeConfig, mockSessionCallback);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeConfig));
+
+        // (2) subscribe success
+        mDut.onSessionConfigSuccessResponse(transactionId.getValue(), false, subscribeId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
+
+        // (3) update subscribe
+        mDut.updateSubscribe(clientId, sessionId.getValue(), subscribeConfig);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(subscribeId),
+                eq(subscribeConfig));
+
+        // (4) update fails
+        mDut.onSessionConfigFailResponse(transactionId.getValue(), false, reasonFail);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSessionConfigFail(reasonFail);
+
+        // (5) another update subscribe
+        mDut.updateSubscribe(clientId, sessionId.getValue(), subscribeConfig);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(subscribeId),
+                eq(subscribeConfig));
+
+        // (6) update succeeds
+        mDut.onSessionConfigSuccessResponse(transactionId.getValue(), false, subscribeId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSessionConfigSuccess();
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mMockNative);
+    }
+
+    /**
+     * Validate race condition: subscribe pending but session terminated (due to
+     * disconnect - can't terminate such a session directly from app). Need to
+     * make sure that once subscribe succeeds (failure isn't a problem) the
+     * session is immediately terminated since no-one is listening for it.
+     */
+    @Test
+    public void testDisconnectWhileSubscribePending() throws Exception {
+        final int clientId = 2005;
+        final int uid = 1000;
+        final int subscribeId = 15;
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
+
+        IWifiNanEventCallback mockCallback = mock(IWifiNanEventCallback.class);
+        IWifiNanSessionCallback mockSessionCallback = mock(IWifiNanSessionCallback.class);
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mMockNative);
+
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
+        mMockLooper.dispatchAll();
+
+        // (0) connect
+        mDut.connect(clientId, uid, mockCallback, configRequest);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
+
+        // (1) initial subscribe
+        mDut.subscribe(clientId, subscribeConfig, mockSessionCallback);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeConfig));
+
+        // (2) disconnect (but doesn't get executed until get response for
+        // subscribe command)
+        mDut.disconnect(clientId);
+        mMockLooper.dispatchAll();
+
+        // (3) subscribe success
+        mDut.onSessionConfigSuccessResponse(transactionId.getValue(), false, subscribeId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSessionStarted(anyInt());
+        inOrder.verify(mMockNative).stopSubscribe((short) 0, subscribeId);
+        inOrder.verify(mMockNative).disable((short) 0);
+
+        validateInternalClientInfoCleanedUp(clientId);
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mMockNative);
+    }
+
+    /**
+     * Validate (1) subscribe (success), (2) match (i.e. discovery), (3) message reception,
+     * (4) message transmission failed (after ok queuing), (5) message transmission success.
+     */
     @Test
     public void testMatchAndMessages() throws Exception {
-        final int uid = 1005;
-        final int sessionId = 20;
+        final int clientId = 1005;
+        final int uid = 1000;
         final String serviceName = "some-service-name";
         final String ssi = "some much longer and more arbitrary data";
         final int subscribeCount = 7;
-        final int reasonFail = WifiNanSessionListener.FAIL_REASON_NO_RESOURCES;
+        final int reasonFail = WifiNanSessionCallback.REASON_TX_FAIL;
         final int subscribeId = 15;
         final int requestorId = 22;
         final byte[] peerMac = HexEncoding.decode("060708090A0B".toCharArray(), false);
@@ -490,73 +893,87 @@
         final String peerMatchFilter = "filter binary array represented as string";
         final String peerMsg = "some message from peer";
         final int messageId = 6948;
+        final int messageId2 = 6949;
 
-        SubscribeData subscribeData = new SubscribeData.Builder().setServiceName(serviceName)
-                .setServiceSpecificInfo(ssi).build();
-
-        SubscribeSettings subscribeSettings = new SubscribeSettings.Builder()
-                .setSubscribeType(SubscribeSettings.SUBSCRIBE_TYPE_PASSIVE)
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().setServiceName(serviceName)
+                .setServiceSpecificInfo(ssi)
+                .setSubscribeType(SubscribeConfig.SUBSCRIBE_TYPE_PASSIVE)
                 .setSubscribeCount(subscribeCount).build();
 
-        IWifiNanSessionListener mockListener = mock(IWifiNanSessionListener.class);
+        IWifiNanEventCallback mockCallback = mock(IWifiNanEventCallback.class);
+        IWifiNanSessionCallback mockSessionCallback = mock(IWifiNanSessionCallback.class);
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
-        InOrder inOrder = inOrder(mockListener, mMockNative);
+        ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mMockNative);
 
-        int allEvents = WifiNanSessionListener.LISTEN_PUBLISH_FAIL
-                | WifiNanSessionListener.LISTEN_PUBLISH_TERMINATED
-                | WifiNanSessionListener.LISTEN_SUBSCRIBE_FAIL
-                | WifiNanSessionListener.LISTEN_SUBSCRIBE_TERMINATED
-                | WifiNanSessionListener.LISTEN_MATCH
-                | WifiNanSessionListener.LISTEN_MESSAGE_SEND_SUCCESS
-                | WifiNanSessionListener.LISTEN_MESSAGE_SEND_FAIL
-                | WifiNanSessionListener.LISTEN_MESSAGE_RECEIVED;
-
-        mDut.connect(uid, null, 0);
-        mDut.createSession(uid, sessionId, mockListener, allEvents);
-        mDut.subscribe(uid, sessionId, subscribeData, subscribeSettings);
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
         mMockLooper.dispatchAll();
 
-        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeData),
-                eq(subscribeSettings));
+        // (0) connect
+        mDut.connect(clientId, uid, mockCallback, configRequest);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
+                eq(configRequest), eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
 
-        mDut.onSubscribeSuccess(transactionId.getValue(), subscribeId);
-        mDut.onMatch(subscribeId, requestorId, peerMac, peerSsi.getBytes(), peerSsi.length(),
-                peerMatchFilter.getBytes(), peerMatchFilter.length());
-        mDut.onMessageReceived(subscribeId, requestorId, peerMac, peerMsg.getBytes(),
+        // (1) subscribe
+        mDut.subscribe(clientId, subscribeConfig, mockSessionCallback);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeConfig));
+        mDut.onSessionConfigSuccessResponse(transactionId.getValue(), false, subscribeId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
+
+        // (2) match
+        mDut.onMatchNotification(subscribeId, requestorId, peerMac, peerSsi.getBytes(),
+                peerSsi.length(), peerMatchFilter.getBytes(), peerMatchFilter.length());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onMatch(requestorId, peerSsi.getBytes(),
+                peerSsi.length(), peerMatchFilter.getBytes(), peerMatchFilter.length());
+
+        // (3) message Rx
+        mDut.onMessageReceivedNotification(subscribeId, requestorId, peerMac, peerMsg.getBytes(),
                 peerMsg.length());
         mMockLooper.dispatchAll();
-
-        validateInternalTransactionInfoCleanedUp(transactionId.getValue());
-        inOrder.verify(mockListener).onMatch(requestorId, peerSsi.getBytes(), peerSsi.length(),
-                peerMatchFilter.getBytes(), peerMatchFilter.length());
-        inOrder.verify(mockListener).onMessageReceived(requestorId, peerMsg.getBytes(),
+        inOrder.verify(mockSessionCallback).onMessageReceived(requestorId, peerMsg.getBytes(),
                 peerMsg.length());
 
-        mDut.sendMessage(uid, sessionId, requestorId, ssi.getBytes(), ssi.length(), messageId);
+        // (4) message Tx successful queuing
+        mDut.sendMessage(clientId, sessionId.getValue(), requestorId, ssi.getBytes(), ssi.length(),
+                messageId, 0);
         mMockLooper.dispatchAll();
-
         inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(subscribeId),
-                eq(requestorId), eq(peerMac), eq(ssi.getBytes()), eq(ssi.length()));
-
-        mDut.onMessageSendFail(transactionId.getValue(), reasonFail);
+                eq(requestorId), eq(peerMac), eq(ssi.getBytes()), eq(ssi.length()), eq(messageId));
+        short tid1 = transactionId.getValue();
+        mDut.onMessageSendQueuedSuccessResponse(tid1);
         mMockLooper.dispatchAll();
 
-        validateInternalTransactionInfoCleanedUp(transactionId.getValue());
-        inOrder.verify(mockListener).onMessageSendFail(messageId, reasonFail);
-
-        mDut.sendMessage(uid, sessionId, requestorId, ssi.getBytes(), ssi.length(), messageId);
+        // (5) message Tx successful queuing
+        mDut.sendMessage(clientId, sessionId.getValue(), requestorId, ssi.getBytes(), ssi.length(),
+                messageId2, 0);
         mMockLooper.dispatchAll();
-
         inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(subscribeId),
-                eq(requestorId), eq(peerMac), eq(ssi.getBytes()), eq(ssi.length()));
-
-        mDut.onMessageSendSuccess(transactionId.getValue());
+                eq(requestorId), eq(peerMac), eq(ssi.getBytes()), eq(ssi.length()), eq(messageId2));
+        short tid2 = transactionId.getValue();
+        mDut.onMessageSendQueuedSuccessResponse(tid2);
         mMockLooper.dispatchAll();
 
-        validateInternalTransactionInfoCleanedUp(transactionId.getValue());
-        inOrder.verify(mockListener).onMessageSendSuccess(messageId);
+        // (4) and (5) final Tx results (on-air results)
+        mDut.onMessageSendFailNotification(tid1, reasonFail);
+        mDut.onMessageSendSuccessNotification(tid2);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onMessageSendFail(messageId, reasonFail);
+        inOrder.verify(mockSessionCallback).onMessageSendSuccess(messageId2);
+        validateInternalSendMessageQueuesCleanedUp(messageId);
+        validateInternalSendMessageQueuesCleanedUp(messageId2);
 
-        verifyNoMoreInteractions(mockListener, mMockNative);
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mMockNative);
     }
 
     /**
@@ -565,11 +982,11 @@
      */
     @Test
     public void testMultipleMessageSources() throws Exception {
-        final int uid = 300;
+        final int clientId = 300;
+        final int uid = 1000;
         final int clusterLow = 7;
         final int clusterHigh = 7;
         final int masterPref = 0;
-        final int sessionId = 26;
         final String serviceName = "some-service-name";
         final int publishId = 88;
         final int peerId1 = 568;
@@ -582,83 +999,79 @@
         final String msgToPeer2 = "hey there 0506...";
         final int msgToPeerId1 = 546;
         final int msgToPeerId2 = 9654;
-        final int reason = WifiNanSessionListener.FAIL_REASON_OTHER;
+        final int reason = WifiNanSessionCallback.REASON_OTHER;
 
         ConfigRequest configRequest = new ConfigRequest.Builder().setClusterLow(clusterLow)
                 .setClusterHigh(clusterHigh).setMasterPreference(masterPref).build();
 
-        PublishData publishData = new PublishData.Builder().setServiceName(serviceName).build();
-
-        PublishSettings publishSettings = new PublishSettings.Builder()
-                .setPublishType(PublishSettings.PUBLISH_TYPE_UNSOLICITED).build();
+        PublishConfig publishConfig = new PublishConfig.Builder().setServiceName(serviceName)
+                .setPublishType(PublishConfig.PUBLISH_TYPE_UNSOLICITED).build();
 
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
-        IWifiNanEventListener mockListener = mock(IWifiNanEventListener.class);
-        IWifiNanSessionListener mockSessionListener = mock(IWifiNanSessionListener.class);
-        InOrder inOrder = inOrder(mMockNative, mockListener, mockSessionListener);
+        ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
+        IWifiNanEventCallback mockCallback = mock(IWifiNanEventCallback.class);
+        IWifiNanSessionCallback mockSessionCallback = mock(IWifiNanSessionCallback.class);
+        InOrder inOrder = inOrder(mMockNative, mockCallback, mockSessionCallback);
 
-        int allEvents = WifiNanEventListener.LISTEN_CONFIG_COMPLETED
-                | WifiNanEventListener.LISTEN_CONFIG_FAILED
-                | WifiNanEventListener.LISTEN_IDENTITY_CHANGED
-                | WifiNanEventListener.LISTEN_NAN_DOWN;
-
-        int allSessionEvents = WifiNanSessionListener.LISTEN_PUBLISH_FAIL
-                | WifiNanSessionListener.LISTEN_PUBLISH_TERMINATED
-                | WifiNanSessionListener.LISTEN_SUBSCRIBE_FAIL
-                | WifiNanSessionListener.LISTEN_SUBSCRIBE_TERMINATED
-                | WifiNanSessionListener.LISTEN_MATCH
-                | WifiNanSessionListener.LISTEN_MESSAGE_SEND_SUCCESS
-                | WifiNanSessionListener.LISTEN_MESSAGE_SEND_FAIL
-                | WifiNanSessionListener.LISTEN_MESSAGE_RECEIVED;
-
-        mDut.connect(uid, mockListener, allEvents);
-        mDut.requestConfig(uid, configRequest);
-        mDut.createSession(uid, sessionId, mockSessionListener, allSessionEvents);
-        mDut.publish(uid, sessionId, publishData, publishSettings);
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
         mMockLooper.dispatchAll();
 
-        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest));
-        short transactionIdConfig = transactionId.getValue();
-
-        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishData),
-                eq(publishSettings));
-        short transactionIdPublish = transactionId.getValue();
-
-        mDut.onConfigCompleted(transactionIdConfig);
-        mDut.onPublishSuccess(transactionIdPublish, publishId);
-        mDut.onMessageReceived(publishId, peerId1, peerMac1, msgFromPeer1.getBytes(),
-                msgFromPeer1.length());
-        mDut.onMessageReceived(publishId, peerId2, peerMac2, msgFromPeer2.getBytes(),
-                msgFromPeer2.length());
-        mDut.sendMessage(uid, sessionId, peerId2, msgToPeer2.getBytes(), msgToPeer2.length(),
-                msgToPeerId2);
-        mDut.sendMessage(uid, sessionId, peerId1, msgToPeer1.getBytes(), msgToPeer1.length(),
-                msgToPeerId1);
+        // (1) connect
+        mDut.connect(clientId, uid, mockCallback, configRequest);
         mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
 
-        validateInternalTransactionInfoCleanedUp(transactionIdConfig);
-        validateInternalTransactionInfoCleanedUp(transactionIdPublish);
-        inOrder.verify(mockListener).onConfigCompleted(configRequest);
-        inOrder.verify(mockSessionListener).onMessageReceived(peerId1, msgFromPeer1.getBytes(),
+        // (2) publish
+        mDut.publish(clientId, publishConfig, mockSessionCallback);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(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(),
                 msgFromPeer1.length());
-        inOrder.verify(mockSessionListener).onMessageReceived(peerId2, msgFromPeer2.getBytes(),
+        mDut.onMessageReceivedNotification(publishId, peerId2, peerMac2, msgFromPeer2.getBytes(),
                 msgFromPeer2.length());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onMessageReceived(peerId1, msgFromPeer1.getBytes(),
+                msgFromPeer1.length());
+        inOrder.verify(mockSessionCallback).onMessageReceived(peerId2, msgFromPeer2.getBytes(),
+                msgFromPeer2.length());
+
+        // (4) sending messages back to same peers: one Tx fails, other succeeds
+        mDut.sendMessage(clientId, sessionId.getValue(), peerId2, msgToPeer2.getBytes(),
+                msgToPeer2.length(), msgToPeerId2, 0);
+        mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(publishId), eq(peerId2),
-                eq(peerMac2), eq(msgToPeer2.getBytes()), eq(msgToPeer2.length()));
-        short transactionIdMsg2 = transactionId.getValue();
-        inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(publishId), eq(peerId1),
-                eq(peerMac1), eq(msgToPeer1.getBytes()), eq(msgToPeer1.length()));
-        short transactionIdMsg1 = transactionId.getValue();
+                eq(peerMac2), eq(msgToPeer2.getBytes()), eq(msgToPeer2.length()), eq(msgToPeerId2));
+        short transactionIdVal = transactionId.getValue();
+        mDut.onMessageSendQueuedSuccessResponse(transactionIdVal);
+        mDut.onMessageSendSuccessNotification(transactionIdVal);
 
-        mDut.onMessageSendFail(transactionIdMsg1, reason);
-        mDut.onMessageSendSuccess(transactionIdMsg2);
+        mDut.sendMessage(clientId, sessionId.getValue(), peerId1, msgToPeer1.getBytes(),
+                msgToPeer1.length(), 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(msgToPeer1.length()), eq(msgToPeerId1));
+        transactionIdVal = transactionId.getValue();
+        mDut.onMessageSendQueuedSuccessResponse(transactionIdVal);
+        mDut.onMessageSendFailNotification(transactionIdVal, reason);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onMessageSendFail(msgToPeerId1, reason);
+        validateInternalSendMessageQueuesCleanedUp(msgToPeerId1);
+        validateInternalSendMessageQueuesCleanedUp(msgToPeerId2);
 
-        validateInternalTransactionInfoCleanedUp(transactionIdMsg1);
-        validateInternalTransactionInfoCleanedUp(transactionIdMsg2);
-        inOrder.verify(mockSessionListener).onMessageSendFail(msgToPeerId1, reason);
-        inOrder.verify(mockSessionListener).onMessageSendSuccess(msgToPeerId2);
-        verifyNoMoreInteractions(mMockNative, mockListener, mockSessionListener);
+        verifyNoMoreInteractions(mMockNative, mockCallback, mockSessionCallback);
     }
 
     /**
@@ -667,11 +1080,11 @@
      */
     @Test
     public void testMessageWhilePeerChangesIdentity() throws Exception {
-        final int uid = 300;
+        final int clientId = 300;
+        final int uid = 1000;
         final int clusterLow = 7;
         final int clusterHigh = 7;
         final int masterPref = 0;
-        final int sessionId = 26;
         final String serviceName = "some-service-name";
         final int publishId = 88;
         final int peerId = 568;
@@ -686,387 +1099,1125 @@
         ConfigRequest configRequest = new ConfigRequest.Builder().setClusterLow(clusterLow)
                 .setClusterHigh(clusterHigh).setMasterPreference(masterPref).build();
 
-        PublishData publishData = new PublishData.Builder().setServiceName(serviceName).build();
-
-        PublishSettings publishSettings = new PublishSettings.Builder()
-                .setPublishType(PublishSettings.PUBLISH_TYPE_UNSOLICITED).build();
+        PublishConfig publishConfig = new PublishConfig.Builder().setServiceName(serviceName)
+                .setPublishType(PublishConfig.PUBLISH_TYPE_UNSOLICITED).build();
 
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
-        IWifiNanEventListener mockListener = mock(IWifiNanEventListener.class);
-        IWifiNanSessionListener mockSessionListener = mock(IWifiNanSessionListener.class);
-        InOrder inOrder = inOrder(mMockNative, mockListener, mockSessionListener);
+        ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
+        IWifiNanEventCallback mockCallback = mock(IWifiNanEventCallback.class);
+        IWifiNanSessionCallback mockSessionCallback = mock(IWifiNanSessionCallback.class);
+        InOrder inOrder = inOrder(mMockNative, mockCallback, mockSessionCallback);
 
-        int allEvents = WifiNanEventListener.LISTEN_CONFIG_COMPLETED
-                | WifiNanEventListener.LISTEN_CONFIG_FAILED
-                | WifiNanEventListener.LISTEN_IDENTITY_CHANGED
-                | WifiNanEventListener.LISTEN_NAN_DOWN;
-
-        int allSessionEvents = WifiNanSessionListener.LISTEN_PUBLISH_FAIL
-                | WifiNanSessionListener.LISTEN_PUBLISH_TERMINATED
-                | WifiNanSessionListener.LISTEN_SUBSCRIBE_FAIL
-                | WifiNanSessionListener.LISTEN_SUBSCRIBE_TERMINATED
-                | WifiNanSessionListener.LISTEN_MATCH
-                | WifiNanSessionListener.LISTEN_MESSAGE_SEND_SUCCESS
-                | WifiNanSessionListener.LISTEN_MESSAGE_SEND_FAIL
-                | WifiNanSessionListener.LISTEN_MESSAGE_RECEIVED;
-
-        mDut.connect(uid, mockListener, allEvents);
-        mDut.requestConfig(uid, configRequest);
-        mDut.createSession(uid, sessionId, mockSessionListener, allSessionEvents);
-        mDut.publish(uid, sessionId, publishData, publishSettings);
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
         mMockLooper.dispatchAll();
 
-        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest));
-        short transactionIdConfig = transactionId.getValue();
+        // (1) connect
+        mDut.connect(clientId, uid, mockCallback, configRequest);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
 
-        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishData),
-                eq(publishSettings));
-        short transactionIdPublish = transactionId.getValue();
+        // (2) publish
+        mDut.publish(clientId, publishConfig, mockSessionCallback);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishConfig));
+        mDut.onSessionConfigSuccessResponse(transactionId.getValue(), true, publishId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
 
-        mDut.onConfigCompleted(transactionIdConfig);
-        mDut.onPublishSuccess(transactionIdPublish, publishId);
-        mDut.onMessageReceived(publishId, peerId, peerMacOrig, msgFromPeer1.getBytes(),
+        // (3) message received & responded to
+        mDut.onMessageReceivedNotification(publishId, peerId, peerMacOrig, msgFromPeer1.getBytes(),
                 msgFromPeer1.length());
-        mDut.sendMessage(uid, sessionId, peerId, msgToPeer1.getBytes(), msgToPeer1.length(),
-                msgToPeerId1);
+        mDut.sendMessage(clientId, sessionId.getValue(), peerId, msgToPeer1.getBytes(),
+                msgToPeer1.length(), msgToPeerId1, 0);
         mMockLooper.dispatchAll();
-
-        validateInternalTransactionInfoCleanedUp(transactionIdConfig);
-        validateInternalTransactionInfoCleanedUp(transactionIdPublish);
-        inOrder.verify(mockListener).onConfigCompleted(configRequest);
-        inOrder.verify(mockSessionListener).onMessageReceived(peerId, msgFromPeer1.getBytes(),
+        inOrder.verify(mockSessionCallback).onMessageReceived(peerId, msgFromPeer1.getBytes(),
                 msgFromPeer1.length());
         inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(publishId), eq(peerId),
-                eq(peerMacOrig), eq(msgToPeer1.getBytes()), eq(msgToPeer1.length()));
-        short transactionIdMsg = transactionId.getValue();
-
-        mDut.onMessageSendSuccess(transactionIdMsg);
-        mDut.onMessageReceived(publishId, peerId, peerMacLater, msgFromPeer2.getBytes(),
-                msgFromPeer2.length());
-        mDut.sendMessage(uid, sessionId, peerId, msgToPeer2.getBytes(), msgToPeer2.length(),
-                msgToPeerId2);
+                eq(peerMacOrig), eq(msgToPeer1.getBytes()), eq(msgToPeer1.length()),
+                eq(msgToPeerId1));
+        mDut.onMessageSendQueuedSuccessResponse(transactionId.getValue());
+        mDut.onMessageSendSuccessNotification(transactionId.getValue());
         mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onMessageSendSuccess(msgToPeerId1);
+        validateInternalSendMessageQueuesCleanedUp(msgToPeerId1);
 
-        validateInternalTransactionInfoCleanedUp(transactionIdMsg);
-        inOrder.verify(mockSessionListener).onMessageSendSuccess(msgToPeerId1);
-        inOrder.verify(mockSessionListener).onMessageReceived(peerId, msgFromPeer2.getBytes(),
+        // (4) message received with same peer ID but different MAC
+        mDut.onMessageReceivedNotification(publishId, peerId, peerMacLater, msgFromPeer2.getBytes(),
+                msgFromPeer2.length());
+        mDut.sendMessage(clientId, sessionId.getValue(), peerId, msgToPeer2.getBytes(),
+                msgToPeer2.length(), msgToPeerId2, 0);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onMessageReceived(peerId, msgFromPeer2.getBytes(),
                 msgFromPeer2.length());
         inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(publishId), eq(peerId),
-                eq(peerMacLater), eq(msgToPeer2.getBytes()), eq(msgToPeer2.length()));
-        transactionIdMsg = transactionId.getValue();
+                eq(peerMacLater), eq(msgToPeer2.getBytes()), eq(msgToPeer2.length()),
+                eq(msgToPeerId2));
+        mDut.onMessageSendQueuedSuccessResponse(transactionId.getValue());
+        mDut.onMessageSendSuccessNotification(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onMessageSendSuccess(msgToPeerId2);
+        validateInternalSendMessageQueuesCleanedUp(msgToPeerId2);
 
-        mDut.onMessageSendSuccess(transactionIdMsg);
+        verifyNoMoreInteractions(mMockNative, mockCallback, mockSessionCallback);
+    }
+
+    /**
+     * Validate that get failure (with correct code) when trying to send a
+     * message to an invalid peer ID.
+     */
+    @Test
+    public void testSendMessageToInvalidPeerId() throws Exception {
+        final int clientId = 1005;
+        final int uid = 1000;
+        final String ssi = "some much longer and more arbitrary data";
+        final int 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 messageId = 6948;
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
+
+        IWifiNanEventCallback mockCallback = mock(IWifiNanEventCallback.class);
+        IWifiNanSessionCallback mockSessionCallback = mock(IWifiNanSessionCallback.class);
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mMockNative);
+
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
         mMockLooper.dispatchAll();
 
-        validateInternalTransactionInfoCleanedUp(transactionIdMsg);
-        inOrder.verify(mockSessionListener).onMessageSendSuccess(msgToPeerId2);
+        // (1) connect
+        mDut.connect(clientId, uid, mockCallback, configRequest);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
 
-        verifyNoMoreInteractions(mMockNative, mockListener, mockSessionListener);
+        // (2) subscribe & match
+        mDut.subscribe(clientId, subscribeConfig, mockSessionCallback);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeConfig));
+        mDut.onSessionConfigSuccessResponse(transactionId.getValue(), false, subscribeId);
+        mDut.onMatchNotification(subscribeId, requestorId, peerMac, peerSsi.getBytes(),
+                peerSsi.length(), peerMatchFilter.getBytes(), peerMatchFilter.length());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
+        inOrder.verify(mockSessionCallback).onMatch(requestorId, peerSsi.getBytes(),
+                peerSsi.length(), peerMatchFilter.getBytes(), peerMatchFilter.length());
+
+        // (3) send message to invalid peer ID
+        mDut.sendMessage(clientId, sessionId.getValue(), requestorId + 5, ssi.getBytes(),
+                ssi.length(), messageId, 0);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onMessageSendFail(messageId,
+                WifiNanSessionCallback.REASON_NO_MATCH_SESSION);
+        validateInternalSendMessageQueuesCleanedUp(messageId);
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mMockNative);
+    }
+
+    /**
+     * Validate that on send message timeout correct callback is dispatched and that a later
+     * firmware notification is ignored.
+     */
+    @Test
+    public void testSendMessageTimeout() throws Exception {
+        final int clientId = 1005;
+        final int uid = 1000;
+        final String ssi = "some much longer and more arbitrary data";
+        final int 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 messageId = 6948;
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
+
+        IWifiNanEventCallback mockCallback = mock(IWifiNanEventCallback.class);
+        IWifiNanSessionCallback mockSessionCallback = mock(IWifiNanSessionCallback.class);
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mMockNative);
+
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
+        mMockLooper.dispatchAll();
+
+        // (1) connect
+        mDut.connect(clientId, uid, mockCallback, configRequest);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
+
+        // (2) subscribe & match
+        mDut.subscribe(clientId, subscribeConfig, mockSessionCallback);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeConfig));
+        mDut.onSessionConfigSuccessResponse(transactionId.getValue(), false, subscribeId);
+        mDut.onMatchNotification(subscribeId, requestorId, peerMac, peerSsi.getBytes(),
+                peerSsi.length(), peerMatchFilter.getBytes(), peerMatchFilter.length());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
+        inOrder.verify(mockSessionCallback).onMatch(requestorId, peerSsi.getBytes(),
+                peerSsi.length(), peerMatchFilter.getBytes(), peerMatchFilter.length());
+
+        // (3) send 2 messages and enqueue successfully
+        mDut.sendMessage(clientId, sessionId.getValue(), requestorId, ssi.getBytes(),
+                ssi.length(), messageId, 0);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(subscribeId),
+                eq(requestorId), eq(peerMac), eq(ssi.getBytes()), eq(ssi.length()), eq(messageId));
+        short transactionId1 = transactionId.getValue();
+        mDut.onMessageSendQueuedSuccessResponse(transactionId1);
+        mMockLooper.dispatchAll();
+
+        mDut.sendMessage(clientId, sessionId.getValue(), requestorId, ssi.getBytes(),
+                ssi.length(), messageId + 1, 0);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(subscribeId),
+                eq(requestorId), eq(peerMac), eq(ssi.getBytes()), eq(ssi.length()),
+                eq(messageId + 1));
+        short transactionId2 = transactionId.getValue();
+        mDut.onMessageSendQueuedSuccessResponse(transactionId2);
+        mMockLooper.dispatchAll();
+
+        // (4) message send timeout
+        assertTrue(mAlarmManager.dispatch(WifiNanStateManager.HAL_SEND_MESSAGE_TIMEOUT_TAG));
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onMessageSendFail(messageId,
+                WifiNanSessionCallback.REASON_TX_FAIL);
+        validateInternalSendMessageQueuesCleanedUp(messageId);
+
+        // (5) firmware response (unlikely - but good to check)
+        mDut.onMessageSendSuccessNotification(transactionId1);
+        mDut.onMessageSendSuccessNotification(transactionId2);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onMessageSendSuccess(messageId + 1);
+
+        validateInternalSendMessageQueuesCleanedUp(messageId + 1);
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mMockNative);
+    }
+
+    /**
+     * Validate that when sending a message with a retry count the message is retried the specified
+     * number of times. Scenario ending with success.
+     */
+    @Test
+    public void testSendMessageRetransmitSuccess() throws Exception {
+        final int clientId = 1005;
+        final int uid = 1000;
+        final String ssi = "some much longer and more arbitrary data";
+        final int 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 messageId = 6948;
+        final int retryCount = 3;
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
+
+        IWifiNanEventCallback mockCallback = mock(IWifiNanEventCallback.class);
+        IWifiNanSessionCallback mockSessionCallback = mock(IWifiNanSessionCallback.class);
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mMockNative);
+
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
+        mMockLooper.dispatchAll();
+
+        // (1) connect
+        mDut.connect(clientId, uid, mockCallback, configRequest);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
+
+        // (2) subscribe & match
+        mDut.subscribe(clientId, subscribeConfig, mockSessionCallback);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeConfig));
+        mDut.onSessionConfigSuccessResponse(transactionId.getValue(), false, subscribeId);
+        mDut.onMatchNotification(subscribeId, requestorId, peerMac, peerSsi.getBytes(),
+                peerSsi.length(), peerMatchFilter.getBytes(), peerMatchFilter.length());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
+        inOrder.verify(mockSessionCallback).onMatch(requestorId, peerSsi.getBytes(),
+                peerSsi.length(), peerMatchFilter.getBytes(), peerMatchFilter.length());
+
+        // (3) send message and enqueue successfully
+        mDut.sendMessage(clientId, sessionId.getValue(), requestorId, ssi.getBytes(),
+                ssi.length(), messageId, retryCount);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(subscribeId),
+                eq(requestorId), eq(peerMac), eq(ssi.getBytes()), eq(ssi.length()), eq(messageId));
+        mDut.onMessageSendQueuedSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+
+        // (4) loop and fail until reach retryCount
+        for (int i = 0; i < retryCount; ++i) {
+            mDut.onMessageSendFailNotification(transactionId.getValue(),
+                    WifiNanSessionCallback.REASON_TX_FAIL);
+            mMockLooper.dispatchAll();
+            inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(subscribeId),
+                    eq(requestorId), eq(peerMac), eq(ssi.getBytes()), eq(ssi.length()),
+                    eq(messageId));
+            mDut.onMessageSendQueuedSuccessResponse(transactionId.getValue());
+            mMockLooper.dispatchAll();
+        }
+
+        // (5) succeed on last retry
+        mDut.onMessageSendSuccessNotification(transactionId.getValue());
+        mMockLooper.dispatchAll();
+
+        inOrder.verify(mockSessionCallback).onMessageSendSuccess(messageId);
+        validateInternalSendMessageQueuesCleanedUp(messageId);
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mMockNative);
+    }
+
+    /**
+     * Validate that when sending a message with a retry count the message is retried the specified
+     * number of times. Scenario ending with failure.
+     */
+    @Test
+    public void testSendMessageRetransmitFail() throws Exception {
+        final int clientId = 1005;
+        final int uid = 1000;
+        final String ssi = "some much longer and more arbitrary data";
+        final int 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 messageId = 6948;
+        final int retryCount = 3;
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
+
+        IWifiNanEventCallback mockCallback = mock(IWifiNanEventCallback.class);
+        IWifiNanSessionCallback mockSessionCallback = mock(IWifiNanSessionCallback.class);
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mMockNative);
+
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
+        mMockLooper.dispatchAll();
+
+        // (1) connect
+        mDut.connect(clientId, uid, mockCallback, configRequest);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
+
+        // (2) subscribe & match
+        mDut.subscribe(clientId, subscribeConfig, mockSessionCallback);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeConfig));
+        mDut.onSessionConfigSuccessResponse(transactionId.getValue(), false, subscribeId);
+        mDut.onMatchNotification(subscribeId, requestorId, peerMac, peerSsi.getBytes(),
+                peerSsi.length(), peerMatchFilter.getBytes(), peerMatchFilter.length());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
+        inOrder.verify(mockSessionCallback).onMatch(requestorId, peerSsi.getBytes(),
+                peerSsi.length(), peerMatchFilter.getBytes(), peerMatchFilter.length());
+
+        // (3) send message and enqueue successfully
+        mDut.sendMessage(clientId, sessionId.getValue(), requestorId, ssi.getBytes(),
+                ssi.length(), messageId, retryCount);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(subscribeId),
+                eq(requestorId), eq(peerMac), eq(ssi.getBytes()), eq(ssi.length()), eq(messageId));
+        mDut.onMessageSendQueuedSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+
+        // (4) loop and fail until reach retryCount+1
+        for (int i = 0; i < retryCount + 1; ++i) {
+            mDut.onMessageSendFailNotification(transactionId.getValue(),
+                    WifiNanSessionCallback.REASON_TX_FAIL);
+            mMockLooper.dispatchAll();
+
+            if (i != retryCount) {
+                inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(subscribeId),
+                        eq(requestorId), eq(peerMac), eq(ssi.getBytes()), eq(ssi.length()),
+                        eq(messageId));
+                mDut.onMessageSendQueuedSuccessResponse(transactionId.getValue());
+                mMockLooper.dispatchAll();
+            }
+        }
+
+        inOrder.verify(mockSessionCallback).onMessageSendFail(messageId,
+                WifiNanSessionCallback.REASON_TX_FAIL);
+        validateInternalSendMessageQueuesCleanedUp(messageId);
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mMockNative);
     }
 
     @Test
+    public void testSendMessageQueueAllQueueFail() throws Exception {
+        WifiNanNative.Capabilities cap = getCapabilities();
+        testSendMessageQueue(SendMessageAnswer.OP_QUEUE_FAIL, cap,
+                cap.maxQueuedTransmitMessages + 5);
+    }
+
+    @Test
+    public void testSendMessageQueueAllTxSuccess() throws Exception {
+        WifiNanNative.Capabilities cap = getCapabilities();
+        testSendMessageQueue(SendMessageAnswer.OP_QUEUE_OK_SEND_OK, cap,
+                cap.maxQueuedTransmitMessages + 5);
+    }
+
+    @Test
+    public void testSendMessageQueueAllTxFailRetxOk() throws Exception {
+        WifiNanNative.Capabilities cap = getCapabilities();
+        testSendMessageQueue(SendMessageAnswer.OP_QUEUE_OK_SEND_RETX_OK, cap,
+                cap.maxQueuedTransmitMessages + 5);
+    }
+
+    @Test
+    public void testSendMessageQueueAllTxFail() throws Exception {
+        WifiNanNative.Capabilities cap = getCapabilities();
+        testSendMessageQueue(SendMessageAnswer.OP_QUEUE_OK_SEND_RETX_FAIL, cap,
+                cap.maxQueuedTransmitMessages + 5);
+    }
+
+    @Test
+    public void testSendMessageQueueRandomize() throws Exception {
+        WifiNanNative.Capabilities cap = getCapabilities();
+        testSendMessageQueue(SendMessageAnswer.OP_QUEUE_RANDOMIZE, cap,
+                cap.maxQueuedTransmitMessages * 10);
+    }
+
+    /**
+     * Validate that when sending more messages than can be queued by the firmware (based on
+     * capability information) they are queued. Support all possible test success/failure codes.
+     * @param behavior: SendMessageAnswer.OP_*.
+     */
+    private void testSendMessageQueue(int behavior, WifiNanNative.Capabilities cap, int numMessages)
+            throws Exception {
+        final int clientId = 1005;
+        final int uid = 1000;
+        final String ssi = "some much longer and more arbitrary data";
+        final int 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 messageId = 6948;
+        final int retryCount = 3;
+        final int reason = WifiNanSessionCallback.REASON_OTHER;
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
+
+        IWifiNanEventCallback mockCallback = mock(IWifiNanEventCallback.class);
+        IWifiNanSessionCallback mockSessionCallback = mock(IWifiNanSessionCallback.class);
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> msgId = ArgumentCaptor.forClass(Integer.class);
+
+        // (0) initial conditions
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), cap);
+        mMockLooper.dispatchAll();
+
+        // (1) connect
+        mDut.connect(clientId, uid, mockCallback, configRequest);
+        mMockLooper.dispatchAll();
+        verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        verify(mockCallback).onConnectSuccess();
+
+        // (2) subscribe & match
+        mDut.subscribe(clientId, subscribeConfig, mockSessionCallback);
+        mMockLooper.dispatchAll();
+        verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeConfig));
+        mDut.onSessionConfigSuccessResponse(transactionId.getValue(), false, subscribeId);
+        mDut.onMatchNotification(subscribeId, requestorId, peerMac, peerSsi.getBytes(),
+                peerSsi.length(), peerMatchFilter.getBytes(), peerMatchFilter.length());
+        mMockLooper.dispatchAll();
+        verify(mockSessionCallback).onSessionStarted(sessionId.capture());
+        verify(mockSessionCallback).onMatch(requestorId, peerSsi.getBytes(), peerSsi.length(),
+                peerMatchFilter.getBytes(), peerMatchFilter.length());
+
+        // (3) send large number of messages
+        SendMessageAnswer answerObj = new SendMessageAnswer(behavior);
+        when(mMockNative.sendMessage(anyShort(), anyInt(), anyInt(), any(byte[].class),
+                any(byte[].class), anyInt(), anyInt())).thenAnswer(answerObj);
+        for (int i = 0; i < numMessages; ++i) {
+            mDut.sendMessage(clientId, sessionId.getValue(), requestorId, ssi.getBytes(),
+                    ssi.length(), messageId + i, retryCount);
+        }
+        mMockLooper.dispatchAll();
+
+        int numSends = answerObj.ops[SendMessageAnswer.OP_QUEUE_FAIL]
+                + answerObj.ops[SendMessageAnswer.OP_QUEUE_OK_SEND_OK]
+                + answerObj.ops[SendMessageAnswer.OP_QUEUE_OK_SEND_RETX_OK] * 2
+                + answerObj.ops[SendMessageAnswer.OP_QUEUE_OK_SEND_RETX_FAIL] * (retryCount + 1);
+        int numOnSendSuccess = answerObj.ops[SendMessageAnswer.OP_QUEUE_OK_SEND_OK]
+                + answerObj.ops[SendMessageAnswer.OP_QUEUE_OK_SEND_RETX_OK];
+        int numOnSendFail = answerObj.ops[SendMessageAnswer.OP_QUEUE_OK_SEND_RETX_FAIL];
+
+        Log.v("WifiNanStateManagerTest",
+                "testSendMessageQueue: ops=" + Arrays.toString(answerObj.ops) + ", numSends="
+                        + numSends + ", numOnSendSuccess=" + numOnSendSuccess + ", numOnSendFail="
+                        + numOnSendFail);
+
+        verify(mMockNative, times(numSends)).sendMessage(anyShort(), eq(subscribeId),
+                eq(requestorId), eq(peerMac), eq(ssi.getBytes()), eq(ssi.length()), anyInt());
+        verify(mockSessionCallback, times(numOnSendSuccess)).onMessageSendSuccess(anyInt());
+        verify(mockSessionCallback, times(numOnSendFail)).onMessageSendFail(anyInt(), anyInt());
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mMockNative);
+    }
+
+    private class SendMessageAnswer extends MockAnswerUtil.AnswerWithArguments {
+        public static final int OP_QUEUE_FAIL = 0;
+        public static final int OP_QUEUE_OK_SEND_OK = 1;
+        public static final int OP_QUEUE_OK_SEND_RETX_OK = 2;
+        public static final int OP_QUEUE_OK_SEND_RETX_FAIL = 3;
+
+        /* psuedo operation: randomly pick from the above 4 operations */
+        public static final int OP_QUEUE_RANDOMIZE = -1;
+
+        /* the number of operations which can be executed. Doesn't cound RANDOMIZE since it is
+         * resolved to one of the 4 types */
+        private static final int NUM_OPS = 4;
+
+        public int[] ops = new int[NUM_OPS];
+
+        private int mBehavior = 0;
+        private SparseIntArray mPacketBehavior = new SparseIntArray();
+
+        SendMessageAnswer(int behavior) {
+            mBehavior = behavior;
+        }
+
+        public boolean answer(short transactionId, int pubSubId, int requestorInstanceId,
+                byte[] dest, byte[] message, int messageLength, int messageId) throws Exception {
+            Log.v("WifiNanStateManagerTest",
+                    "SendMessageAnswer.answer: mBehavior=" + mBehavior + ", transactionId="
+                            + transactionId + ", messageId=" + messageId
+                            + ", mPacketBehavior[messageId]" + mPacketBehavior.get(messageId, -1));
+
+            int behavior = mBehavior;
+            if (behavior == OP_QUEUE_RANDOMIZE) {
+                behavior = mRandomNg.nextInt(NUM_OPS);
+            }
+
+            boolean packetRetx = mPacketBehavior.get(messageId, -1) != -1;
+            if (packetRetx) {
+                behavior = mPacketBehavior.get(messageId);
+            } else {
+                mPacketBehavior.put(messageId, behavior);
+            }
+
+            if (behavior == OP_QUEUE_FAIL) {
+                ops[OP_QUEUE_FAIL]++;
+                mDut.onMessageSendQueuedFailResponse(transactionId,
+                        WifiNanSessionCallback.REASON_OTHER);
+            } else if (behavior == OP_QUEUE_OK_SEND_OK) {
+                ops[OP_QUEUE_OK_SEND_OK]++;
+                mDut.onMessageSendQueuedSuccessResponse(transactionId);
+                mDut.onMessageSendSuccessNotification(transactionId);
+            } else if (behavior == OP_QUEUE_OK_SEND_RETX_OK) {
+                mDut.onMessageSendQueuedSuccessResponse(transactionId);
+                if (!packetRetx) {
+                    mDut.onMessageSendFailNotification(transactionId,
+                            WifiNanSessionCallback.REASON_TX_FAIL);
+                } else {
+                    ops[OP_QUEUE_OK_SEND_RETX_OK]++;
+                    mDut.onMessageSendSuccessNotification(transactionId);
+                }
+            } else if (behavior == OP_QUEUE_OK_SEND_RETX_FAIL) {
+                mDut.onMessageSendQueuedSuccessResponse(transactionId);
+                if (!packetRetx) {
+                    ops[OP_QUEUE_OK_SEND_RETX_FAIL]++;
+                }
+                mDut.onMessageSendFailNotification(transactionId,
+                        WifiNanSessionCallback.REASON_TX_FAIL);
+            }
+            return true;
+        }
+    }
+
+    /**
+     * Validate that start ranging function fills-in correct MAC addresses for peer IDs and
+     * passed along to RTT module.
+     */
+    @Test
+    public void testStartRanging() throws Exception {
+        final int clientId = 1005;
+        final int uid = 1000;
+        final int 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();
+
+        IWifiNanEventCallback mockCallback = mock(IWifiNanEventCallback.class);
+        IWifiNanSessionCallback mockSessionCallback = mock(IWifiNanSessionCallback.class);
+
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<WifiNanClientState> clientCaptor =
+                ArgumentCaptor.forClass(WifiNanClientState.class);
+        ArgumentCaptor<RttManager.RttParams[]> rttParamsCaptor =
+                ArgumentCaptor.forClass(RttManager.RttParams[].class);
+
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mMockNative,
+                mMockNanRttStateManager);
+
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
+        mMockLooper.dispatchAll();
+
+        // (1) connect
+        mDut.connect(clientId, uid, mockCallback, configRequest);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
+
+        // (2) subscribe & match
+        mDut.subscribe(clientId, subscribeConfig, mockSessionCallback);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeConfig));
+        mDut.onSessionConfigSuccessResponse(transactionId.getValue(), false, subscribeId);
+        mDut.onMatchNotification(subscribeId, requestorId, peerMac, peerSsi.getBytes(),
+                peerSsi.length(), peerMatchFilter.getBytes(), peerMatchFilter.length());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
+        inOrder.verify(mockSessionCallback).onMatch(requestorId, peerSsi.getBytes(),
+                peerSsi.length(), peerMatchFilter.getBytes(), peerMatchFilter.length());
+
+        // (3) start ranging: pass along a valid peer ID and an invalid one
+        mDut.startRanging(clientId, sessionId.getValue(), params, rangingId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNanRttStateManager).startRanging(eq(rangingId), clientCaptor.capture(),
+                rttParamsCaptor.capture());
+        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));
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mMockNative,
+                mMockNanRttStateManager);
+    }
+
+    /**
+     * Test sequence of configuration: (1) config1, (2) config2 - incompatible,
+     * (3) config3 - compatible with config1 (requiring upgrade), (4) disconnect
+     * config3 (should get a downgrade), (5) disconnect config1 (should get a
+     * disable).
+     */
+    @Test
     public void testConfigs() throws Exception {
-        final int uid1 = 9999;
+        final int clientId1 = 9999;
+        final int uid = 1000;
         final int clusterLow1 = 5;
         final int clusterHigh1 = 100;
         final int masterPref1 = 111;
-        final int uid2 = 1001;
+        final int clientId2 = 1001;
         final boolean support5g2 = true;
         final int clusterLow2 = 7;
         final int clusterHigh2 = 155;
         final int masterPref2 = 0;
-        final int uid3 = 55;
+        final int clientId3 = 55;
 
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
         ArgumentCaptor<ConfigRequest> crCapture = ArgumentCaptor.forClass(ConfigRequest.class);
 
         ConfigRequest configRequest1 = new ConfigRequest.Builder().setClusterLow(clusterLow1)
-                .setClusterHigh(clusterHigh1).setMasterPreference(masterPref1).build();
+                .setClusterHigh(clusterHigh1).setMasterPreference(masterPref1)
+                .setEnableIdentityChangeCallback(false).build();
 
         ConfigRequest configRequest2 = new ConfigRequest.Builder().setSupport5gBand(support5g2)
                 .setClusterLow(clusterLow2).setClusterHigh(clusterHigh2)
                 .setMasterPreference(masterPref2).build();
 
-        ConfigRequest configRequest3 = new ConfigRequest.Builder().build();
+        ConfigRequest configRequest3 = new ConfigRequest.Builder().setClusterLow(clusterLow1)
+                .setClusterHigh(clusterHigh1).setMasterPreference(masterPref1)
+                .setEnableIdentityChangeCallback(true).build();
 
-        IWifiNanEventListener mockListener1 = mock(IWifiNanEventListener.class);
-        IWifiNanEventListener mockListener2 = mock(IWifiNanEventListener.class);
-        IWifiNanEventListener mockListener3 = mock(IWifiNanEventListener.class);
+        IWifiNanEventCallback mockCallback1 = mock(IWifiNanEventCallback.class);
+        IWifiNanEventCallback mockCallback2 = mock(IWifiNanEventCallback.class);
+        IWifiNanEventCallback mockCallback3 = mock(IWifiNanEventCallback.class);
 
-        InOrder inOrder = inOrder(mMockNative, mockListener1, mockListener2, mockListener3);
+        InOrder inOrder = inOrder(mMockNative, mockCallback1, mockCallback2, mockCallback3);
 
-        mDut.connect(uid1, mockListener1, WifiNanEventListener.LISTEN_CONFIG_COMPLETED);
-        mDut.requestConfig(uid1, configRequest1);
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
         mMockLooper.dispatchAll();
 
+        // (1) config1 (valid)
+        mDut.connect(clientId1, uid, mockCallback1, configRequest1);
+        mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                crCapture.capture());
-        collector.checkThat("merge: stage 0", configRequest1, equalTo(crCapture.getValue()));
-
-        mDut.onConfigCompleted(transactionId.getValue());
+                crCapture.capture(), eq(true));
+        collector.checkThat("merge: stage 1", crCapture.getValue(), equalTo(configRequest1));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback1).onConnectSuccess();
 
-        validateInternalTransactionInfoCleanedUp(transactionId.getValue());
-        inOrder.verify(mockListener1).onConfigCompleted(configRequest1);
-
-        mDut.connect(uid2, mockListener2, WifiNanEventListener.LISTEN_CONFIG_COMPLETED);
-        mDut.requestConfig(uid2, configRequest2);
+        // (2) config2 (incompatible with config1)
+        mDut.connect(clientId2, uid, mockCallback2, configRequest2);
         mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback2)
+                .onConnectFail(WifiNanEventCallback.REASON_ALREADY_CONNECTED_INCOMPAT_CONFIG);
+        validateInternalClientInfoCleanedUp(clientId2);
 
+        // (3) config3 (compatible with config1 but requires upgrade - i.e. no
+        // OTA changes)
+        mDut.connect(clientId3, uid, mockCallback3, configRequest3);
+        mMockLooper.dispatchAll();
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                crCapture.capture());
-        collector.checkThat("merge: stage 1: support 5g", crCapture.getValue().mSupport5gBand,
-                equalTo(true));
-        collector.checkThat("merge: stage 1: master pref", crCapture.getValue().mMasterPreference,
-                equalTo(Math.max(masterPref1, masterPref2)));
-        collector.checkThat("merge: stage 1: cluster low", crCapture.getValue().mClusterLow,
-                equalTo(Math.min(clusterLow1, clusterLow2)));
-        collector.checkThat("merge: stage 1: cluster high", crCapture.getValue().mClusterHigh,
-                equalTo(Math.max(clusterHigh1, clusterHigh2)));
-
-        mDut.onConfigCompleted(transactionId.getValue());
+                crCapture.capture(), eq(false));
+        collector.checkThat("merge: stage 3: support 5g", crCapture.getValue().mSupport5gBand,
+                equalTo(false));
+        collector.checkThat("merge: stage 3: master pref", crCapture.getValue().mMasterPreference,
+                equalTo(masterPref1));
+        collector.checkThat("merge: stage 3: cluster low", crCapture.getValue().mClusterLow,
+                equalTo(clusterLow1));
+        collector.checkThat("merge: stage 3: cluster high", crCapture.getValue().mClusterHigh,
+                equalTo(clusterHigh1));
+        collector.checkThat("merge: stage 3: enable identity change callback",
+                crCapture.getValue().mEnableIdentityChangeCallback, equalTo(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback3).onConnectSuccess();
 
-        validateInternalTransactionInfoCleanedUp(transactionId.getValue());
-        inOrder.verify(mockListener1).onConfigCompleted(crCapture.getValue());
-
-        mDut.connect(uid3, mockListener3, WifiNanEventListener.LISTEN_CONFIG_COMPLETED);
-        mDut.requestConfig(uid3, configRequest3);
+        // (4) disconnect config3: want a downgrade
+        mDut.disconnect(clientId3);
         mMockLooper.dispatchAll();
-
+        validateInternalClientInfoCleanedUp(clientId3);
         inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                crCapture.capture());
-        collector.checkThat("merge: stage 2: support 5g", crCapture.getValue().mSupport5gBand,
-                equalTo(true));
-        collector.checkThat("merge: stage 2: master pref", crCapture.getValue().mMasterPreference,
-                equalTo(Math.max(masterPref1, masterPref2)));
-        collector.checkThat("merge: stage 2: cluster low", crCapture.getValue().mClusterLow,
-                equalTo(Math.min(clusterLow1, clusterLow2)));
-        collector.checkThat("merge: stage 2: cluster high", crCapture.getValue().mClusterHigh,
-                equalTo(Math.max(clusterHigh1, clusterHigh2)));
-
-        mDut.onConfigCompleted(transactionId.getValue());
+                crCapture.capture(), eq(false));
+        collector.checkThat("merge: stage 4", crCapture.getValue(), equalTo(configRequest1));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
 
-        validateInternalTransactionInfoCleanedUp(transactionId.getValue());
-        inOrder.verify(mockListener1).onConfigCompleted(crCapture.getValue());
-
-        mDut.disconnect(uid2);
+        // (5) disconnect config1: disable
+        mDut.disconnect(clientId1);
         mMockLooper.dispatchAll();
+        validateInternalClientInfoCleanedUp(clientId1);
+        inOrder.verify(mMockNative).disable((short) 0);
 
-        validateInternalClientInfoCleanedUp(uid2);
-        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                crCapture.capture());
-        collector.checkThat("merge: stage 3", configRequest1, equalTo(crCapture.getValue()));
-
-        mDut.onConfigCompleted(transactionId.getValue());
-        mMockLooper.dispatchAll();
-
-        validateInternalTransactionInfoCleanedUp(transactionId.getValue());
-        inOrder.verify(mockListener1).onConfigCompleted(crCapture.getValue());
-
-        mDut.disconnect(uid1);
-        mMockLooper.dispatchAll();
-
-        validateInternalClientInfoCleanedUp(uid2);
-        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                crCapture.capture());
-        collector.checkThat("merge: stage 4", configRequest3, equalTo(crCapture.getValue()));
-
-        mDut.onConfigCompleted(transactionId.getValue());
-        mMockLooper.dispatchAll();
-
-        validateInternalTransactionInfoCleanedUp(transactionId.getValue());
-        inOrder.verify(mockListener3).onConfigCompleted(crCapture.getValue());
-
-        mDut.disconnect(uid3);
-        mMockLooper.dispatchAll();
-
-        validateInternalClientInfoCleanedUp(uid2);
-        inOrder.verify(mMockNative).disable(anyShort());
-
-        verifyNoMoreInteractions(mMockNative);
+        verifyNoMoreInteractions(mMockNative, mockCallback1, mockCallback2, mockCallback3);
     }
 
     /**
      * Summary: disconnect a client while there are pending transactions.
-     * Validate that no callbacks are called and that internal state is
-     * cleaned-up.
      */
     @Test
     public void testDisconnectWithPendingTransactions() throws Exception {
-        final int uid = 125;
+        final int clientId = 125;
+        final int uid = 1000;
         final int clusterLow = 5;
         final int clusterHigh = 100;
         final int masterPref = 111;
-        final int sessionId = 20;
         final String serviceName = "some-service-name";
         final String ssi = "some much longer and more arbitrary data";
         final int publishCount = 7;
-        final int reason = WifiNanSessionListener.TERMINATE_REASON_DONE;
+        final int reason = WifiNanSessionCallback.TERMINATE_REASON_DONE;
         final int publishId = 22;
 
         ConfigRequest configRequest = new ConfigRequest.Builder().setClusterLow(clusterLow)
                 .setClusterHigh(clusterHigh).setMasterPreference(masterPref).build();
 
-        PublishData publishData = new PublishData.Builder().setServiceName(serviceName)
-                .setServiceSpecificInfo(ssi).build();
-
-        PublishSettings publishSettings = new PublishSettings.Builder()
-                .setPublishType(PublishSettings.PUBLISH_TYPE_UNSOLICITED)
+        PublishConfig publishConfig = new PublishConfig.Builder().setServiceName(serviceName)
+                .setServiceSpecificInfo(ssi).setPublishType(PublishConfig.PUBLISH_TYPE_UNSOLICITED)
                 .setPublishCount(publishCount).build();
 
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
-        IWifiNanEventListener mockListener = mock(IWifiNanEventListener.class);
-        IWifiNanSessionListener mockSessionListener = mock(IWifiNanSessionListener.class);
-        InOrder inOrder = inOrder(mMockNative, mockListener, mockSessionListener);
+        IWifiNanEventCallback mockCallback = mock(IWifiNanEventCallback.class);
+        IWifiNanSessionCallback mockSessionCallback = mock(IWifiNanSessionCallback.class);
+        InOrder inOrder = inOrder(mMockNative, mockCallback, mockSessionCallback);
 
-        int allEvents = WifiNanEventListener.LISTEN_CONFIG_COMPLETED
-                | WifiNanEventListener.LISTEN_CONFIG_FAILED
-                | WifiNanEventListener.LISTEN_IDENTITY_CHANGED
-                | WifiNanEventListener.LISTEN_NAN_DOWN;
-
-        int allSessionEvents = WifiNanSessionListener.LISTEN_PUBLISH_FAIL
-                | WifiNanSessionListener.LISTEN_PUBLISH_TERMINATED
-                | WifiNanSessionListener.LISTEN_SUBSCRIBE_FAIL
-                | WifiNanSessionListener.LISTEN_SUBSCRIBE_TERMINATED
-                | WifiNanSessionListener.LISTEN_MATCH
-                | WifiNanSessionListener.LISTEN_MESSAGE_SEND_SUCCESS
-                | WifiNanSessionListener.LISTEN_MESSAGE_SEND_FAIL
-                | WifiNanSessionListener.LISTEN_MESSAGE_RECEIVED;
-
-        mDut.connect(uid, mockListener, allEvents);
-        mDut.createSession(uid, sessionId, mockSessionListener, allSessionEvents);
-        mDut.requestConfig(uid, configRequest);
-        mDut.publish(uid, sessionId, publishData, publishSettings);
-        mDut.disconnect(uid);
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
         mMockLooper.dispatchAll();
 
-        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest));
-        short transactionIdConfig = transactionId.getValue();
+        // (1) connect
+        mDut.connect(clientId, uid, mockCallback, configRequest);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
 
-        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishData),
-                eq(publishSettings));
-        short transactionIdPublish = transactionId.getValue();
+        // (2) publish (no response yet)
+        mDut.publish(clientId, publishConfig, mockSessionCallback);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishConfig));
 
-        validateInternalClientInfoCleanedUp(uid);
-        validateInternalTransactionInfoCleanedUp(transactionIdPublish);
-
-        mDut.onConfigCompleted(transactionIdConfig);
-        mDut.onPublishSuccess(transactionIdPublish, publishId);
+        // (3) disconnect (but doesn't get executed until get a RESPONSE to the
+        // previous publish)
+        mDut.disconnect(clientId);
         mMockLooper.dispatchAll();
 
-        mDut.onPublishTerminated(publishId, reason);
+        // (4) get successful response to the publish
+        mDut.onSessionConfigSuccessResponse(transactionId.getValue(), true, publishId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSessionStarted(anyInt());
+        inOrder.verify(mMockNative).stopPublish((short) 0, publishId);
+        inOrder.verify(mMockNative).disable((short) 0);
+
+        validateInternalClientInfoCleanedUp(clientId);
+
+        // (5) trying to publish on the same client: NOP
+        mDut.publish(clientId, publishConfig, mockSessionCallback);
         mMockLooper.dispatchAll();
 
-        verifyZeroInteractions(mockListener, mockSessionListener);
+        // (6) got some callback on original publishId - should be ignored
+        mDut.onSessionTerminatedNotification(publishId, reason, true);
+        mMockLooper.dispatchAll();
+
+        verifyNoMoreInteractions(mMockNative, mockCallback, mockSessionCallback);
     }
 
     /**
-     * Summary: destroy a session while there are pending transactions. Validate
-     * that no callbacks are called and that internal state is cleaned-up.
+     * Validate that an unknown transaction (i.e. a callback from HAL with an
+     * unknown type) is simply ignored - but also cleans up its state.
      */
     @Test
-    public void testDestroySessionWithPendingTransactions() throws Exception {
-        final int uid = 128;
+    public void testUnknownTransactionType() throws Exception {
+        final int clientId = 129;
+        final int uid = 1000;
         final int clusterLow = 15;
         final int clusterHigh = 192;
         final int masterPref = 234;
-        final int publishSessionId = 19;
-        final int subscribeSessionId = 24;
         final String serviceName = "some-service-name";
         final String ssi = "some much longer and more arbitrary data";
         final int publishCount = 15;
-        final int subscribeCount = 22;
-        final int reason = WifiNanSessionListener.TERMINATE_REASON_DONE;
-        final int publishId = 23;
-        final int subscribeId = 55;
 
         ConfigRequest configRequest = new ConfigRequest.Builder().setClusterLow(clusterLow)
                 .setClusterHigh(clusterHigh).setMasterPreference(masterPref).build();
 
-        PublishData publishData = new PublishData.Builder().setServiceName(serviceName)
-                .setServiceSpecificInfo(ssi).build();
-
-        PublishSettings publishSettings = new PublishSettings.Builder()
-                .setPublishType(PublishSettings.PUBLISH_TYPE_UNSOLICITED)
+        PublishConfig publishConfig = new PublishConfig.Builder().setServiceName(serviceName)
+                .setServiceSpecificInfo(ssi).setPublishType(PublishConfig.PUBLISH_TYPE_UNSOLICITED)
                 .setPublishCount(publishCount).build();
 
-        SubscribeData subscribeData = new SubscribeData.Builder().setServiceName(serviceName)
-                .setServiceSpecificInfo(ssi).build();
-
-        SubscribeSettings subscribeSettings = new SubscribeSettings.Builder()
-                .setSubscribeType(SubscribeSettings.SUBSCRIBE_TYPE_PASSIVE)
-                .setSubscribeCount(subscribeCount).build();
-
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
-        IWifiNanEventListener mockListener = mock(IWifiNanEventListener.class);
-        IWifiNanSessionListener mockPublishSessionListener = mock(IWifiNanSessionListener.class);
-        IWifiNanSessionListener mockSubscribeSessionListener = mock(IWifiNanSessionListener.class);
-        InOrder inOrder = inOrder(mMockNative, mockListener, mockPublishSessionListener,
-                mockSubscribeSessionListener);
+        IWifiNanEventCallback mockCallback = mock(IWifiNanEventCallback.class);
+        IWifiNanSessionCallback mockPublishSessionCallback = mock(IWifiNanSessionCallback.class);
+        InOrder inOrder = inOrder(mMockNative, mockCallback, mockPublishSessionCallback);
 
-        int allEvents = WifiNanEventListener.LISTEN_CONFIG_COMPLETED
-                | WifiNanEventListener.LISTEN_CONFIG_FAILED
-                | WifiNanEventListener.LISTEN_IDENTITY_CHANGED
-                | WifiNanEventListener.LISTEN_NAN_DOWN;
-
-        int allSessionEvents = WifiNanSessionListener.LISTEN_PUBLISH_FAIL
-                | WifiNanSessionListener.LISTEN_PUBLISH_TERMINATED
-                | WifiNanSessionListener.LISTEN_SUBSCRIBE_FAIL
-                | WifiNanSessionListener.LISTEN_SUBSCRIBE_TERMINATED
-                | WifiNanSessionListener.LISTEN_MATCH
-                | WifiNanSessionListener.LISTEN_MESSAGE_SEND_SUCCESS
-                | WifiNanSessionListener.LISTEN_MESSAGE_SEND_FAIL
-                | WifiNanSessionListener.LISTEN_MESSAGE_RECEIVED;
-
-        mDut.connect(uid, mockListener, allEvents);
-        mDut.requestConfig(uid, configRequest);
-        mDut.createSession(uid, publishSessionId, mockPublishSessionListener, allSessionEvents);
-        mDut.publish(uid, publishSessionId, publishData, publishSettings);
-        mDut.createSession(uid, subscribeSessionId, mockSubscribeSessionListener, allSessionEvents);
-        mDut.subscribe(uid, subscribeSessionId, subscribeData, subscribeSettings);
-        mDut.destroySession(uid, publishSessionId);
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
         mMockLooper.dispatchAll();
 
-        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest));
-        short transactionIdConfig = transactionId.getValue();
-
-        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishData),
-                eq(publishSettings));
-        short transactionIdPublish = transactionId.getValue();
-
-        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeData),
-                eq(subscribeSettings));
-        short transactionIdSubscribe = transactionId.getValue();
-
-        validateInternalTransactionInfoCleanedUp(transactionIdPublish);
-
-        mDut.onConfigCompleted(transactionIdConfig);
-        mDut.onPublishSuccess(transactionIdPublish, publishId);
-        mDut.onSubscribeSuccess(transactionIdSubscribe, subscribeId);
+        // (1) connect
+        mDut.connect(clientId, uid, mockCallback, configRequest);
         mMockLooper.dispatchAll();
-
-        mDut.onPublishTerminated(publishId, reason);
-        mDut.destroySession(uid, subscribeSessionId);
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
         mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
 
-        inOrder.verify(mockListener).onConfigCompleted(configRequest);
-        verifyZeroInteractions(mockPublishSessionListener);
-        verifyNoMoreInteractions(mockSubscribeSessionListener);
+        // (2) publish - no response
+        mDut.publish(clientId, publishConfig, mockPublishSessionCallback);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishConfig));
+
+        verifyNoMoreInteractions(mMockNative, mockCallback, mockPublishSessionCallback);
     }
 
+    /**
+     * Validate that a NoOp transaction (i.e. a callback from HAL which doesn't
+     * require any action except clearing up state) actually cleans up its state
+     * (and does nothing else).
+     */
     @Test
-    public void testTransactionIdIncrement() {
+    public void testNoOpTransaction() throws Exception {
+        final int clientId = 1294;
+        final int uid = 1000;
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        IWifiNanEventCallback mockCallback = mock(IWifiNanEventCallback.class);
+        IWifiNanSessionCallback mockSessionCallback = mock(IWifiNanSessionCallback.class);
+        InOrder inOrder = inOrder(mMockNative, mockCallback, mockSessionCallback);
+
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
+        mMockLooper.dispatchAll();
+
+        // (1) connect (no response)
+        mDut.connect(clientId, uid, mockCallback, configRequest);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(true));
+
+        verifyNoMoreInteractions(mMockNative, mockCallback, mockSessionCallback);
+    }
+
+    /**
+     * Validate that getting callbacks from HAL with unknown (expired)
+     * transaction ID or invalid publish/subscribe ID session doesn't have any
+     * impact.
+     */
+    @Test
+    public void testInvalidCallbackIdParameters() throws Exception {
+        final int pubSubId = 1235;
+        final int clientId = 132;
+        final int uid = 1000;
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        IWifiNanEventCallback mockCallback = mock(IWifiNanEventCallback.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 and succeed
+        mDut.connect(clientId, uid, mockCallback, configRequest);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(true));
+        short transactionIdConfig = transactionId.getValue();
+        mDut.onConfigSuccessResponse(transactionIdConfig);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
+
+        // (2) use the same transaction ID to send a bunch of other responses
+        mDut.onConfigSuccessResponse(transactionIdConfig);
+        mDut.onConfigFailedResponse(transactionIdConfig, -1);
+        mDut.onSessionConfigFailResponse(transactionIdConfig, true, -1);
+        mDut.onMessageSendQueuedSuccessResponse(transactionIdConfig);
+        mDut.onMessageSendQueuedFailResponse(transactionIdConfig, -1);
+        mDut.onSessionConfigFailResponse(transactionIdConfig, false, -1);
+        mDut.onMatchNotification(-1, -1, new byte[0], new byte[0], 0, new byte[0], 0);
+        mDut.onSessionTerminatedNotification(-1, -1, true);
+        mDut.onSessionTerminatedNotification(-1, -1, false);
+        mDut.onMessageReceivedNotification(-1, -1, new byte[0], new byte[0], 0);
+        mDut.onSessionConfigSuccessResponse(transactionIdConfig, true, pubSubId);
+        mDut.onSessionConfigSuccessResponse(transactionIdConfig, false, pubSubId);
+        mMockLooper.dispatchAll();
+
+        verifyNoMoreInteractions(mMockNative, mockCallback);
+    }
+
+    /**
+     * Validate that trying to update-subscribe on a publish session fails.
+     */
+    @Test
+    public void testSubscribeOnPublishSessionType() throws Exception {
+        final int clientId = 188;
+        final int uid = 1000;
+        final int publishId = 25;
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        PublishConfig publishConfig = new PublishConfig.Builder().build();
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
+
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
+        IWifiNanEventCallback mockCallback = mock(IWifiNanEventCallback.class);
+        IWifiNanSessionCallback mockSessionCallback = mock(IWifiNanSessionCallback.class);
+        InOrder inOrder = inOrder(mMockNative, mockCallback, mockSessionCallback);
+
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
+        mMockLooper.dispatchAll();
+
+        // (1) connect
+        mDut.connect(clientId, uid, mockCallback, configRequest);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
+
+        // (2) publish
+        mDut.publish(clientId, publishConfig, mockSessionCallback);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishConfig));
+        mDut.onSessionConfigSuccessResponse(transactionId.getValue(), true, publishId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
+
+        // (3) update-subscribe -> failure
+        mDut.updateSubscribe(clientId, sessionId.getValue(), subscribeConfig);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback)
+                .onSessionConfigFail(WifiNanSessionCallback.REASON_OTHER);
+
+        verifyNoMoreInteractions(mMockNative, mockCallback, mockSessionCallback);
+    }
+
+    /**
+     * Validate that trying to (re)subscribe on a publish session or (re)publish
+     * on a subscribe session fails.
+     */
+    @Test
+    public void testPublishOnSubscribeSessionType() throws Exception {
+        final int clientId = 188;
+        final int uid = 1000;
+        final int subscribeId = 25;
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        PublishConfig publishConfig = new PublishConfig.Builder().build();
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
+
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
+        IWifiNanEventCallback mockCallback = mock(IWifiNanEventCallback.class);
+        IWifiNanSessionCallback mockSessionCallback = mock(IWifiNanSessionCallback.class);
+        InOrder inOrder = inOrder(mMockNative, mockCallback, mockSessionCallback);
+
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
+        mMockLooper.dispatchAll();
+
+        // (1) connect
+        mDut.connect(clientId, uid, mockCallback, configRequest);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
+                eq(configRequest), eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
+
+        // (2) subscribe
+        mDut.subscribe(clientId, subscribeConfig, mockSessionCallback);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeConfig));
+        mDut.onSessionConfigSuccessResponse(transactionId.getValue(), false, subscribeId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
+
+        // (3) update-publish -> error
+        mDut.updatePublish(clientId, sessionId.getValue(), publishConfig);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback)
+                .onSessionConfigFail(WifiNanSessionCallback.REASON_OTHER);
+
+        verifyNoMoreInteractions(mMockNative, mockCallback, mockSessionCallback);
+    }
+
+    /**
+     * Validate that the session ID increments monotonically
+     */
+    @Test
+    public void testSessionIdIncrement() throws Exception {
+        final int clientId = 188;
+        final int uid = 1000;
         int loopCount = 100;
 
-        short prevId = 0;
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        PublishConfig publishConfig = new PublishConfig.Builder().build();
+
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
+        IWifiNanEventCallback mockCallback = mock(IWifiNanEventCallback.class);
+        IWifiNanSessionCallback mockSessionCallback = mock(IWifiNanSessionCallback.class);
+        InOrder inOrder = inOrder(mMockNative, mockCallback, mockSessionCallback);
+
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
+        mMockLooper.dispatchAll();
+
+        // (1) connect
+        mDut.connect(clientId, uid, mockCallback, configRequest);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
+                eq(configRequest), eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
+
+        int prevId = 0;
         for (int i = 0; i < loopCount; ++i) {
-            short id = mDut.createNextTransactionId();
+            // (2) publish
+            mDut.publish(clientId, publishConfig, mockSessionCallback);
+            mMockLooper.dispatchAll();
+            inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishConfig));
+
+            // (3) publish-success
+            mDut.onSessionConfigSuccessResponse(transactionId.getValue(), true, i + 1);
+            mMockLooper.dispatchAll();
+            inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
+
             if (i != 0) {
-                assertTrue("Transaction ID incrementing", id > prevId);
+                assertTrue("Session ID incrementing", sessionId.getValue() > prevId);
             }
-            prevId = id;
+            prevId = sessionId.getValue();
         }
     }
 
@@ -1079,34 +2230,83 @@
 
     /**
      * Utility routine used to validate that the internal state is cleaned-up
-     * after the specific transaction ID. To be used in every test which
-     * involves a transaction.
+     * after a client is disconnected. To be used in every test which terminates
+     * a client.
      *
-     * @param transactionId The transaction ID whose state should be erased.
+     * @param clientId The ID of the client which should be deleted.
      */
-    public void validateInternalTransactionInfoCleanedUp(short transactionId) throws Exception {
-        Object info = getInternalPendingTransactionInfo(mDut, transactionId);
-        collector.checkThat("Transaction record not cleared up for transactionId=" + transactionId,
-                info, nullValue());
+    private void validateInternalClientInfoCleanedUp(int clientId) throws Exception {
+        WifiNanClientState client = getInternalClientState(mDut, clientId);
+        collector.checkThat("Client record not cleared up for clientId=" + clientId, client,
+                nullValue());
     }
 
     /**
      * Utility routine used to validate that the internal state is cleaned-up
-     * after a client is disconnected. To be used in every test which terminates
-     * a client.
+     * (deleted) after a session is terminated through API (not callback!). To
+     * be used in every test which terminates a session.
      *
-     * @param uid The ID of the client which should be deleted.
+     * @param clientId The ID of the client containing the session.
+     * @param sessionId The ID of the terminated session.
      */
-    public void validateInternalClientInfoCleanedUp(int uid) throws Exception {
-        WifiNanClientState client = getInternalClientState(mDut, uid);
-        collector.checkThat("Client record not cleared up for uid=" + uid, client, nullValue());
+    private void validateInternalSessionInfoCleanedUp(int clientId, int sessionId)
+            throws Exception {
+        WifiNanClientState client = getInternalClientState(mDut, clientId);
+        collector.checkThat("Client record exists clientId=" + clientId, client, notNullValue());
+        WifiNanSessionState session = getInternalSessionState(client, sessionId);
+        collector.checkThat("Client record not cleaned-up for sessionId=" + sessionId, session,
+                nullValue());
+    }
+
+    /**
+     * Utility routine used to validate that the internal state is cleaned-up
+     * (deleted) correctly. Checks that a specific client has no sessions
+     * attached to it.
+     *
+     * @param clientId The ID of the client which we want to check.
+     */
+    private void validateInternalNoSessions(int clientId) throws Exception {
+        WifiNanClientState client = getInternalClientState(mDut, clientId);
+        collector.checkThat("Client record exists clientId=" + clientId, client, notNullValue());
+
+        Field field = WifiNanClientState.class.getDeclaredField("mSessions");
+        field.setAccessible(true);
+        @SuppressWarnings("unchecked")
+        SparseArray<WifiNanSessionState> sessions = (SparseArray<WifiNanSessionState>) field
+                .get(client);
+
+        collector.checkThat("No sessions exist for clientId=" + clientId, sessions.size(),
+                equalTo(0));
+    }
+
+    /**
+     * Validates that the broadcast sent on NAN status change is correct.
+     *
+     * @param expectedEnabled The expected change status - i.e. are we expected
+     *            to announce that NAN is enabled (true) or disabled (false).
+     */
+    private void validateCorrectNanStatusChangeBroadcast(InOrder inOrder, boolean expectedEnabled) {
+        ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
+
+        inOrder.verify(mMockContext).sendBroadcastAsUser(intent.capture(), eq(UserHandle.ALL));
+
+        collector.checkThat("intent action", intent.getValue().getAction(),
+                equalTo(WifiNanManager.WIFI_NAN_STATE_CHANGED_ACTION));
+        collector.checkThat("intent contains wifi status key",
+                intent.getValue().getExtras().containsKey(WifiNanManager.EXTRA_WIFI_STATE),
+                equalTo(true));
+        collector.checkThat("intnent wifi status key value",
+                intent.getValue().getExtras().getInt(WifiNanManager.EXTRA_WIFI_STATE),
+                equalTo(expectedEnabled ? WifiNanManager.WIFI_NAN_STATE_ENABLED
+                        : WifiNanManager.WIFI_NAN_STATE_DISABLED));
     }
 
     /*
      * Utilities
      */
 
-    private static WifiNanStateManager installNewNanStateManagerAndResetState() throws Exception {
+    private static WifiNanStateManager installNewNanStateManager()
+            throws Exception {
         Constructor<WifiNanStateManager> ctr = WifiNanStateManager.class.getDeclaredConstructor();
         ctr.setAccessible(true);
         WifiNanStateManager nanStateManager = ctr.newInstance();
@@ -1118,30 +2318,95 @@
         return WifiNanStateManager.getInstance();
     }
 
+    private static void installMocksInStateManager(WifiNanStateManager nanStateManager,
+            WifiNanRttStateManager mockRtt, WifiNanDataPathStateManager mockDpMgr)
+            throws Exception {
+        Field field = WifiNanStateManager.class.getDeclaredField("mRtt");
+        field.setAccessible(true);
+        field.set(nanStateManager, mockRtt);
+
+        field = WifiNanStateManager.class.getDeclaredField("mDataPathMgr");
+        field.setAccessible(true);
+        field.set(nanStateManager, mockDpMgr);
+    }
+
     private static void installMockWifiNanNative(WifiNanNative obj) throws Exception {
         Field field = WifiNanNative.class.getDeclaredField("sWifiNanNativeSingleton");
         field.setAccessible(true);
         field.set(null, obj);
     }
 
-    private static Object getInternalPendingTransactionInfo(WifiNanStateManager dut,
-            short transactionId) throws Exception {
-        Field field = WifiNanStateManager.class.getDeclaredField("mPendingResponses");
-        field.setAccessible(true);
-        @SuppressWarnings("unchecked")
-        SparseArray<Object> pendingResponses = (SparseArray<Object>) field.get(dut);
-
-        return pendingResponses.get(transactionId);
-    }
-
-    private static WifiNanClientState getInternalClientState(WifiNanStateManager dut,
-            int uid) throws Exception {
+    private static WifiNanClientState getInternalClientState(WifiNanStateManager dut, int clientId)
+            throws Exception {
         Field field = WifiNanStateManager.class.getDeclaredField("mClients");
         field.setAccessible(true);
         @SuppressWarnings("unchecked")
         SparseArray<WifiNanClientState> clients = (SparseArray<WifiNanClientState>) field.get(dut);
 
-        return clients.get(uid);
+        return clients.get(clientId);
+    }
+
+    private static WifiNanSessionState getInternalSessionState(WifiNanClientState client,
+            int sessionId) throws Exception {
+        Field field = WifiNanClientState.class.getDeclaredField("mSessions");
+        field.setAccessible(true);
+        @SuppressWarnings("unchecked")
+        SparseArray<WifiNanSessionState> sessions = (SparseArray<WifiNanSessionState>) field
+                .get(client);
+
+        return sessions.get(sessionId);
+    }
+
+    private void validateInternalSendMessageQueuesCleanedUp(int messageId) throws Exception {
+        Field field = WifiNanStateManager.class.getDeclaredField("mSm");
+        field.setAccessible(true);
+        WifiNanStateManager.WifiNanStateMachine sm =
+                (WifiNanStateManager.WifiNanStateMachine) field.get(mDut);
+
+        field = WifiNanStateManager.WifiNanStateMachine.class.getDeclaredField(
+                "mHostQueuedSendMessages");
+        field.setAccessible(true);
+        SparseArray<Message> hostQueuedSendMessages = (SparseArray<Message>) field.get(sm);
+
+        field = WifiNanStateManager.WifiNanStateMachine.class.getDeclaredField(
+                "mFwQueuedSendMessages");
+        field.setAccessible(true);
+        Map<Short, Message> fwQueuedSendMessages = (Map<Short, Message>) field.get(sm);
+
+        for (int i = 0; i < hostQueuedSendMessages.size(); ++i) {
+            Message msg = hostQueuedSendMessages.valueAt(i);
+            if (msg.getData().getInt("message_id") == messageId) {
+                collector.checkThat(
+                        "Message not cleared-up from host queue. Message ID=" + messageId, msg,
+                        nullValue());
+            }
+        }
+
+        for (Message msg: fwQueuedSendMessages.values()) {
+            if (msg.getData().getInt("message_id") == messageId) {
+                collector.checkThat(
+                        "Message not cleared-up from firmware queue. Message ID=" + messageId, msg,
+                        nullValue());
+            }
+        }
+    }
+
+    private static WifiNanNative.Capabilities getCapabilities() {
+        WifiNanNative.Capabilities cap = new WifiNanNative.Capabilities();
+        cap.maxConcurrentNanClusters = 1;
+        cap.maxPublishes = 2;
+        cap.maxSubscribes = 2;
+        cap.maxServiceNameLen = 255;
+        cap.maxMatchFilterLen = 255;
+        cap.maxTotalMatchFilterLen = 255;
+        cap.maxServiceSpecificInfoLen = 255;
+        cap.maxVsaDataLen = 255;
+        cap.maxMeshDataLen = 255;
+        cap.maxNdiInterfaces = 1;
+        cap.maxNdpSessions = 1;
+        cap.maxAppInfoLen = 255;
+        cap.maxQueuedTransmitMessages = 6;
+        return cap;
     }
 }
 
diff --git a/tests/wifitests/src/com/android/server/wifi/scanner/BaseWifiScannerImplTest.java b/tests/wifitests/src/com/android/server/wifi/scanner/BaseWifiScannerImplTest.java
index 1355961..cd3909e 100644
--- a/tests/wifitests/src/com/android/server/wifi/scanner/BaseWifiScannerImplTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/scanner/BaseWifiScannerImplTest.java
@@ -23,16 +23,16 @@
 import static org.junit.Assert.*;
 import static org.mockito.Mockito.*;
 
+import android.app.test.TestAlarmManager;
 import android.content.Context;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiScanner;
 import android.net.wifi.WifiScanner.ScanData;
 import android.net.wifi.WifiSsid;
 import android.os.SystemClock;
+import android.os.test.TestLooper;
 
 import com.android.server.wifi.Clock;
-import com.android.server.wifi.MockAlarmManager;
-import com.android.server.wifi.MockLooper;
 import com.android.server.wifi.MockResources;
 import com.android.server.wifi.MockWifiMonitor;
 import com.android.server.wifi.ScanDetail;
@@ -59,9 +59,9 @@
  */
 public abstract class BaseWifiScannerImplTest {
     @Mock Context mContext;
-    MockAlarmManager mAlarmManager;
+    TestAlarmManager mAlarmManager;
     MockWifiMonitor mWifiMonitor;
-    MockLooper mLooper;
+    TestLooper mLooper;
     @Mock WifiNative mWifiNative;
     MockResources mResources;
     @Mock Clock mClock;
@@ -75,8 +75,8 @@
     public void setUpBase() throws Exception {
         MockitoAnnotations.initMocks(this);
 
-        mLooper = new MockLooper();
-        mAlarmManager = new MockAlarmManager();
+        mLooper = new TestLooper();
+        mAlarmManager = new TestAlarmManager();
         mWifiMonitor = new MockWifiMonitor();
         mResources = new MockResources();
 
@@ -86,7 +86,7 @@
                 .thenReturn(mAlarmManager.getAlarmManager());
 
         when(mContext.getResources()).thenReturn(mResources);
-        when(mClock.elapsedRealtime()).thenReturn(SystemClock.elapsedRealtime());
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime());
     }
 
     protected Set<Integer> expectedBandScanFreqs(int band) {
@@ -389,7 +389,7 @@
                         WifiScanner.WIFI_BAND_24_GHZ)
                 .build();
 
-        long approxScanStartUs = mClock.elapsedRealtime() * 1000;
+        long approxScanStartUs = mClock.getElapsedSinceBootMillis() * 1000;
         ArrayList<ScanDetail> rawResults = new ArrayList<>(Arrays.asList(
                         new ScanDetail(WifiSsid.createFromAsciiEncoded("TEST AP 1"),
                                 "00:00:00:00:00:00", "", -70, 2450,
diff --git a/tests/wifitests/src/com/android/server/wifi/scanner/SupplicantPnoScannerTest.java b/tests/wifitests/src/com/android/server/wifi/scanner/SupplicantPnoScannerTest.java
index 39709f8..35e7ce3 100644
--- a/tests/wifitests/src/com/android/server/wifi/scanner/SupplicantPnoScannerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/scanner/SupplicantPnoScannerTest.java
@@ -22,16 +22,17 @@
 import static org.junit.Assert.*;
 import static org.mockito.Mockito.*;
 
+import android.app.test.TestAlarmManager;
 import android.content.Context;
+import android.os.test.TestLooper;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiScanner;
 import android.os.SystemClock;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.internal.R;
+
 import com.android.server.wifi.Clock;
-import com.android.server.wifi.MockAlarmManager;
-import com.android.server.wifi.MockLooper;
 import com.android.server.wifi.MockResources;
 import com.android.server.wifi.MockWifiMonitor;
 import com.android.server.wifi.ScanResults;
@@ -56,9 +57,9 @@
 public class SupplicantPnoScannerTest {
 
     @Mock Context mContext;
-    MockAlarmManager mAlarmManager;
+    TestAlarmManager mAlarmManager;
     MockWifiMonitor mWifiMonitor;
-    MockLooper mLooper;
+    TestLooper mLooper;
     @Mock WifiNative mWifiNative;
     MockResources mResources;
     @Mock Clock mClock;
@@ -68,8 +69,8 @@
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
 
-        mLooper = new MockLooper();
-        mAlarmManager = new MockAlarmManager();
+        mLooper = new TestLooper();
+        mAlarmManager = new TestAlarmManager();
         mWifiMonitor = new MockWifiMonitor();
         mResources = new MockResources();
 
@@ -77,7 +78,7 @@
         when(mContext.getSystemService(Context.ALARM_SERVICE))
                 .thenReturn(mAlarmManager.getAlarmManager());
         when(mContext.getResources()).thenReturn(mResources);
-        when(mClock.elapsedRealtime()).thenReturn(SystemClock.elapsedRealtime());
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime());
     }
 
     /**
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 03a11dc..82d9e8d 100644
--- a/tests/wifitests/src/com/android/server/wifi/scanner/WifiScanningServiceTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/scanner/WifiScanningServiceTest.java
@@ -22,6 +22,8 @@
 import static org.mockito.Matchers.*;
 import static org.mockito.Mockito.*;
 
+import android.app.test.MockAnswerUtil.AnswerWithArguments;
+import android.app.test.TestAlarmManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.IntentFilter;
@@ -35,17 +37,15 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.WorkSource;
+import android.os.test.TestLooper;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Pair;
 
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.util.AsyncChannel;
 import com.android.internal.util.Protocol;
-import com.android.server.wifi.BidirectionalAsyncChannel;
+import com.android.internal.util.test.BidirectionalAsyncChannel;
 import com.android.server.wifi.Clock;
-import com.android.server.wifi.MockAlarmManager;
-import com.android.server.wifi.MockAnswerUtil.AnswerWithArguments;
-import com.android.server.wifi.MockLooper;
 import com.android.server.wifi.ScanResults;
 import com.android.server.wifi.TestUtil;
 import com.android.server.wifi.WifiInjector;
@@ -78,14 +78,14 @@
     public static final String TAG = "WifiScanningServiceTest";
 
     @Mock Context mContext;
-    MockAlarmManager mAlarmManager;
+    TestAlarmManager mAlarmManager;
     @Mock WifiScannerImpl mWifiScannerImpl;
     @Mock WifiScannerImpl.WifiScannerImplFactory mWifiScannerImplFactory;
     @Mock IBatteryStats mBatteryStats;
     @Mock WifiInjector mWifiInjector;
     @Mock Clock mClock;
     WifiMetrics mWifiMetrics;
-    MockLooper mLooper;
+    TestLooper mLooper;
     WifiScanningServiceImpl mWifiScanningServiceImpl;
 
 
@@ -93,7 +93,7 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
-        mAlarmManager = new MockAlarmManager();
+        mAlarmManager = new TestAlarmManager();
         when(mContext.getSystemService(Context.ALARM_SERVICE))
                 .thenReturn(mAlarmManager.getAlarmManager());
         mWifiMetrics = new WifiMetrics(mClock);
@@ -103,7 +103,7 @@
                 new int[]{5150, 5175},
                 new int[]{5600, 5650, 5660});
 
-        mLooper = new MockLooper();
+        mLooper = new TestLooper();
         when(mWifiScannerImplFactory
                 .create(any(Context.class), any(Looper.class), any(Clock.class)))
                 .thenReturn(mWifiScannerImpl);
diff --git a/tests/wifitests/src/com/android/server/wifi/ByteArrayRingBufferTest.java b/tests/wifitests/src/com/android/server/wifi/util/ByteArrayRingBufferTest.java
similarity index 100%
rename from tests/wifitests/src/com/android/server/wifi/ByteArrayRingBufferTest.java
rename to tests/wifitests/src/com/android/server/wifi/util/ByteArrayRingBufferTest.java
diff --git a/tests/wifitests/src/com/android/server/wifi/util/ScanDetailUtilTest.java b/tests/wifitests/src/com/android/server/wifi/util/ScanResultUtilTest.java
similarity index 93%
rename from tests/wifitests/src/com/android/server/wifi/util/ScanDetailUtilTest.java
rename to tests/wifitests/src/com/android/server/wifi/util/ScanResultUtilTest.java
index 07d2521..275a40b 100644
--- a/tests/wifitests/src/com/android/server/wifi/util/ScanDetailUtilTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/util/ScanResultUtilTest.java
@@ -33,10 +33,10 @@
 import java.util.Arrays;
 
 /**
- * Unit tests for {@link com.android.server.wifi.util.ScanDetailUtil}.
+ * Unit tests for {@link com.android.server.wifi.util.ScanResultUtil}.
  */
 @SmallTest
-public class ScanDetailUtilTest {
+public class ScanResultUtilTest {
 
     @Test
     public void convertScanResult() {
@@ -49,7 +49,7 @@
             createIE(InformationElement.EID_SSID, ssid.getBytes(StandardCharsets.UTF_8))
         };
 
-        ScanDetail output = ScanDetailUtil.toScanDetail(input);
+        ScanDetail output = ScanResultUtil.toScanDetail(input);
 
         validateScanDetail(input, output);
     }
@@ -66,7 +66,7 @@
         };
         input.anqpLines = Arrays.asList("LINE 1", "line 2", "Line 3");
 
-        ScanDetail output = ScanDetailUtil.toScanDetail(input);
+        ScanDetail output = ScanResultUtil.toScanDetail(input);
 
         validateScanDetail(input, output);
     }
@@ -80,7 +80,7 @@
             createIE(InformationElement.EID_SSID, ssid.getBytes(StandardCharsets.UTF_8))
         };
 
-        ScanDetail output = ScanDetailUtil.toScanDetail(input);
+        ScanDetail output = ScanResultUtil.toScanDetail(input);
 
         validateScanDetail(input, output);
     }
diff --git a/tests/wifitests/src/com/android/server/wifi/util/XmlUtilTest.java b/tests/wifitests/src/com/android/server/wifi/util/XmlUtilTest.java
new file mode 100644
index 0000000..7b06164
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/util/XmlUtilTest.java
@@ -0,0 +1,402 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.*;
+
+import android.net.IpConfiguration;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
+import android.net.wifi.WifiEnterpriseConfig;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Pair;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+import com.android.server.wifi.WifiConfigurationTestUtil;
+import com.android.server.wifi.util.XmlUtil.IpConfigurationXmlUtil;
+import com.android.server.wifi.util.XmlUtil.NetworkSelectionStatusXmlUtil;
+import com.android.server.wifi.util.XmlUtil.WifiConfigurationXmlUtil;
+import com.android.server.wifi.util.XmlUtil.WifiEnterpriseConfigXmlUtil;
+
+import org.junit.Test;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.util.XmlUtil}.
+ */
+@SmallTest
+public class XmlUtilTest {
+
+    private static final String TEST_PACKAGE_NAME = "XmlUtilPackage";
+    private static final String TEST_STATIC_IP_GATEWAY_ADDRESS = "192.168.48.1";
+    private static final String TEST_DUMMY_CONFIG_KEY = "XmlUtilDummyConfigKey";
+    private static final String TEST_IDENTITY = "XmlUtilTestIdentity";
+    private static final String TEST_ANON_IDENTITY = "XmlUtilTestAnonIdentity";
+    private static final String TEST_PASSWORD = "XmlUtilTestPassword";
+    private static final String TEST_CLIENT_CERT = "XmlUtilTestClientCert";
+    private static final String TEST_CA_CERT = "XmlUtilTestCaCert";
+    private static final String TEST_SUBJECT_MATCH = "XmlUtilTestSubjectMatch";
+    private static final String TEST_ENGINE = "XmlUtilTestEngine";
+    private static final String TEST_ENGINE_ID = "XmlUtilTestEngineId";
+    private static final String TEST_PRIVATE_KEY_ID = "XmlUtilTestPrivateKeyId";
+    private static final String TEST_ALTSUBJECT_MATCH = "XmlUtilTestAltSubjectMatch";
+    private static final String TEST_DOM_SUFFIX_MATCH = "XmlUtilTestDomSuffixMatch";
+    private static final String TEST_CA_PATH = "XmlUtilTestCaPath";
+    private static final int TEST_EAP_METHOD = WifiEnterpriseConfig.Eap.PEAP;
+    private static final int TEST_PHASE2_METHOD = WifiEnterpriseConfig.Phase2.MSCHAPV2;
+    private final String mXmlDocHeader = "XmlUtilTest";
+
+    /**
+     * Verify that a open WifiConfiguration is serialized & deserialized correctly.
+     */
+    @Test
+    public void testOpenWifiConfigurationSerializeDeserialize()
+            throws IOException, XmlPullParserException {
+        serializeDeserializeWifiConfiguration(WifiConfigurationTestUtil.createOpenNetwork());
+    }
+
+    /**
+     * Verify that a open hidden WifiConfiguration is serialized & deserialized correctly.
+     */
+    @Test
+    public void testOpenHiddenWifiConfigurationSerializeDeserialize()
+            throws IOException, XmlPullParserException {
+        serializeDeserializeWifiConfiguration(WifiConfigurationTestUtil.createOpenHiddenNetwork());
+    }
+
+    /**
+     * Verify that a psk WifiConfiguration is serialized & deserialized correctly.
+     */
+    @Test
+    public void testPskWifiConfigurationSerializeDeserialize()
+            throws IOException, XmlPullParserException {
+        serializeDeserializeWifiConfiguration(WifiConfigurationTestUtil.createPskNetwork());
+    }
+
+    /**
+     * Verify that a psk hidden WifiConfiguration is serialized & deserialized correctly.
+     */
+    @Test
+    public void testPskHiddenWifiConfigurationSerializeDeserialize()
+            throws IOException, XmlPullParserException {
+        serializeDeserializeWifiConfiguration(WifiConfigurationTestUtil.createPskHiddenNetwork());
+    }
+
+    /**
+     * Verify that a WEP WifiConfiguration is serialized & deserialized correctly.
+     */
+    @Test
+    public void testWepWifiConfigurationSerializeDeserialize()
+            throws IOException, XmlPullParserException {
+        serializeDeserializeWifiConfiguration(WifiConfigurationTestUtil.createWepNetwork());
+    }
+
+    /**
+     * Verify that a EAP WifiConfiguration is serialized & deserialized correctly only for
+     * ConfigStore.
+     */
+    @Test
+    public void testEapWifiConfigurationSerializeDeserialize()
+            throws IOException, XmlPullParserException {
+        serializeDeserializeWifiConfigurationForConfigStore(
+                WifiConfigurationTestUtil.createEapNetwork());
+    }
+
+    /**
+     * Verify that a static IpConfiguration with PAC proxy is serialized & deserialized correctly.
+     */
+    @Test
+    public void testStaticIpConfigurationWithPacProxySerializeDeserialize()
+            throws IOException, XmlPullParserException {
+        serializeDeserializeIpConfiguration(
+                WifiConfigurationTestUtil.createStaticIpConfigurationWithPacProxy());
+    }
+
+    /**
+     * Verify that a static IpConfiguration with static proxy is serialized & deserialized correctly.
+     */
+    @Test
+    public void testStaticIpConfigurationWithStaticProxySerializeDeserialize()
+            throws IOException, XmlPullParserException {
+        serializeDeserializeIpConfiguration(
+                WifiConfigurationTestUtil.createStaticIpConfigurationWithStaticProxy());
+    }
+
+    /**
+     * Verify that a partial static IpConfiguration with PAC proxy is serialized & deserialized
+     * correctly.
+     */
+    @Test
+    public void testPartialStaticIpConfigurationWithPacProxySerializeDeserialize()
+            throws IOException, XmlPullParserException {
+        serializeDeserializeIpConfiguration(
+                WifiConfigurationTestUtil.createPartialStaticIpConfigurationWithPacProxy());
+    }
+
+    /**
+     * Verify that a DHCP IpConfiguration with PAC proxy is serialized & deserialized
+     * correctly.
+     */
+    @Test
+    public void testDHCPIpConfigurationWithPacProxySerializeDeserialize()
+            throws IOException, XmlPullParserException {
+        serializeDeserializeIpConfiguration(
+                WifiConfigurationTestUtil.createDHCPIpConfigurationWithPacProxy());
+    }
+
+    /**
+     * Verify that a DHCP IpConfiguration with Static proxy is serialized & deserialized
+     * correctly.
+     */
+    @Test
+    public void testDHCPIpConfigurationWithStaticProxySerializeDeserialize()
+            throws IOException, XmlPullParserException {
+        serializeDeserializeIpConfiguration(
+                WifiConfigurationTestUtil.createDHCPIpConfigurationWithStaticProxy());
+    }
+
+    /**
+     * Verify that a EAP WifiConfiguration is serialized & deserialized correctly for config store.
+     * This basically exercises all the elements being serialized in config store.
+     */
+    @Test
+    public void testEapWifiConfigurationSerializeDeserializeForConfigStore()
+            throws IOException, XmlPullParserException {
+        WifiConfiguration configuration = WifiConfigurationTestUtil.createEapNetwork();
+        configuration.linkedConfigurations = new HashMap<>();
+        configuration.linkedConfigurations.put(TEST_DUMMY_CONFIG_KEY, Integer.valueOf(1));
+        configuration.defaultGwMacAddress = TEST_STATIC_IP_GATEWAY_ADDRESS;
+        configuration.validatedInternetAccess = true;
+        configuration.noInternetAccessExpected = true;
+        configuration.userApproved = WifiConfiguration.USER_UNSPECIFIED;
+        configuration.meteredHint = true;
+        configuration.useExternalScores = true;
+        configuration.numAssociation = 5;
+        configuration.lastUpdateUid = configuration.lastConnectUid = configuration.creatorUid;
+        configuration.creatorName = configuration.lastUpdateName = TEST_PACKAGE_NAME;
+        configuration.creationTime = "04-04-2016";
+
+        serializeDeserializeWifiConfigurationForConfigStore(configuration);
+    }
+
+    /**
+     * Verify that an enabled network selection status object is serialized & deserialized
+     * correctly.
+     */
+    @Test
+    public void testEnabledNetworkSelectionStatusSerializeDeserialize()
+            throws IOException, XmlPullParserException {
+        NetworkSelectionStatus status = new NetworkSelectionStatus();
+        status.setNetworkSelectionStatus(NetworkSelectionStatus.NETWORK_SELECTION_ENABLED);
+        status.setNetworkSelectionDisableReason(NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
+        status.setConnectChoice(TEST_DUMMY_CONFIG_KEY);
+        status.setConnectChoiceTimestamp(867889);
+        status.setHasEverConnected(true);
+        serializeDeserializeNetworkSelectionStatus(status);
+    }
+
+    /**
+     * Verify that a temporarily disabled network selection status object is serialized &
+     * deserialized correctly.
+     */
+    @Test
+    public void testTemporarilyDisabledNetworkSelectionStatusSerializeDeserialize()
+            throws IOException, XmlPullParserException {
+        NetworkSelectionStatus status = new NetworkSelectionStatus();
+        status.setNetworkSelectionStatus(
+                NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED);
+        status.setNetworkSelectionDisableReason(
+                NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION);
+        serializeDeserializeNetworkSelectionStatus(status);
+    }
+
+    /**
+     * Verify that a WifiEnterpriseConfig object is serialized & deserialized correctly.
+     */
+    @Test
+    public void testWifiEnterpriseConfigSerializeDeserialize()
+            throws IOException, XmlPullParserException {
+        WifiEnterpriseConfig config = new WifiEnterpriseConfig();
+        config.setFieldValue(WifiEnterpriseConfig.IDENTITY_KEY, TEST_IDENTITY);
+        config.setFieldValue(WifiEnterpriseConfig.ANON_IDENTITY_KEY, TEST_ANON_IDENTITY);
+        config.setFieldValue(WifiEnterpriseConfig.PASSWORD_KEY, TEST_PASSWORD);
+        config.setFieldValue(WifiEnterpriseConfig.CLIENT_CERT_KEY, TEST_CLIENT_CERT);
+        config.setFieldValue(WifiEnterpriseConfig.CA_CERT_KEY, TEST_CA_CERT);
+        config.setFieldValue(WifiEnterpriseConfig.SUBJECT_MATCH_KEY, TEST_SUBJECT_MATCH);
+        config.setFieldValue(WifiEnterpriseConfig.ENGINE_KEY, TEST_ENGINE);
+        config.setFieldValue(WifiEnterpriseConfig.ENGINE_ID_KEY, TEST_ENGINE_ID);
+        config.setFieldValue(WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY, TEST_PRIVATE_KEY_ID);
+        config.setFieldValue(WifiEnterpriseConfig.ALTSUBJECT_MATCH_KEY, TEST_ALTSUBJECT_MATCH);
+        config.setFieldValue(WifiEnterpriseConfig.DOM_SUFFIX_MATCH_KEY, TEST_DOM_SUFFIX_MATCH);
+        config.setFieldValue(WifiEnterpriseConfig.CA_PATH_KEY, TEST_CA_PATH);
+        config.setEapMethod(TEST_EAP_METHOD);
+        config.setPhase2Method(TEST_PHASE2_METHOD);
+        serializeDeserializeWifiEnterpriseConfig(config);
+    }
+
+    private byte[] serializeWifiConfigurationForBackup(WifiConfiguration configuration)
+            throws IOException, XmlPullParserException {
+        final XmlSerializer out = new FastXmlSerializer();
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        out.setOutput(outputStream, StandardCharsets.UTF_8.name());
+        XmlUtil.writeDocumentStart(out, mXmlDocHeader);
+        WifiConfigurationXmlUtil.writeToXmlForBackup(out, configuration);
+        XmlUtil.writeDocumentEnd(out, mXmlDocHeader);
+        return outputStream.toByteArray();
+    }
+
+    private byte[] serializeWifiConfigurationForConfigStore(
+            WifiConfiguration configuration)
+            throws IOException, XmlPullParserException {
+        final XmlSerializer out = new FastXmlSerializer();
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        out.setOutput(outputStream, StandardCharsets.UTF_8.name());
+        XmlUtil.writeDocumentStart(out, mXmlDocHeader);
+        WifiConfigurationXmlUtil.writeToXmlForConfigStore(out, configuration);
+        XmlUtil.writeDocumentEnd(out, mXmlDocHeader);
+        return outputStream.toByteArray();
+    }
+
+    private Pair<String, WifiConfiguration> deserializeWifiConfiguration(byte[] data)
+            throws IOException, XmlPullParserException {
+        // Deserialize the configuration object.
+        final XmlPullParser in = Xml.newPullParser();
+        ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
+        in.setInput(inputStream, StandardCharsets.UTF_8.name());
+        XmlUtil.gotoDocumentStart(in, mXmlDocHeader);
+        return WifiConfigurationXmlUtil.parseFromXml(in, in.getDepth());
+    }
+
+    /**
+     * This helper method tests the serialization for backup/restore.
+     */
+    private void serializeDeserializeWifiConfigurationForBackupRestore(
+            WifiConfiguration configuration)
+            throws IOException, XmlPullParserException {
+        Pair<String, WifiConfiguration> retrieved;
+        // Test serialization/deserialization for config store.
+        retrieved =
+                deserializeWifiConfiguration(
+                        serializeWifiConfigurationForBackup(configuration));
+        assertEquals(retrieved.first, retrieved.second.configKey());
+        WifiConfigurationTestUtil.assertConfigurationEqualForBackup(
+                configuration, retrieved.second);
+    }
+
+    /**
+     * This helper method tests the serialization for config store.
+     */
+    private void serializeDeserializeWifiConfigurationForConfigStore(
+            WifiConfiguration configuration)
+            throws IOException, XmlPullParserException {
+        // Reset enterprise config because this needs to be serialized/deserialized separately.
+        configuration.enterpriseConfig = new WifiEnterpriseConfig();
+        Pair<String, WifiConfiguration> retrieved;
+        // Test serialization/deserialization for config store.
+        retrieved =
+                deserializeWifiConfiguration(
+                        serializeWifiConfigurationForConfigStore(configuration));
+        assertEquals(retrieved.first, retrieved.second.configKey());
+        WifiConfigurationTestUtil.assertConfigurationEqualForConfigStore(
+                configuration, retrieved.second);
+    }
+
+    /**
+     * This helper method tests both the serialization for backup/restore and config store.
+     */
+    private void serializeDeserializeWifiConfiguration(WifiConfiguration configuration)
+            throws IOException, XmlPullParserException {
+        Pair<String, WifiConfiguration> retrieved;
+        // Test serialization/deserialization for backup first.
+        serializeDeserializeWifiConfigurationForBackupRestore(configuration);
+
+        // Test serialization/deserialization for config store.
+        serializeDeserializeWifiConfigurationForConfigStore(configuration);
+    }
+
+    private void serializeDeserializeIpConfiguration(IpConfiguration configuration)
+            throws IOException, XmlPullParserException {
+        // Serialize the configuration object.
+        final XmlSerializer out = new FastXmlSerializer();
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        out.setOutput(outputStream, StandardCharsets.UTF_8.name());
+        XmlUtil.writeDocumentStart(out, mXmlDocHeader);
+        IpConfigurationXmlUtil.writeToXml(out, configuration);
+        XmlUtil.writeDocumentEnd(out, mXmlDocHeader);
+
+        // Deserialize the configuration object.
+        final XmlPullParser in = Xml.newPullParser();
+        ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
+        in.setInput(inputStream, StandardCharsets.UTF_8.name());
+        XmlUtil.gotoDocumentStart(in, mXmlDocHeader);
+        IpConfiguration retrievedConfiguration =
+                IpConfigurationXmlUtil.parseFromXml(in, in.getDepth());
+        assertEquals(configuration, retrievedConfiguration);
+    }
+
+    private void serializeDeserializeNetworkSelectionStatus(NetworkSelectionStatus status)
+            throws IOException, XmlPullParserException {
+        // Serialize the configuration object.
+        final XmlSerializer out = new FastXmlSerializer();
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        out.setOutput(outputStream, StandardCharsets.UTF_8.name());
+        XmlUtil.writeDocumentStart(out, mXmlDocHeader);
+        NetworkSelectionStatusXmlUtil.writeToXml(out, status);
+        XmlUtil.writeDocumentEnd(out, mXmlDocHeader);
+
+        // Deserialize the configuration object.
+        final XmlPullParser in = Xml.newPullParser();
+        ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
+        in.setInput(inputStream, StandardCharsets.UTF_8.name());
+        XmlUtil.gotoDocumentStart(in, mXmlDocHeader);
+        NetworkSelectionStatus retrievedStatus =
+                NetworkSelectionStatusXmlUtil.parseFromXml(in, in.getDepth());
+        WifiConfigurationTestUtil.assertNetworkSelectionStatusEqualForConfigStore(
+                status, retrievedStatus);
+    }
+
+    private void serializeDeserializeWifiEnterpriseConfig(WifiEnterpriseConfig config)
+            throws IOException, XmlPullParserException {
+        // Serialize the configuration object.
+        final XmlSerializer out = new FastXmlSerializer();
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        out.setOutput(outputStream, StandardCharsets.UTF_8.name());
+        XmlUtil.writeDocumentStart(out, mXmlDocHeader);
+        WifiEnterpriseConfigXmlUtil.writeToXml(out, config);
+        XmlUtil.writeDocumentEnd(out, mXmlDocHeader);
+
+        // Deserialize the configuration object.
+        final XmlPullParser in = Xml.newPullParser();
+        ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
+        in.setInput(inputStream, StandardCharsets.UTF_8.name());
+        XmlUtil.gotoDocumentStart(in, mXmlDocHeader);
+        WifiEnterpriseConfig retrievedConfig =
+                WifiEnterpriseConfigXmlUtil.parseFromXml(in, in.getDepth());
+        WifiConfigurationTestUtil.assertWifiEnterpriseConfigEqualForConfigStore(
+                config, retrievedConfig);
+    }
+}